From a3f62c910c0128e0b4312480414dbf8b26dbca1c Mon Sep 17 00:00:00 2001 From: Dmitry Ivakhnenko Date: Thu, 22 Dec 2022 14:07:25 +0300 Subject: [PATCH] fix render to string (#2115) * fix render to string * reuse render to buffer in tests * move all methods to one file * add more todos * add change set --- .changeset/tiny-jobs-hug.md | 5 + packages/renderer/src/dom/index.js | 1 + packages/renderer/src/index.js | 7 + packages/renderer/src/node/index.js | 11 +- packages/renderer/src/node/renderTo.js | 39 ++++++ packages/renderer/src/node/renderToFile.js | 21 --- packages/renderer/src/node/renderToStream.js | 9 -- packages/renderer/src/node/renderToString.js | 8 -- packages/renderer/tests/renderComponent.js | 131 ++++++++----------- 9 files changed, 110 insertions(+), 122 deletions(-) create mode 100644 .changeset/tiny-jobs-hug.md create mode 100644 packages/renderer/src/node/renderTo.js delete mode 100644 packages/renderer/src/node/renderToFile.js delete mode 100644 packages/renderer/src/node/renderToStream.js delete mode 100644 packages/renderer/src/node/renderToString.js diff --git a/.changeset/tiny-jobs-hug.md b/.changeset/tiny-jobs-hug.md new file mode 100644 index 000000000..cb23e0d0a --- /dev/null +++ b/.changeset/tiny-jobs-hug.md @@ -0,0 +1,5 @@ +--- +'@react-pdf/renderer': patch +--- + +fix `renderToString` method in node js diff --git a/packages/renderer/src/dom/index.js b/packages/renderer/src/dom/index.js index 5a7b2c956..01ca53c6b 100644 --- a/packages/renderer/src/dom/index.js +++ b/packages/renderer/src/dom/index.js @@ -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, diff --git a/packages/renderer/src/index.js b/packages/renderer/src/index.js index 7946aae44..9b2cb8cd7 100644 --- a/packages/renderer/src/index.js +++ b/packages/renderer/src/index.js @@ -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 diff --git a/packages/renderer/src/node/index.js b/packages/renderer/src/node/index.js index de47c0d86..b12aa3acb 100644 --- a/packages/renderer/src/node/index.js +++ b/packages/renderer/src/node/index.js @@ -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 => { @@ -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, diff --git a/packages/renderer/src/node/renderTo.js b/packages/renderer/src/node/renderTo.js new file mode 100644 index 000000000..e0d0287f7 --- /dev/null +++ b/packages/renderer/src/node/renderTo.js @@ -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()); diff --git a/packages/renderer/src/node/renderToFile.js b/packages/renderer/src/node/renderToFile.js deleted file mode 100644 index a3fefef24..000000000 --- a/packages/renderer/src/node/renderToFile.js +++ /dev/null @@ -1,21 +0,0 @@ -import fs from 'fs'; - -import { pdf } from '../index'; - -export const renderToFile = async (element, filePath, callback) => { - const instance = pdf(element); - const output = await instance.toBuffer(); - 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 default renderToFile; diff --git a/packages/renderer/src/node/renderToStream.js b/packages/renderer/src/node/renderToStream.js deleted file mode 100644 index 7857f119e..000000000 --- a/packages/renderer/src/node/renderToStream.js +++ /dev/null @@ -1,9 +0,0 @@ -import { pdf } from '../index'; - -export const renderToStream = async element => { - const instance = pdf(element); - const buffer = await instance.toBuffer(); - return buffer; -}; - -export default renderToStream; diff --git a/packages/renderer/src/node/renderToString.js b/packages/renderer/src/node/renderToString.js deleted file mode 100644 index 35b92666c..000000000 --- a/packages/renderer/src/node/renderToString.js +++ /dev/null @@ -1,8 +0,0 @@ -import { pdf } from '../index'; - -export const renderToString = element => { - const instance = pdf(element); - return instance.toString(); -}; - -export default renderToString; diff --git a/packages/renderer/tests/renderComponent.js b/packages/renderer/tests/renderComponent.js index 89589a15f..550d3e9fa 100644 --- a/packages/renderer/tests/renderComponent.js +++ b/packages/renderer/tests/renderComponent.js @@ -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;