Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/layout/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@react-pdf/yoga": "^4.1.2",
"cross-fetch": "^3.1.5",
"emoji-regex": "^10.2.1",
"lodash.flatten": "^4.4.0",
"queue": "^6.0.1"
},
"devDependencies": {
Expand Down
22 changes: 16 additions & 6 deletions packages/layout/src/node/createInstances.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { castArray } from '@react-pdf/fns';
import { TextInstance } from '@react-pdf/primitives';
import { TextInstance, Image } from '@react-pdf/primitives';
import flatten from 'lodash.flatten';

import fetchImage from '../image/fetchImage';

const isString = value => typeof value === 'string';

const isNumber = value => typeof value === 'number';

const isImage = value => value.type === Image;

const isFragment = value =>
value && value.type === Symbol.for('react.fragment');

Expand All @@ -16,7 +21,7 @@ const isFragment = value =>
* @param {Object} React element
* @returns {Array} parsed react elements
*/
const createInstances = element => {
const createInstances = async element => {
if (!element) return [];

if (isString(element) || isNumber(element)) {
Expand All @@ -34,16 +39,21 @@ const createInstances = element => {
if (!isString(element.type)) {
return createInstances(element.type(element.props));
}

const {
type,
props: { style = {}, children = [], ...props },
} = element;

const nextChildren = castArray(children).reduce(
(acc, child) => acc.concat(createInstances(child)),
[],
if (isImage(element)) {
const node = { props, type, style, box: {} };
await fetchImage(node);
return [node];
}

const instances = await Promise.all(
castArray(children).map(child => createInstances(child)),
);
const nextChildren = flatten(instances);

return [
{
Expand Down
67 changes: 41 additions & 26 deletions packages/layout/src/steps/resolvePagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,44 +147,51 @@ const shouldResolveDynamicNodes = node => {
return isDynamic(node) || children.some(shouldResolveDynamicNodes);
};

const resolveDynamicNodes = (props, node) => {
const resolveDynamicNodes = async (props, node) => {
const isNodeDynamic = isDynamic(node);

// Call render prop on dynamic nodes and append result to children
const resolveChildren = (children = []) => {
const resolveChildren = async (children = []) => {
if (isNodeDynamic) {
const res = node.props.render(props);
return createInstances(res)
.filter(Boolean)
.map(n => resolveDynamicNodes(props, n));
const dynamicRenderResult = node.props.render(props);
const dynamicInstances = await createInstances(dynamicRenderResult);
const resolvedDynamicNodes = await Promise.all(
dynamicInstances
.filter(Boolean)
.map(n => resolveDynamicNodes(props, n)),
);
return resolvedDynamicNodes;
}

return children.map(c => resolveDynamicNodes(props, c));
const resolvedChildren = await Promise.all(
children.map(c => resolveDynamicNodes(props, c)),
);
return resolvedChildren;
};

// We reset dynamic text box so it can be computed again later on
const resetHeight = isNodeDynamic && isText(node);
const box = resetHeight ? { ...node.box, height: 0 } : node.box;

const children = resolveChildren(node.children);
const children = await resolveChildren(node.children);
const lines = isNodeDynamic ? null : node.lines;

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);
const resolvedPage = await resolveDynamicNodes(props, page);
return relayoutPage(resolvedPage, fontStore);
}

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(
Expand Down Expand Up @@ -217,7 +224,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 = {
Expand All @@ -242,18 +249,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];
Expand All @@ -270,24 +282,27 @@ const paginate = (page, pageNumber, fontStore) => {
* @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);

// eslint-disable-next-line no-restricted-syntax
for (const page of doc.children) {
// eslint-disable-next-line no-await-in-loop
let subpages = await paginate(page, pageNumber, fontStore);
subpages = assocSubPageData(subpages);
pageNumber += subpages.length;
pages = pages.concat(subpages);
}

pages = pages.map((...args) =>
dissocSubPageData(resolvePageIndices(fontStore, ...args)),
);

return assingChildren(pages, doc);
const nextPages = [];
// eslint-disable-next-line no-restricted-syntax
for (const [index, page] of pages.entries()) {
// eslint-disable-next-line no-await-in-loop
const indices = await resolvePageIndices(fontStore, page, index, pages);
nextPages.push(dissocSubPageData(indices));
}
return assingChildren(nextPages, doc);
};

export default resolvePagination;