From b1fce3b7bd8eff1585ddf9b17cd6e4dac400f5c5 Mon Sep 17 00:00:00 2001 From: mskec Date: Fri, 10 Mar 2023 21:09:56 +0100 Subject: [PATCH] fix: fetch assets inside dynamic content (#1369, #1587, #1630, #1936, #2064) --- .../layout/src/steps/resolvePagination.js | 61 +++++++++++------- .../tests/steps/resolvePagination.test.js | 21 +++--- .../renderer/tests/dynamicContent.test.js | 26 ++++++++ ...-content-should-render-an-image-1-snap.png | Bin 0 -> 20778 bytes 4 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 packages/renderer/tests/dynamicContent.test.js create mode 100644 packages/renderer/tests/snapshots/dynamic-content-test-js-dynamic-content-should-render-an-image-1-snap.png diff --git a/packages/layout/src/steps/resolvePagination.js b/packages/layout/src/steps/resolvePagination.js index c4a84903e..936ed3759 100644 --- a/packages/layout/src/steps/resolvePagination.js +++ b/packages/layout/src/steps/resolvePagination.js @@ -2,7 +2,7 @@ /* eslint-disable prefer-destructuring */ import * as P from '@react-pdf/primitives'; -import { isNil, omit, compose } from '@react-pdf/fns'; +import { isNil, omit, asyncCompose } from '@react-pdf/fns'; import isFixed from '../node/isFixed'; import splitText from '../text/splitText'; @@ -15,6 +15,7 @@ import shouldNodeBreak from '../node/shouldBreak'; import resolveTextLayout from './resolveTextLayout'; import resolveInheritance from './resolveInheritance'; import { resolvePageDimensions } from './resolveDimensions'; +import resolveAssets from './resolveAssets'; const isText = node => node.type === P.Text; @@ -30,7 +31,8 @@ const allFixed = nodes => nodes.every(isFixed); const isDynamic = node => !isNil(node.props?.render); -const relayoutPage = compose( +const relayoutPage = asyncCompose( + resolveAssets, resolveTextLayout, resolveInheritance, resolvePageDimensions, @@ -172,19 +174,20 @@ const resolveDynamicNodes = (props, node) => { return Object.assign({}, node, { box, lines, children }); }; -const resolveDynamicPage = (props, page, fontStore) => { +const resolveDynamicPage = async (props, page, fontStore) => { if (shouldResolveDynamicNodes(page)) { const resolvedPage = resolveDynamicNodes(props, page); - return relayoutPage(resolvedPage, fontStore); + const relayoutedPage = await relayoutPage(resolvedPage, fontStore); + return relayoutedPage; } return page; }; -const splitPage = (page, pageNumber, fontStore) => { +const splitPage = async (page, pageNumber, fontStore) => { const wrapArea = getWrapArea(page); const contentArea = getContentArea(page); - const dynamicPage = resolveDynamicPage({ pageNumber }, page, fontStore); + const dynamicPage = await resolveDynamicPage({ pageNumber }, page, fontStore); const height = page.style.height; const [currentChilds, nextChilds] = splitNodes( @@ -193,10 +196,10 @@ const splitPage = (page, pageNumber, fontStore) => { dynamicPage.children, ); - const relayout = node => relayoutPage(node, fontStore); + const relayout = async node => relayoutPage(node, fontStore); const currentBox = { ...page.box, height }; - const currentPage = relayout( + const currentPage = await relayout( Object.assign({}, page, { box: currentBox, children: currentChilds }), ); @@ -206,7 +209,7 @@ const splitPage = (page, pageNumber, fontStore) => { const nextBox = omit('height', page.box); const nextProps = omit('bookmark', page.props); - const nextPage = relayout( + const nextPage = await relayout( Object.assign({}, page, { props: nextProps, box: nextBox, @@ -217,7 +220,7 @@ const splitPage = (page, pageNumber, fontStore) => { return [currentPage, nextPage]; }; -const resolvePageIndices = (fontStore, page, pageNumber, pages) => { +const resolvePageIndices = async (fontStore, page, pageNumber, pages) => { const totalPages = pages.length; const props = { @@ -242,18 +245,23 @@ const dissocSubPageData = page => { return omit(['subPageNumber', 'subPageTotalPages'], page); }; -const paginate = (page, pageNumber, fontStore) => { +const paginate = async (page, pageNumber, fontStore) => { if (!page) return []; if (page.props?.wrap === false) return [page]; - let splittedPage = splitPage(page, pageNumber, fontStore); + let splittedPage = await splitPage(page, pageNumber, fontStore); const pages = [splittedPage[0]]; let nextPage = splittedPage[1]; while (nextPage !== null) { - splittedPage = splitPage(nextPage, pageNumber + pages.length, fontStore); + // eslint-disable-next-line no-await-in-loop + splittedPage = await splitPage( + nextPage, + pageNumber + pages.length, + fontStore, + ); pages.push(splittedPage[0]); nextPage = splittedPage[1]; @@ -263,28 +271,31 @@ const paginate = (page, pageNumber, fontStore) => { }; /** - * Performs pagination. This is the step responsible of breaking the whole document - * into pages following pagiation rules, such as `fixed`, `break` and dynamic nodes. + * Performs pagination. This is the step responsible for breaking the whole document + * into pages following pagination rules, such as `fixed`, `break` and dynamic nodes. * * @param {Object} node * @param {Object} fontStore font store * @returns {Object} layout node */ -const resolvePagination = (doc, fontStore) => { +const resolvePagination = async (doc, fontStore) => { let pages = []; let pageNumber = 1; - for (let i = 0; i < doc.children.length; i += 1) { - const page = doc.children[i]; - let subpages = paginate(page, pageNumber, fontStore); + await Promise.all( + doc.children.map(async page => { + let subpages = await paginate(page, pageNumber, fontStore); - subpages = assocSubPageData(subpages); - pageNumber += subpages.length; - pages = pages.concat(subpages); - } + subpages = assocSubPageData(subpages); + pageNumber += subpages.length; + pages.push(...subpages); + }), + ); - pages = pages.map((...args) => - dissocSubPageData(resolvePageIndices(fontStore, ...args)), + pages = await Promise.all( + pages.map(async (...args) => + dissocSubPageData(await resolvePageIndices(fontStore, ...args)), + ), ); return assingChildren(pages, doc); diff --git a/packages/layout/tests/steps/resolvePagination.test.js b/packages/layout/tests/steps/resolvePagination.test.js index 8f76e178c..5ebaa2543 100644 --- a/packages/layout/tests/steps/resolvePagination.test.js +++ b/packages/layout/tests/steps/resolvePagination.test.js @@ -5,7 +5,7 @@ import resolveDimensions from '../../src/steps/resolveDimensions'; const calcLayout = node => resolvePagination(resolveDimensions(node)); describe('pagination step', () => { - test('should stretch absolute block to full page size', () => { + test('should stretch absolute block to full page size', async () => { const root = { type: 'DOCUMENT', children: [ @@ -46,7 +46,7 @@ describe('pagination step', () => { ], }; - const layout = calcLayout(root); + const layout = await calcLayout(root); const page = layout.children[0]; const view = layout.children[0].children[0]; @@ -55,7 +55,7 @@ describe('pagination step', () => { expect(view.box.height).toBe(100); }); - test('should force new height for split nodes', () => { + test('should force new height for split nodes', async () => { const root = { type: 'DOCUMENT', children: [ @@ -93,7 +93,7 @@ describe('pagination step', () => { ], }; - const layout = calcLayout(root); + const layout = await calcLayout(root); const view1 = layout.children[0].children[0]; const view2 = layout.children[1].children[0]; @@ -102,7 +102,7 @@ describe('pagination step', () => { expect(view2.box.height).not.toBe(60); }); - test('should force new height for split nodes with fixed height', () => { + test('should force new height for split nodes with fixed height', async () => { const root = { type: 'DOCUMENT', children: [ @@ -127,7 +127,7 @@ describe('pagination step', () => { ], }; - const layout = calcLayout(root); + const layout = await calcLayout(root); const view1 = layout.children[0].children[0]; const view2 = layout.children[1].children[0]; @@ -138,7 +138,7 @@ describe('pagination step', () => { expect(view3.box.height).toBe(10); }); - test('should not wrap page with false wrap prop', () => { + test('should not wrap page with false wrap prop', async () => { const root = { type: 'DOCUMENT', children: [ @@ -165,12 +165,12 @@ describe('pagination step', () => { ], }; - const layout = calcLayout(root); + const layout = await calcLayout(root); expect(layout.children.length).toBe(1); }); - test('should break on a container whose children can not fit on a page', () => { + test('should break on a container whose children can not fit on a page', async () => { const root = { type: 'DOCUMENT', children: [ @@ -219,8 +219,7 @@ describe('pagination step', () => { ], }; - const layout = calcLayout(root); - console.log(layout.children[0].children); + const layout = await calcLayout(root); const page1 = layout.children[0]; const page2 = layout.children[1]; diff --git a/packages/renderer/tests/dynamicContent.test.js b/packages/renderer/tests/dynamicContent.test.js new file mode 100644 index 000000000..9d29aeef2 --- /dev/null +++ b/packages/renderer/tests/dynamicContent.test.js @@ -0,0 +1,26 @@ +import { Document, Image, Page, View } from '..'; +import renderToImage from './renderComponent'; + +const mount = async children => { + const image = await renderToImage( + + {children} + , + ); + + return image; +}; + +describe('dynamic content', () => { + test('should render an image', async () => { + const url = + 'https://user-images.githubusercontent.com/5600341/27505816-c8bc37aa-587f-11e7-9a86-08a2d081a8b9.png'; + const image = await mount( + } + />, + ); + + expect(image).toMatchImageSnapshot(); + }, 10000); +}); diff --git a/packages/renderer/tests/snapshots/dynamic-content-test-js-dynamic-content-should-render-an-image-1-snap.png b/packages/renderer/tests/snapshots/dynamic-content-test-js-dynamic-content-should-render-an-image-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb477f1fb801d2236ad19dc3cf051354b1b7d17 GIT binary patch literal 20778 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 UF&9h0<%h>iuUZ<_8r(_zFPi={&j0`b literal 0 HcmV?d00001