Skip to content

Commit

Permalink
fix render to string (#2115)
Browse files Browse the repository at this point in the history
* fix render to string

* reuse render to buffer in tests

* move all methods to one file

* add more todos

* add change set
  • Loading branch information
jeetiss authored Dec 22, 2022
1 parent b385dcb commit a3f62c9
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 122 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-jobs-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@react-pdf/renderer': patch
---

fix `renderToString` method in node js
1 change: 1 addition & 0 deletions packages/renderer/src/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export * from './PDFDownloadLink';

export * from '@react-pdf/primitives';

// TODO: remove this default export in next major release because it breaks tree-shacking
export default {
pdf,
usePDF,
Expand Down
7 changes: 7 additions & 0 deletions packages/renderer/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,18 @@ const pdf = initialValue => {
});
};

// TODO: rename this method to `toStream` in next major release, because it return stream not a buffer
const toBuffer = async () => {
callOnRender();
return render();
};

/*
* TODO: remove this method in next major release. it is buggy
* see
* - https://github.com/diegomura/react-pdf/issues/2112
* - https://github.com/diegomura/react-pdf/issues/2095
*/
const toString = async () => {
let result = '';
const instance = await render(false); // For some reason, when rendering to string if compress=true the document is blank
Expand Down
11 changes: 3 additions & 8 deletions packages/renderer/src/node/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as primitives from '@react-pdf/primitives';

import renderToFile from './renderToFile';
import renderToStream from './renderToStream';
import renderToString from './renderToString';
import { renderToFile, renderToStream, renderToString } from './renderTo';
import { pdf, version, Font, StyleSheet } from '../index';

const throwEnvironmentError = name => {
Expand Down Expand Up @@ -31,14 +29,11 @@ export const render = renderToFile;

export * from '../index';

export * from './renderToFile';

export * from './renderToStream';

export * from './renderToString';
export * from './renderTo';

export * from '@react-pdf/primitives';

// TODO: remove this default export in next major release because it breaks tree-shacking
export default {
pdf,
Font,
Expand Down
39 changes: 39 additions & 0 deletions packages/renderer/src/node/renderTo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from 'fs';
import { Buffer } from 'buffer';

import { pdf } from '../index';

export const renderToStream = async element => {
const instance = pdf(element);
const stream = await instance.toBuffer();
return stream;
};

export const renderToFile = async (element, filePath, callback) => {
const output = await renderToStream(element);
const stream = fs.createWriteStream(filePath);

output.pipe(stream);

return new Promise((resolve, reject) => {
stream.on('finish', () => {
if (callback) callback(output, filePath);
resolve(output);
});
stream.on('error', reject);
});
};

export const renderToBuffer = element =>
renderToStream(element).then(
stream =>
new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks)));
stream.on('error', error => reject(error));
}),
);

export const renderToString = element =>
renderToBuffer(element).then(buffer => buffer.toString());
21 changes: 0 additions & 21 deletions packages/renderer/src/node/renderToFile.js

This file was deleted.

9 changes: 0 additions & 9 deletions packages/renderer/src/node/renderToStream.js

This file was deleted.

8 changes: 0 additions & 8 deletions packages/renderer/src/node/renderToString.js

This file was deleted.

131 changes: 55 additions & 76 deletions packages/renderer/tests/renderComponent.js
Original file line number Diff line number Diff line change
@@ -1,128 +1,107 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-param-reassign */

import { Buffer } from 'buffer'
import Canvas from 'canvas'
import * as pdfjs from 'pdfjs-dist/legacy/build/pdf'
import Canvas from 'canvas';
import * as pdfjs from 'pdfjs-dist/legacy/build/pdf';

import { renderToStream } from '..'
import { renderToBuffer } from '..';

/**
* copy-pasted code from
* https://github.com/mozilla/pdf.js/blob/master/examples/node/pdf2png/pdf2png.js#L20-L49
*/
const NodeCanvasFactory = {
create (width, height) {
const canvas = Canvas.createCanvas(width, height)
const context = canvas.getContext('2d')
create(width, height) {
const canvas = Canvas.createCanvas(width, height);
const context = canvas.getContext('2d');
return {
canvas,
context
}
context,
};
},

reset (canvasAndContext, width, height) {
canvasAndContext.canvas.width = width
canvasAndContext.canvas.height = height
reset(canvasAndContext, width, height) {
canvasAndContext.canvas.width = width;
canvasAndContext.canvas.height = height;
},

destroy (canvasAndContext) {
canvasAndContext.canvas.width = 0
canvasAndContext.canvas.height = 0
canvasAndContext.canvas = null
canvasAndContext.context = null
}
}
destroy(canvasAndContext) {
canvasAndContext.canvas.width = 0;
canvasAndContext.canvas.height = 0;
canvasAndContext.canvas = null;
canvasAndContext.context = null;
},
};

async function getCanvas (pagePromise) {
const page = await pagePromise
const viewport = page.getViewport({ scale: 1.0 })
const canvasFactory = NodeCanvasFactory
async function getCanvas(pagePromise) {
const page = await pagePromise;
const viewport = page.getViewport({ scale: 1.0 });
const canvasFactory = NodeCanvasFactory;
const { canvas, context } = canvasFactory.create(
viewport.width,
viewport.height
)
viewport.height,
);
const renderContext = {
canvasContext: context,
viewport,
canvasFactory
}
canvasFactory,
};

const renderTask = page.render(renderContext)
await renderTask.promise
const renderTask = page.render(renderContext);
await renderTask.promise;

return canvas
return canvas;
}

const composeCanvases = (canvases) => {
const composeCanvases = canvases => {
const [maxWidth, maxHeight] = canvases.reduce(
([width, height], canvas) => [
Math.max(width, canvas.width),
Math.max(height, canvas.height)
Math.max(height, canvas.height),
],
[0, 0]
)
[0, 0],
);

const resultCanvas = Canvas.createCanvas(
maxWidth,
maxHeight * canvases.length
)
const resultContext = resultCanvas.getContext('2d')
maxHeight * canvases.length,
);
const resultContext = resultCanvas.getContext('2d');

canvases.forEach((canvas, index) => {
resultContext.drawImage(canvas, 0, maxHeight * index)
})
resultContext.drawImage(canvas, 0, maxHeight * index);
});

return resultCanvas
}
return resultCanvas;
};

/**
* Generates a array with numbers from 0 to length-1
* @param {number} length — size of array
* @returns {number[]} array
*/
const range = (length) => Array.from({ length }, (_, index) => index)
const range = length => Array.from({ length }, (_, index) => index);

/**
* Renders `@react-pdf/renderer` element to buffer
* @param {import('react').ReactElement} element — react element
* @returns {Buffer} raw pdf buffer
*/
async function renderToBuffer (element) {
const stream = await renderToStream(element)
return new Promise((resolve) => {
const buffers = []
stream.on('data', (d) => {
buffers.push(d)
})
stream.on('end', () => {
resolve(Buffer.concat(buffers))
})
})
}

const renderComponent = async (element) => {
const source = await renderToBuffer(element)
const renderComponent = async element => {
const source = await renderToBuffer(element);

const document = await pdfjs.getDocument({
data: source.buffer,
verbosity: 0
}).promise
verbosity: 0,
}).promise;

const pages = range(document.numPages).map((pageIndex) =>
document.getPage(pageIndex + 1)
)
const pages = range(document.numPages).map(pageIndex =>
document.getPage(pageIndex + 1),
);

if (pages.length === 1) {
return (await getCanvas(pages[0])).toBuffer()
}
return (await getCanvas(pages[0])).toBuffer();
}

const canvases = await Promise.all(
pages.map((page) => getCanvas(page))
)
const pageSnapshots = composeCanvases(canvases)
const canvases = await Promise.all(pages.map(page => getCanvas(page)));
const pageSnapshots = composeCanvases(canvases);

return pageSnapshots.toBuffer()
}
return pageSnapshots.toBuffer();
};

export default renderComponent
export default renderComponent;

0 comments on commit a3f62c9

Please sign in to comment.