diff --git a/packages/layout/src/steps/resolvePagination.js b/packages/layout/src/steps/resolvePagination.js
index 98d6f3135..b3efe7f1a 100644
--- a/packages/layout/src/steps/resolvePagination.js
+++ b/packages/layout/src/steps/resolvePagination.js
@@ -3,7 +3,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';
@@ -17,6 +17,7 @@ import resolveTextLayout from './resolveTextLayout';
import resolveInheritance from './resolveInheritance';
import { resolvePageDimensions } from './resolveDimensions';
import { resolvePageStyles } from './resolveStyles';
+import resolveAssets from './resolveAssets';
const isText = (node) => node.type === P.Text;
@@ -32,7 +33,8 @@ const allFixed = (nodes) => nodes.every(isFixed);
const isDynamic = (node) => !isNil(node.props?.render);
-const relayoutPage = compose(
+const relayoutPage = asyncCompose(
+ resolveAssets,
resolveTextLayout,
resolvePageDimensions,
resolveInheritance,
@@ -175,19 +177,20 @@ const resolveDynamicNodes = (props, node) => {
return Object.assign({}, node, { box, lines, children });
};
-const resolveDynamicPage = (props, page, fontStore, yoga) => {
+const resolveDynamicPage = async (props, page, fontStore, yoga) => {
if (shouldResolveDynamicNodes(page)) {
const resolvedPage = resolveDynamicNodes(props, page);
- return relayoutPage(resolvedPage, fontStore, yoga);
+ const relayoutedPage = await relayoutPage(resolvedPage, fontStore, yoga);
+ return relayoutedPage;
}
return page;
};
-const splitPage = (page, pageNumber, fontStore, yoga) => {
+const splitPage = async (page, pageNumber, fontStore, yoga) => {
const wrapArea = getWrapArea(page);
const contentArea = getContentArea(page);
- const dynamicPage = resolveDynamicPage({ pageNumber }, page, fontStore, yoga);
+ const dynamicPage = await resolveDynamicPage({ pageNumber }, page, fontStore, yoga);
const height = page.style.height;
const [currentChilds, nextChilds] = splitNodes(
@@ -196,10 +199,10 @@ const splitPage = (page, pageNumber, fontStore, yoga) => {
dynamicPage.children,
);
- const relayout = (node) => relayoutPage(node, fontStore, yoga);
+ const relayout = async node => relayoutPage(node, fontStore, yoga);
const currentBox = { ...page.box, height };
- const currentPage = relayout(
+ const currentPage = await relayout(
Object.assign({}, page, { box: currentBox, children: currentChilds }),
);
@@ -209,7 +212,7 @@ const splitPage = (page, pageNumber, fontStore, yoga) => {
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,
@@ -220,7 +223,7 @@ const splitPage = (page, pageNumber, fontStore, yoga) => {
return [currentPage, nextPage];
};
-const resolvePageIndices = (fontStore, yoga, page, pageNumber, pages) => {
+const resolvePageIndices = async (fontStore, yoga, page, pageNumber, pages) => {
const totalPages = pages.length;
const props = {
@@ -233,24 +236,25 @@ const resolvePageIndices = (fontStore, yoga, page, pageNumber, pages) => {
return resolveDynamicPage(props, page, fontStore, yoga);
};
-const assocSubPageData = (subpages) => {
+const assocSubPageData = (subpages, pageIndex) => {
return subpages.map((page, i) => ({
...page,
+ pageIndex,
subPageNumber: i,
subPageTotalPages: subpages.length,
}));
};
-const dissocSubPageData = (page) => {
- return omit(['subPageNumber', 'subPageTotalPages'], page);
+const dissocSubPageData = page => {
+ return omit(['pageIndex', 'subPageNumber', 'subPageTotalPages'], page);
};
-const paginate = (page, pageNumber, fontStore, yoga) => {
+const paginate = async (page, pageNumber, fontStore, yoga) => {
if (!page) return [];
if (page.props?.wrap === false) return [page];
- let splittedPage = splitPage(page, pageNumber, fontStore, yoga);
+ let splittedPage = await splitPage(page, pageNumber, fontStore, yoga);
const pages = [splittedPage[0]];
let nextPage = splittedPage[1];
@@ -271,28 +275,40 @@ const paginate = (page, pageNumber, fontStore, yoga) => {
};
/**
- * 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} doc 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, doc.yoga);
+ await Promise.all(
+ doc.children.map(async (page, pageIndex) => {
+ let subpages = await paginate(page, pageNumber, fontStore, doc.yoga);
- subpages = assocSubPageData(subpages);
- pageNumber += subpages.length;
- pages = pages.concat(subpages);
- }
+ subpages = assocSubPageData(subpages, pageIndex);
+ pageNumber += subpages.length;
+ pages.push(...subpages);
+ }),
+ );
+
+ // because the subpages are pushed into the array according to the speed they are paginated,
+ // we sort them by their initial index, while keeping the subpages order.
+ pages.sort((a, b) => {
+ if (a.pageIndex !== b.pageIndex) {
+ return a.pageIndex - b.pageIndex;
+ }
+ return a.subPageNumber - b.subPageNumber;
+ });
- pages = pages.map((...args) =>
- dissocSubPageData(resolvePageIndices(fontStore, doc.yoga, ...args)),
+ pages = await Promise.all(
+ pages.map(async (...args) =>
+ dissocSubPageData(await resolvePageIndices(fontStore, doc.yoga, ...args)),
+ ),
);
return assingChildren(pages, doc);
diff --git a/packages/layout/tests/steps/resolvePagination.test.js b/packages/layout/tests/steps/resolvePagination.test.jsx
similarity index 58%
rename from packages/layout/tests/steps/resolvePagination.test.js
rename to packages/layout/tests/steps/resolvePagination.test.jsx
index f84ba7215..7f1540080 100644
--- a/packages/layout/tests/steps/resolvePagination.test.js
+++ b/packages/layout/tests/steps/resolvePagination.test.jsx
@@ -1,9 +1,9 @@
import { describe, expect, test } from 'vitest';
-
-import { loadYoga } from '../../src/yoga';
+import { Text } from '@react-pdf/primitives';
import resolvePagination from '../../src/steps/resolvePagination';
import resolveDimensions from '../../src/steps/resolveDimensions';
+import { loadYoga } from '../../src/yoga';
// dimensions is required by pagination step and them are calculated here
const calcLayout = (node) => resolvePagination(resolveDimensions(node));
@@ -53,7 +53,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];
@@ -103,7 +103,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];
@@ -140,7 +140,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];
@@ -151,6 +151,161 @@ describe('pagination step', () => {
expect(view3.box.height).toBe(10);
});
+ test.skip('should calculate height and keep the page order', async () => {
+ const yoga = await loadYoga();
+
+ const root = {
+ type: 'DOCUMENT',
+ yoga,
+ children: [
+ {
+ type: 'PAGE',
+ box: {},
+ style: {
+ width: 5,
+ height: 60,
+ },
+ children: [
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 18 },
+ props: { fixed: true },
+ children: [],
+ },
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 30 },
+ props: {},
+ children: [],
+ },
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 57 },
+ props: {},
+ children: [],
+ },
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 15 },
+ props: {},
+ children: [],
+ },
+ ],
+ },
+ {
+ type: 'PAGE',
+ box: {},
+ style: {
+ height: 50,
+ },
+ children: [
+ {
+ type: 'VIEW',
+ box: {},
+ style: {},
+ props: {
+ fixed: true,
+ render: () => rear window,
+ },
+ children: [],
+ },
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 22 },
+ props: {},
+ children: [],
+ },
+ ],
+ },
+ {
+ type: 'PAGE',
+ box: {},
+ style: {
+ height: 40,
+ },
+ children: [
+ {
+ type: 'VIEW',
+ box: {},
+ style: { height: 12 },
+ props: {},
+ children: [],
+ },
+ ],
+ },
+ {
+ type: 'PAGE',
+ box: {},
+ style: {
+ height: 30,
+ },
+ children: [
+ {
+ type: 'VIEW',
+ box: {},
+ style: {},
+ props: {},
+ children: [],
+ },
+ ],
+ },
+ ],
+ };
+
+ const layout = await calcLayout(root);
+
+ const page1 = layout.children[0];
+ const [view1, view2, view3] = page1.children;
+
+ const page2 = layout.children[1];
+ const [view4, view5] = page2.children;
+
+ const page3 = layout.children[2];
+ const [view6, view7, view8] = page3.children;
+
+ const page4 = layout.children[3];
+ const [view9, view10] = page4.children;
+
+ const page5 = layout.children[4];
+ const [view11] = page5.children;
+
+ const page6 = layout.children[5];
+
+ // page 1
+ expect(view1.box.height).toBe(18); // fixed header
+ expect(view2.box.height).toBe(30);
+ expect(view3.box.height).toBe(12);
+ expect(page1.box.height).toBe(60);
+
+ // page 2
+ expect(view4.box.height).toBe(18); // fixed header
+ expect(view5.box.height).toBe(42);
+ expect(page2.box.height).toBe(60);
+
+ // page 3
+ expect(view6.box.height).toBe(18); // fixed header
+ expect(view7.box.height).toBe(3);
+ expect(view8.box.height).toBe(15);
+ expect(page3.box.height).toBe(60);
+
+ // page 4
+ expect(view9.box.height).toBe(10);
+ expect(view10.box.height).toBe(22);
+ expect(page4.box.height).toBe(50);
+
+ // page 5
+ expect(page5.box.height).toBe(40);
+ expect(view11.box.height).toBe(12);
+
+ // page 6
+ expect(page6.box.height).toBe(30);
+ });
+
test('should not wrap page with false wrap prop', async () => {
const yoga = await loadYoga();
@@ -181,7 +336,7 @@ describe('pagination step', () => {
],
};
- const layout = calcLayout(root);
+ const layout = await calcLayout(root);
expect(layout.children.length).toBe(1);
});
@@ -238,7 +393,7 @@ describe('pagination step', () => {
],
};
- const layout = calcLayout(root);
+ const layout = await calcLayout(root);
const page1 = layout.children[0];
const page2 = layout.children[1];
diff --git a/packages/renderer/tests/dynamicContent.test.jsx b/packages/renderer/tests/dynamicContent.test.jsx
new file mode 100644
index 000000000..1e9c5e8e2
--- /dev/null
+++ b/packages/renderer/tests/dynamicContent.test.jsx
@@ -0,0 +1,28 @@
+import { describe, expect, test } from 'vitest';
+
+import { Document, Image, Page, View } from '@react-pdf/renderer';
+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-jsx-tests-dynamic-content-test-jsx-dynamic-content-should-render-an-image-1-snap.png b/packages/renderer/tests/snapshots/dynamic-content-test-jsx-tests-dynamic-content-test-jsx-dynamic-content-should-render-an-image-1-snap.png
new file mode 100644
index 000000000..dbb477f1f
Binary files /dev/null and b/packages/renderer/tests/snapshots/dynamic-content-test-jsx-tests-dynamic-content-test-jsx-dynamic-content-should-render-an-image-1-snap.png differ