ats-form-react-pdf-layout 4.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,3015 @@
1
+ import { upperFirst, capitalize, parseFloat as parseFloat$1, without, pick, compose, evolve, mapValues, matchPercent, castArray, isNil, omit, asyncCompose } from '@react-pdf/fns';
2
+ import * as P from '@react-pdf/primitives';
3
+ import resolveStyle, { transformColor, flatten } from '@react-pdf/stylesheet';
4
+ import layoutEngine, { fontSubstitution, wordHyphenation, scriptItemizer, textDecoration, justification, linebreaker, bidi, fromFragments } from '@react-pdf/textkit';
5
+ import * as Yoga from 'yoga-layout/load';
6
+ import { loadYoga as loadYoga$1 } from 'yoga-layout/load';
7
+ import emojiRegex from 'emoji-regex-xs';
8
+ import resolveImage from '@react-pdf/image';
9
+
10
+ /**
11
+ * Apply transformation to text string
12
+ *
13
+ * @param {string} text
14
+ * @param {string} transformation type
15
+ * @returns {string} transformed text
16
+ */
17
+ const transformText = (text, transformation) => {
18
+ switch (transformation) {
19
+ case 'uppercase':
20
+ return text.toUpperCase();
21
+ case 'lowercase':
22
+ return text.toLowerCase();
23
+ case 'capitalize':
24
+ return capitalize(text);
25
+ case 'upperfirst':
26
+ return upperFirst(text);
27
+ default:
28
+ return text;
29
+ }
30
+ };
31
+
32
+ const isTspan = (node) => node.type === P.Tspan;
33
+ const isTextInstance$4 = (node) => node.type === P.TextInstance;
34
+ const engines$1 = {
35
+ bidi,
36
+ linebreaker,
37
+ justification,
38
+ textDecoration,
39
+ scriptItemizer,
40
+ wordHyphenation,
41
+ fontSubstitution,
42
+ };
43
+ const engine$1 = layoutEngine(engines$1);
44
+ const getFragments$1 = (fontStore, instance) => {
45
+ if (!instance)
46
+ return [{ string: '' }];
47
+ const fragments = [];
48
+ const { fill = 'black', fontFamily = 'Helvetica', fontWeight, fontStyle, fontSize = 18, textDecorationColor, textDecorationStyle, textTransform, opacity, } = instance.props;
49
+ const _textDecoration = instance.props.textDecoration;
50
+ const fontFamilies = typeof fontFamily === 'string' ? [fontFamily] : [...(fontFamily || [])];
51
+ // Fallback font
52
+ fontFamilies.push('Helvetica');
53
+ const font = fontFamilies.map((fontFamilyName) => {
54
+ const opts = { fontFamily: fontFamilyName, fontWeight, fontStyle };
55
+ const obj = fontStore.getFont(opts);
56
+ return obj?.data;
57
+ });
58
+ const attributes = {
59
+ font,
60
+ opacity,
61
+ fontSize,
62
+ color: fill,
63
+ underlineStyle: textDecorationStyle,
64
+ underline: _textDecoration === 'underline' ||
65
+ _textDecoration === 'underline line-through' ||
66
+ _textDecoration === 'line-through underline',
67
+ underlineColor: textDecorationColor || fill,
68
+ strike: _textDecoration === 'line-through' ||
69
+ _textDecoration === 'underline line-through' ||
70
+ _textDecoration === 'line-through underline',
71
+ strikeStyle: textDecorationStyle,
72
+ strikeColor: textDecorationColor || fill,
73
+ };
74
+ for (let i = 0; i < instance.children.length; i += 1) {
75
+ const child = instance.children[i];
76
+ if (isTextInstance$4(child)) {
77
+ fragments.push({
78
+ string: transformText(child.value, textTransform),
79
+ attributes,
80
+ });
81
+ }
82
+ else if (child) {
83
+ fragments.push(...getFragments$1(fontStore, child));
84
+ }
85
+ }
86
+ return fragments;
87
+ };
88
+ const getAttributedString$1 = (fontStore, instance) => fromFragments(getFragments$1(fontStore, instance));
89
+ const AlmostInfinity = 999999999999;
90
+ const shrinkWhitespaceFactor = { before: -0.5, after: -0.5 };
91
+ const layoutTspan = (fontStore) => (node, xOffset) => {
92
+ const attributedString = getAttributedString$1(fontStore, node);
93
+ const x = node.props.x === undefined ? xOffset : node.props.x;
94
+ const y = node.props?.y || 0;
95
+ const container = { x, y, width: AlmostInfinity, height: AlmostInfinity };
96
+ const hyphenationCallback = node.props.hyphenationCallback ||
97
+ fontStore?.getHyphenationCallback() ||
98
+ null;
99
+ const layoutOptions = { hyphenationCallback, shrinkWhitespaceFactor };
100
+ const lines = engine$1(attributedString, container, layoutOptions).flat();
101
+ return Object.assign({}, node, { lines });
102
+ };
103
+ // Consecutive TSpan elements should be joined with a space
104
+ const joinTSpanLines = (node) => {
105
+ const children = node.children.map((child, index) => {
106
+ if (!isTspan(child))
107
+ return child;
108
+ const textInstance = child.children[0];
109
+ if (child.props.x === undefined &&
110
+ index < node.children.length - 1 &&
111
+ textInstance?.value) {
112
+ return Object.assign({}, child, {
113
+ children: [{ ...textInstance, value: `${textInstance.value} ` }],
114
+ });
115
+ }
116
+ return child;
117
+ }, []);
118
+ return Object.assign({}, node, { children });
119
+ };
120
+ const layoutText$1 = (fontStore, node) => {
121
+ if (!node.children)
122
+ return node;
123
+ let currentXOffset = node.props?.x || 0;
124
+ const layoutFn = layoutTspan(fontStore);
125
+ const joinedNode = joinTSpanLines(node);
126
+ const children = joinedNode.children.map((child) => {
127
+ const childWithLayout = layoutFn(child, currentXOffset);
128
+ currentXOffset += childWithLayout.lines[0].xAdvance;
129
+ return childWithLayout;
130
+ });
131
+ return Object.assign({}, node, { children });
132
+ };
133
+
134
+ const isDefs = (node) => node.type === P.Defs;
135
+ const getDefs = (node) => {
136
+ const children = node.children || [];
137
+ const defs = children.find(isDefs);
138
+ const values = defs?.children || [];
139
+ return values.reduce((acc, value) => {
140
+ const id = value.props?.id;
141
+ if (id)
142
+ acc[id] = value;
143
+ return acc;
144
+ }, {});
145
+ };
146
+
147
+ const isNotDefs = (node) => node.type !== P.Defs;
148
+ const detachDefs = (node) => {
149
+ if (!node.children)
150
+ return node;
151
+ const children = node.children.filter(isNotDefs);
152
+ return Object.assign({}, node, { children });
153
+ };
154
+ const URL_REGEX = /url\(['"]?#([^'"]+)['"]?\)/;
155
+ const replaceDef = (defs, value) => {
156
+ if (!value)
157
+ return undefined;
158
+ if (!URL_REGEX.test(value))
159
+ return value;
160
+ const match = value.match(URL_REGEX);
161
+ return defs[match[1]];
162
+ };
163
+ const parseNodeDefs = (defs) => (node) => {
164
+ const props = node.props;
165
+ const fill = `fill` in props ? replaceDef(defs, props?.fill) : undefined;
166
+ const clipPath = `clipPath` in props
167
+ ? replaceDef(defs, props?.clipPath)
168
+ : undefined;
169
+ const newProps = Object.assign({}, node.props, { fill, clipPath });
170
+ const children = node.children
171
+ ? node.children.map(parseNodeDefs(defs))
172
+ : undefined;
173
+ return Object.assign({}, node, { props: newProps, children });
174
+ };
175
+ const parseDefs = (root) => {
176
+ if (!root.children)
177
+ return root;
178
+ const defs = getDefs(root);
179
+ const children = root.children.map(parseNodeDefs(defs));
180
+ return Object.assign({}, root, { children });
181
+ };
182
+ const replaceDefs = (node) => {
183
+ return detachDefs(parseDefs(node));
184
+ };
185
+
186
+ const parseViewbox = (value) => {
187
+ if (!value)
188
+ return null;
189
+ if (typeof value !== 'string')
190
+ return value;
191
+ const values = value.split(/[,\s]+/).map(parseFloat$1);
192
+ if (values.length !== 4)
193
+ return null;
194
+ return { minX: values[0], minY: values[1], maxX: values[2], maxY: values[3] };
195
+ };
196
+
197
+ const getContainer$1 = (node) => {
198
+ const viewbox = parseViewbox(node.props.viewBox);
199
+ if (viewbox) {
200
+ return { width: viewbox.maxX, height: viewbox.maxY };
201
+ }
202
+ if (node.props.width && node.props.height) {
203
+ return {
204
+ width: parseFloat$1(node.props.width),
205
+ height: parseFloat$1(node.props.height),
206
+ };
207
+ }
208
+ return { width: 0, height: 0 };
209
+ };
210
+
211
+ const BASE_SVG_INHERITED_PROPS = [
212
+ 'x',
213
+ 'y',
214
+ 'clipPath',
215
+ 'clipRule',
216
+ 'opacity',
217
+ 'fill',
218
+ 'fillOpacity',
219
+ 'fillRule',
220
+ 'stroke',
221
+ 'strokeLinecap',
222
+ 'strokeLinejoin',
223
+ 'strokeOpacity',
224
+ 'strokeWidth',
225
+ 'textAnchor',
226
+ 'dominantBaseline',
227
+ 'color',
228
+ 'fontFamily',
229
+ 'fontSize',
230
+ 'fontStyle',
231
+ 'fontWeight',
232
+ 'letterSpacing',
233
+ 'opacity',
234
+ 'textDecoration',
235
+ 'lineHeight',
236
+ 'textAlign',
237
+ 'visibility',
238
+ 'wordSpacing',
239
+ ];
240
+ // Do not inherit "x" for <tspan> elements from <text> parent
241
+ const TEXT_SVG_INHERITED_PROPS = without(['x'], BASE_SVG_INHERITED_PROPS);
242
+ const SVG_INHERITED_PROPS = {
243
+ [P.Text]: TEXT_SVG_INHERITED_PROPS,
244
+ };
245
+ const getInheritProps = (node) => {
246
+ const props = node.props || {};
247
+ const svgInheritedProps = SVG_INHERITED_PROPS[node.type] ?? BASE_SVG_INHERITED_PROPS;
248
+ return pick(svgInheritedProps, props);
249
+ };
250
+ const inheritProps = (node) => {
251
+ if (!node.children)
252
+ return node;
253
+ const inheritedProps = getInheritProps(node);
254
+ const children = node.children.map((child) => {
255
+ const props = Object.assign({}, inheritedProps, child.props || {});
256
+ const newChild = Object.assign({}, child, { props });
257
+ return inheritProps(newChild);
258
+ });
259
+ return Object.assign({}, node, { children });
260
+ };
261
+
262
+ const parseAspectRatio = (value) => {
263
+ if (typeof value !== 'string')
264
+ return value;
265
+ const match = value
266
+ .replace(/[\s\r\t\n]+/gm, ' ')
267
+ .replace(/^defer\s/, '')
268
+ .split(' ');
269
+ const align = (match[0] || 'xMidYMid');
270
+ const meetOrSlice = (match[1] ||
271
+ 'meet');
272
+ return { align, meetOrSlice };
273
+ };
274
+
275
+ const STYLE_PROPS = [
276
+ 'width',
277
+ 'height',
278
+ 'color',
279
+ 'stroke',
280
+ 'strokeWidth',
281
+ 'opacity',
282
+ 'fillOpacity',
283
+ 'strokeOpacity',
284
+ 'fill',
285
+ 'fillRule',
286
+ 'clipPath',
287
+ 'offset',
288
+ 'transform',
289
+ 'strokeLinejoin',
290
+ 'strokeLinecap',
291
+ 'strokeDasharray',
292
+ 'gradientUnits',
293
+ 'gradientTransform',
294
+ ];
295
+ const VERTICAL_PROPS = ['y', 'y1', 'y2', 'height', 'cy', 'ry'];
296
+ const HORIZONTAL_PROPS = ['x', 'x1', 'x2', 'width', 'cx', 'rx'];
297
+ const isSvg$3 = (node) => node.type === P.Svg;
298
+ const isText$5 = (node) => node.type === P.Text;
299
+ const isTextInstance$3 = (node) => node.type === P.TextInstance;
300
+ const transformPercent = (container) => (props) => mapValues(props, (value, key) => {
301
+ const match = matchPercent(value);
302
+ if (match && VERTICAL_PROPS.includes(key)) {
303
+ return match.percent * container.height;
304
+ }
305
+ if (match && HORIZONTAL_PROPS.includes(key)) {
306
+ return match.percent * container.width;
307
+ }
308
+ return value;
309
+ });
310
+ const parsePercent = (value) => {
311
+ const match = matchPercent(value);
312
+ return match ? match.percent : parseFloat$1(value);
313
+ };
314
+ const parseTransform = (container) => (value) => {
315
+ return resolveStyle(container, { transform: value }).transform;
316
+ };
317
+ const parseProps = (container) => (node) => {
318
+ let props = transformPercent(container)(node.props);
319
+ props = evolve({
320
+ x: parseFloat$1,
321
+ x1: parseFloat$1,
322
+ x2: parseFloat$1,
323
+ y: parseFloat$1,
324
+ y1: parseFloat$1,
325
+ y2: parseFloat$1,
326
+ r: parseFloat$1,
327
+ rx: parseFloat$1,
328
+ ry: parseFloat$1,
329
+ cx: parseFloat$1,
330
+ cy: parseFloat$1,
331
+ width: parseFloat$1,
332
+ height: parseFloat$1,
333
+ offset: parsePercent,
334
+ fill: transformColor,
335
+ opacity: parsePercent,
336
+ stroke: transformColor,
337
+ stopOpacity: parsePercent,
338
+ stopColor: transformColor,
339
+ transform: parseTransform(container),
340
+ gradientTransform: parseTransform(container),
341
+ }, props);
342
+ return Object.assign({}, node, { props });
343
+ };
344
+ const mergeStyles$1 = (node) => {
345
+ const style = node.style || {};
346
+ const props = Object.assign({}, style, node.props);
347
+ return Object.assign({}, node, { props });
348
+ };
349
+ const removeNoneValues = (node) => {
350
+ const removeNone = (value) => (value === 'none' ? null : value);
351
+ const props = mapValues(node.props, removeNone);
352
+ return Object.assign({}, node, { props });
353
+ };
354
+ const pickStyleProps = (node) => {
355
+ const props = node.props || {};
356
+ const styleProps = pick(STYLE_PROPS, props);
357
+ const style = Object.assign({}, styleProps, node.style || {});
358
+ return Object.assign({}, node, { style });
359
+ };
360
+ const parseSvgProps = (node) => {
361
+ const props = evolve({
362
+ width: parseFloat$1,
363
+ height: parseFloat$1,
364
+ viewBox: parseViewbox,
365
+ preserveAspectRatio: parseAspectRatio,
366
+ }, node.props);
367
+ return Object.assign({}, node, { props });
368
+ };
369
+ const wrapBetweenTspan = (node) => ({
370
+ type: P.Tspan,
371
+ props: {},
372
+ style: {},
373
+ children: [node],
374
+ });
375
+ const addMissingTspan = (node) => {
376
+ if (!isText$5(node))
377
+ return node;
378
+ if (!node.children)
379
+ return node;
380
+ const resolveChild = (child) => isTextInstance$3(child) ? wrapBetweenTspan(child) : child;
381
+ const children = node.children.map(resolveChild);
382
+ return Object.assign({}, node, { children });
383
+ };
384
+ const parseText = (fontStore) => (node) => {
385
+ if (isText$5(node))
386
+ return layoutText$1(fontStore, node);
387
+ if (!node.children)
388
+ return node;
389
+ const children = node.children.map(parseText(fontStore));
390
+ return Object.assign({}, node, { children });
391
+ };
392
+ const resolveSvgNode = (container) => compose(parseProps(container), addMissingTspan, removeNoneValues, mergeStyles$1);
393
+ const resolveChildren = (container) => (node) => {
394
+ if (!node.children)
395
+ return node;
396
+ const resolveChild = compose(resolveChildren(container), resolveSvgNode(container));
397
+ const children = node.children.map(resolveChild);
398
+ return Object.assign({}, node, { children });
399
+ };
400
+ const buildXLinksIndex = (node) => {
401
+ const idIndex = {};
402
+ const listToExplore = node.children?.slice(0) || [];
403
+ while (listToExplore.length > 0) {
404
+ const child = listToExplore.shift();
405
+ if (child.props && 'id' in child.props) {
406
+ idIndex[child.props.id] = child;
407
+ }
408
+ if (child.children)
409
+ listToExplore.push(...child.children);
410
+ }
411
+ return idIndex;
412
+ };
413
+ const replaceXLinks = (node, idIndex) => {
414
+ if (node.props && 'xlinkHref' in node.props) {
415
+ const linkedNode = idIndex[node.props.xlinkHref.replace(/^#/, '')];
416
+ // No node to extend from
417
+ if (!linkedNode)
418
+ return node;
419
+ const newProps = Object.assign({}, linkedNode.props, node.props);
420
+ delete newProps.xlinkHref;
421
+ return Object.assign({}, linkedNode, { props: newProps });
422
+ }
423
+ const children = node.children?.map((child) => replaceXLinks(child, idIndex));
424
+ return Object.assign({}, node, { children });
425
+ };
426
+ const resolveXLinks = (node) => {
427
+ const idIndex = buildXLinksIndex(node);
428
+ return replaceXLinks(node, idIndex);
429
+ };
430
+ const resolveSvgRoot = (node, fontStore) => {
431
+ const container = getContainer$1(node);
432
+ return compose(replaceDefs, parseText(fontStore), parseSvgProps, pickStyleProps, inheritProps, resolveChildren(container), resolveXLinks)(node);
433
+ };
434
+ /**
435
+ * Pre-process SVG nodes so they can be rendered in the next steps
436
+ *
437
+ * @param node - Root node
438
+ * @param fontStore - Font store
439
+ * @returns Root node
440
+ */
441
+ const resolveSvg = (node, fontStore) => {
442
+ if (!('children' in node))
443
+ return node;
444
+ const resolveChild = (child) => resolveSvg(child, fontStore);
445
+ const root = isSvg$3(node) ? resolveSvgRoot(node, fontStore) : node;
446
+ const children = root.children?.map(resolveChild);
447
+ return Object.assign({}, root, { children });
448
+ };
449
+
450
+ let instancePromise;
451
+ const loadYoga = async () => {
452
+ // Yoga WASM binaries must be asynchronously compiled and loaded
453
+ // to prevent Event emitter memory leak warnings, Yoga must be loaded only once
454
+ const instance = await (instancePromise ??= loadYoga$1());
455
+ const config = instance.Config.create();
456
+ config.setPointScaleFactor(0);
457
+ const node = { create: () => instance.Node.createWithConfig(config) };
458
+ return { node };
459
+ };
460
+
461
+ const resolveYoga = async (root) => {
462
+ const yoga = await loadYoga();
463
+ return Object.assign({}, root, { yoga });
464
+ };
465
+
466
+ const getZIndex = (node) => node.style.zIndex;
467
+ const shouldSort = (node) => node.type !== P.Document && node.type !== P.Svg;
468
+ const sortZIndex = (a, b) => {
469
+ const za = getZIndex(a);
470
+ const zb = getZIndex(b);
471
+ if (!za && !zb)
472
+ return 0;
473
+ if (!za)
474
+ return 1;
475
+ if (!zb)
476
+ return -1;
477
+ return zb - za;
478
+ };
479
+ /**
480
+ * Sort children by zIndex value
481
+ *
482
+ * @param node
483
+ * @returns Node
484
+ */
485
+ const resolveNodeZIndex = (node) => {
486
+ if (!node.children)
487
+ return node;
488
+ const sortedChildren = shouldSort(node)
489
+ ? node.children.sort(sortZIndex)
490
+ : node.children;
491
+ const children = sortedChildren.map(resolveNodeZIndex);
492
+ return Object.assign({}, node, { children });
493
+ };
494
+ /**
495
+ * Sort children by zIndex value
496
+ *
497
+ * @param node
498
+ * @returns Node
499
+ */
500
+ const resolveZIndex = (root) => resolveNodeZIndex(root);
501
+
502
+ /* eslint-disable no-console */
503
+ // Caches emoji images data
504
+ const emojis = {};
505
+ const regex = emojiRegex();
506
+ /**
507
+ * When an emoji as no variations, it might still have 2 parts,
508
+ * the canonical emoji and an empty string.
509
+ * ex.
510
+ * (no color) Array.from('❤️') => ["❤", "️"]
511
+ * (w/ color) Array.from('👍🏿') => ["👍", "🏿"]
512
+ *
513
+ * The empty string needs to be removed otherwise the generated
514
+ * url will be incorect.
515
+ */
516
+ const removeVariationSelectors = (x) => x !== '️';
517
+ const getCodePoints = (string, withVariationSelectors = false) => Array.from(string)
518
+ .filter(withVariationSelectors ? () => true : removeVariationSelectors)
519
+ .map((char) => char.codePointAt(0).toString(16))
520
+ .join('-');
521
+ const buildEmojiUrl = (emoji, source) => {
522
+ if ('builder' in source) {
523
+ return source.builder(getCodePoints(emoji, source.withVariationSelectors));
524
+ }
525
+ const { url, format = 'png', withVariationSelectors } = source;
526
+ return `${url}${getCodePoints(emoji, withVariationSelectors)}.${format}`;
527
+ };
528
+ const fetchEmojis = (string, source) => {
529
+ if (!source)
530
+ return [];
531
+ const promises = [];
532
+ Array.from(string.matchAll(regex)).forEach((match) => {
533
+ const emoji = match[0];
534
+ if (!emojis[emoji] || emojis[emoji].loading) {
535
+ const emojiUrl = buildEmojiUrl(emoji, source);
536
+ emojis[emoji] = { loading: true };
537
+ promises.push(resolveImage({ uri: emojiUrl })
538
+ .then((image) => {
539
+ emojis[emoji].loading = false;
540
+ emojis[emoji].data = image.data;
541
+ })
542
+ .catch((e) => {
543
+ console.warn(e, 'Failed to load emoji image');
544
+ emojis[emoji].loading = false;
545
+ }));
546
+ }
547
+ });
548
+ return promises;
549
+ };
550
+ const embedEmojis = (fragments) => {
551
+ const result = [];
552
+ for (let i = 0; i < fragments.length; i += 1) {
553
+ const fragment = fragments[i];
554
+ let lastIndex = 0;
555
+ Array.from(fragment.string.matchAll(regex)).forEach((match) => {
556
+ const { index } = match;
557
+ const emoji = match[0];
558
+ const emojiSize = fragment.attributes.fontSize;
559
+ const chunk = fragment.string.slice(lastIndex, index + match[0].length);
560
+ // If emoji image was found, we create a new fragment with the
561
+ // correct attachment and object substitution character;
562
+ if (emojis[emoji] && emojis[emoji].data) {
563
+ result.push({
564
+ string: chunk.replace(match[0], String.fromCharCode(0xfffc)),
565
+ attributes: {
566
+ ...fragment.attributes,
567
+ attachment: {
568
+ width: emojiSize,
569
+ height: emojiSize,
570
+ yOffset: Math.floor(emojiSize * 0.1),
571
+ image: emojis[emoji].data,
572
+ },
573
+ },
574
+ });
575
+ }
576
+ else {
577
+ // If no emoji data, we try to use emojis in the font
578
+ result.push({
579
+ string: chunk,
580
+ attributes: fragment.attributes,
581
+ });
582
+ }
583
+ lastIndex = index + emoji.length;
584
+ });
585
+ if (lastIndex < fragment.string.length) {
586
+ result.push({
587
+ string: fragment.string.slice(lastIndex),
588
+ attributes: fragment.attributes,
589
+ });
590
+ }
591
+ }
592
+ return result;
593
+ };
594
+
595
+ /**
596
+ * Get image source
597
+ *
598
+ * @param node - Image node
599
+ * @returns Image src
600
+ */
601
+ const getSource = (node) => {
602
+ if (node.props.src)
603
+ return node.props.src;
604
+ if (node.props.source)
605
+ return node.props.source;
606
+ };
607
+
608
+ /**
609
+ * Resolves `src` to `@react-pdf/image` interface.
610
+ *
611
+ * Also it handles factories and async sources.
612
+ *
613
+ * @param src
614
+ * @returns Resolved src
615
+ */
616
+ const resolveSource = async (src) => {
617
+ const source = typeof src === 'function' ? await src() : await src;
618
+ return typeof source === 'string' ? { uri: source } : source;
619
+ };
620
+
621
+ /**
622
+ * Fetches image and append data to node
623
+ * Ideally this fn should be immutable.
624
+ *
625
+ * @param node
626
+ */
627
+ const fetchImage = async (node) => {
628
+ const src = getSource(node);
629
+ const { cache } = node.props;
630
+ if (!src) {
631
+ console.warn(false, 'Image should receive either a "src" or "source" prop');
632
+ return;
633
+ }
634
+ try {
635
+ const source = await resolveSource(src);
636
+ if (!source) {
637
+ throw new Error(`Image's "src" or "source" prop returned ${source}`);
638
+ }
639
+ node.image = await resolveImage(source, { cache });
640
+ if (Buffer.isBuffer(source) || source instanceof Blob)
641
+ return;
642
+ node.image.key = 'data' in source ? source.data.toString() : source.uri;
643
+ }
644
+ catch (e) {
645
+ console.warn(e.message);
646
+ }
647
+ };
648
+
649
+ const isImage$2 = (node) => node.type === P.Image;
650
+ /**
651
+ * Get all asset promises that need to be resolved
652
+ *
653
+ * @param fontStore - Font store
654
+ * @param node - Root node
655
+ * @returns Asset promises
656
+ */
657
+ const fetchAssets = (fontStore, node) => {
658
+ const promises = [];
659
+ const listToExplore = node.children?.slice(0) || [];
660
+ const emojiSource = fontStore ? fontStore.getEmojiSource() : null;
661
+ while (listToExplore.length > 0) {
662
+ const n = listToExplore.shift();
663
+ if (isImage$2(n)) {
664
+ promises.push(fetchImage(n));
665
+ }
666
+ if (fontStore && n.style?.fontFamily) {
667
+ const fontFamilies = castArray(n.style.fontFamily);
668
+ promises.push(...fontFamilies.map((fontFamily) => fontStore.load({
669
+ fontFamily,
670
+ fontStyle: n.style.fontStyle,
671
+ fontWeight: n.style.fontWeight,
672
+ })));
673
+ }
674
+ if (typeof n === 'string') {
675
+ promises.push(...fetchEmojis(n, emojiSource));
676
+ }
677
+ if ('value' in n && typeof n.value === 'string') {
678
+ promises.push(...fetchEmojis(n.value, emojiSource));
679
+ }
680
+ if (n.children) {
681
+ n.children.forEach((childNode) => {
682
+ listToExplore.push(childNode);
683
+ });
684
+ }
685
+ }
686
+ return promises;
687
+ };
688
+ /**
689
+ * Fetch image, font and emoji assets in parallel.
690
+ * Layout process will not be resumed until promise resolves.
691
+ *
692
+ * @param node root node
693
+ * @param fontStore font store
694
+ * @returns Root node
695
+ */
696
+ const resolveAssets = async (node, fontStore) => {
697
+ const promises = fetchAssets(fontStore, node);
698
+ await Promise.all(promises);
699
+ return node;
700
+ };
701
+
702
+ const isLink$1 = (node) => node.type === P.Link;
703
+ const DEFAULT_LINK_STYLES = {
704
+ color: 'blue',
705
+ textDecoration: 'underline',
706
+ };
707
+ /**
708
+ * Computes styles using stylesheet
709
+ *
710
+ * @param container
711
+ * @param node - Document node
712
+ * @returns Computed styles
713
+ */
714
+ const computeStyle = (container, node) => {
715
+ let baseStyle = [node.style];
716
+ if (isLink$1(node)) {
717
+ baseStyle = Array.isArray(node.style)
718
+ ? [DEFAULT_LINK_STYLES, ...node.style]
719
+ : [DEFAULT_LINK_STYLES, node.style];
720
+ }
721
+ return resolveStyle(container, baseStyle);
722
+ };
723
+ /**
724
+ * Resolves node styles
725
+ *
726
+ * @param container
727
+ * @returns Resolve node styles
728
+ */
729
+ const resolveNodeStyles = (container) => (node) => {
730
+ const style = computeStyle(container, node);
731
+ if (!node.children)
732
+ return Object.assign({}, node, { style });
733
+ const children = node.children.map(resolveNodeStyles(container));
734
+ return Object.assign({}, node, { style, children });
735
+ };
736
+ /**
737
+ * Resolves page styles
738
+ *
739
+ * @param page Document page
740
+ * @returns Document page with resolved styles
741
+ */
742
+ const resolvePageStyles = (page) => {
743
+ const dpi = page.props?.dpi || 72;
744
+ const style = page.style;
745
+ const width = page.box?.width || style.width;
746
+ const height = page.box?.height || style.height;
747
+ const orientation = page.props?.orientation || 'portrait';
748
+ const remBase = style?.fontSize || 18;
749
+ const container = { width, height, orientation, dpi, remBase };
750
+ return resolveNodeStyles(container)(page);
751
+ };
752
+ /**
753
+ * Resolves document styles
754
+ *
755
+ * @param root - Document root
756
+ * @returns Document root with resolved styles
757
+ */
758
+ const resolveStyles = (root) => {
759
+ if (!root.children)
760
+ return root;
761
+ const children = root.children.map(resolvePageStyles);
762
+ return Object.assign({}, root, { children });
763
+ };
764
+
765
+ const getTransformStyle = (s) => (node) => isNil(node.style?.[s]) ? '50%' : node.style?.[s] ?? null;
766
+ /**
767
+ * Get node origin
768
+ *
769
+ * @param node
770
+ * @returns {{ left?: number, top?: number }} node origin
771
+ */
772
+ const getOrigin = (node) => {
773
+ if (!node.box)
774
+ return null;
775
+ const { left, top, width, height } = node.box;
776
+ const transformOriginX = getTransformStyle('transformOriginX')(node);
777
+ const transformOriginY = getTransformStyle('transformOriginY')(node);
778
+ const percentX = matchPercent(transformOriginX);
779
+ const percentY = matchPercent(transformOriginY);
780
+ const offsetX = percentX ? width * percentX.percent : transformOriginX;
781
+ const offsetY = percentY ? height * percentY.percent : transformOriginY;
782
+ if (isNil(offsetX) || typeof offsetX === 'string')
783
+ throw new Error(`Invalid origin offsetX: ${offsetX}`);
784
+ if (isNil(offsetY) || typeof offsetY === 'string')
785
+ throw new Error(`Invalid origin offsetY: ${offsetY}`);
786
+ return { left: left + offsetX, top: top + offsetY };
787
+ };
788
+
789
+ /**
790
+ * Resolve node origin
791
+ *
792
+ * @param node
793
+ * @returns Node with origin attribute
794
+ */
795
+ const resolveNodeOrigin = (node) => {
796
+ const origin = getOrigin(node);
797
+ const newNode = Object.assign({}, node, { origin });
798
+ if (!node.children)
799
+ return newNode;
800
+ const children = node.children.map(resolveNodeOrigin);
801
+ return Object.assign({}, newNode, { children });
802
+ };
803
+ /**
804
+ * Resolve document origins
805
+ *
806
+ * @param root - Document root
807
+ * @returns Document root
808
+ */
809
+ const resolveOrigin = (root) => {
810
+ if (!root.children)
811
+ return root;
812
+ const children = root.children.map(resolveNodeOrigin);
813
+ return Object.assign({}, root, { children });
814
+ };
815
+
816
+ const getBookmarkValue = (bookmark) => {
817
+ return typeof bookmark === 'string'
818
+ ? { title: bookmark, fit: false, expanded: false }
819
+ : bookmark;
820
+ };
821
+ const resolveBookmarks = (node) => {
822
+ let refs = 0;
823
+ const children = (node.children || []).slice(0);
824
+ const listToExplore = children.map((value) => ({
825
+ value,
826
+ parent: null,
827
+ }));
828
+ while (listToExplore.length > 0) {
829
+ const element = listToExplore.shift();
830
+ if (!element)
831
+ break;
832
+ const child = element.value;
833
+ let parent = element.parent;
834
+ if (child.props && 'bookmark' in child.props && child.props.bookmark) {
835
+ const bookmark = getBookmarkValue(child.props.bookmark);
836
+ const ref = refs++;
837
+ const newHierarchy = { ref, parent: parent?.ref, ...bookmark };
838
+ child.props.bookmark = newHierarchy;
839
+ parent = newHierarchy;
840
+ }
841
+ if (child.children) {
842
+ child.children.forEach((childNode) => {
843
+ listToExplore.push({ value: childNode, parent });
844
+ });
845
+ }
846
+ }
847
+ return node;
848
+ };
849
+
850
+ const VALID_ORIENTATIONS = ['portrait', 'landscape'];
851
+ /**
852
+ * Get page orientation. Defaults to portrait
853
+ *
854
+ * @param page - Page object
855
+ * @returns Page orientation
856
+ */
857
+ const getOrientation = (page) => {
858
+ const value = page.props?.orientation || 'portrait';
859
+ return VALID_ORIENTATIONS.includes(value) ? value : 'portrait';
860
+ };
861
+
862
+ /**
863
+ * Return true if page is landscape
864
+ *
865
+ * @param page - Page instance
866
+ * @returns Is page landscape
867
+ */
868
+ const isLandscape = (page) => getOrientation(page) === 'landscape';
869
+
870
+ // Page sizes for 72dpi. 72dpi is used internally by pdfkit.
871
+ const PAGE_SIZES = {
872
+ '4A0': [4767.87, 6740.79],
873
+ '2A0': [3370.39, 4767.87],
874
+ A0: [2383.94, 3370.39],
875
+ A1: [1683.78, 2383.94],
876
+ A2: [1190.55, 1683.78],
877
+ A3: [841.89, 1190.55],
878
+ A4: [595.28, 841.89],
879
+ A5: [419.53, 595.28],
880
+ A6: [297.64, 419.53],
881
+ A7: [209.76, 297.64],
882
+ A8: [147.4, 209.76],
883
+ A9: [104.88, 147.4],
884
+ A10: [73.7, 104.88],
885
+ B0: [2834.65, 4008.19],
886
+ B1: [2004.09, 2834.65],
887
+ B2: [1417.32, 2004.09],
888
+ B3: [1000.63, 1417.32],
889
+ B4: [708.66, 1000.63],
890
+ B5: [498.9, 708.66],
891
+ B6: [354.33, 498.9],
892
+ B7: [249.45, 354.33],
893
+ B8: [175.75, 249.45],
894
+ B9: [124.72, 175.75],
895
+ B10: [87.87, 124.72],
896
+ C0: [2599.37, 3676.54],
897
+ C1: [1836.85, 2599.37],
898
+ C2: [1298.27, 1836.85],
899
+ C3: [918.43, 1298.27],
900
+ C4: [649.13, 918.43],
901
+ C5: [459.21, 649.13],
902
+ C6: [323.15, 459.21],
903
+ C7: [229.61, 323.15],
904
+ C8: [161.57, 229.61],
905
+ C9: [113.39, 161.57],
906
+ C10: [79.37, 113.39],
907
+ RA0: [2437.8, 3458.27],
908
+ RA1: [1729.13, 2437.8],
909
+ RA2: [1218.9, 1729.13],
910
+ RA3: [864.57, 1218.9],
911
+ RA4: [609.45, 864.57],
912
+ SRA0: [2551.18, 3628.35],
913
+ SRA1: [1814.17, 2551.18],
914
+ SRA2: [1275.59, 1814.17],
915
+ SRA3: [907.09, 1275.59],
916
+ SRA4: [637.8, 907.09],
917
+ EXECUTIVE: [521.86, 756.0],
918
+ FOLIO: [612.0, 936.0],
919
+ LEGAL: [612.0, 1008.0],
920
+ LETTER: [612.0, 792.0],
921
+ TABLOID: [792.0, 1224.0],
922
+ ID1: [153, 243],
923
+ };
924
+ /**
925
+ * Parses scalar value in value and unit pairs
926
+ *
927
+ * @param value - Scalar value
928
+ * @returns Parsed value
929
+ */
930
+ const parseValue = (value) => {
931
+ if (typeof value === 'number')
932
+ return { value, unit: undefined };
933
+ const match = /^(-?\d*\.?\d+)(in|mm|cm|pt|px)?$/g.exec(value);
934
+ return match
935
+ ? { value: parseFloat(match[1]), unit: match[2] || 'pt' }
936
+ : { value, unit: undefined };
937
+ };
938
+ /**
939
+ * Transform given scalar value to 72dpi equivalent of size
940
+ *
941
+ * @param value - Styles value
942
+ * @param inputDpi - User defined dpi
943
+ * @returns Transformed value
944
+ */
945
+ const transformUnit = (value, inputDpi) => {
946
+ if (!value)
947
+ return 0;
948
+ const scalar = parseValue(value);
949
+ const outputDpi = 72;
950
+ const mmFactor = (1 / 25.4) * outputDpi;
951
+ const cmFactor = (1 / 2.54) * outputDpi;
952
+ if (typeof scalar.value === 'string')
953
+ throw new Error(`Invalid page size: ${value}`);
954
+ switch (scalar.unit) {
955
+ case 'in':
956
+ return scalar.value * outputDpi;
957
+ case 'mm':
958
+ return scalar.value * mmFactor;
959
+ case 'cm':
960
+ return scalar.value * cmFactor;
961
+ case 'px':
962
+ return Math.round(scalar.value * (outputDpi / inputDpi));
963
+ default:
964
+ return scalar.value;
965
+ }
966
+ };
967
+ const transformUnits = ({ width, height }, dpi) => ({
968
+ width: transformUnit(width, dpi),
969
+ height: transformUnit(height, dpi),
970
+ });
971
+ /**
972
+ * Transforms array into size object
973
+ *
974
+ * @param v - Values array
975
+ * @returns Size object with width and height
976
+ */
977
+ const toSizeObject = (v) => ({
978
+ width: v[0],
979
+ height: v[1],
980
+ });
981
+ /**
982
+ * Flip size object
983
+ *
984
+ * @param v - Size object
985
+ * @returns Flipped size object
986
+ */
987
+ const flipSizeObject = (v) => ({
988
+ width: v.height,
989
+ height: v.width,
990
+ });
991
+ /**
992
+ * Returns size object from a given string
993
+ *
994
+ * @param v - Page size string
995
+ * @returns Size object with width and height
996
+ */
997
+ const getStringSize = (v) => {
998
+ return toSizeObject(PAGE_SIZES[v.toUpperCase()]);
999
+ };
1000
+ /**
1001
+ * Returns size object from a single number
1002
+ *
1003
+ * @param n - Page size number
1004
+ * @returns Size object with width and height
1005
+ */
1006
+ const getNumberSize = (n) => toSizeObject([n, n]);
1007
+ /**
1008
+ * Return page size in an object { width, height }
1009
+ *
1010
+ * @param page - Page node
1011
+ * @returns Size object with width and height
1012
+ */
1013
+ const getSize = (page) => {
1014
+ const value = page.props?.size || 'A4';
1015
+ const dpi = page.props?.dpi || 72;
1016
+ let size;
1017
+ if (typeof value === 'string') {
1018
+ size = getStringSize(value);
1019
+ }
1020
+ else if (Array.isArray(value)) {
1021
+ size = transformUnits(toSizeObject(value), dpi);
1022
+ }
1023
+ else if (typeof value === 'number') {
1024
+ size = transformUnits(getNumberSize(value), dpi);
1025
+ }
1026
+ else {
1027
+ size = transformUnits(value, dpi);
1028
+ }
1029
+ return isLandscape(page) ? flipSizeObject(size) : size;
1030
+ };
1031
+
1032
+ /**
1033
+ * Resolves page size
1034
+ *
1035
+ * @param page
1036
+ * @returns Page with resolved size in style attribute
1037
+ */
1038
+ const resolvePageSize = (page) => {
1039
+ const size = getSize(page);
1040
+ const style = flatten(page.style || {});
1041
+ return { ...page, style: { ...style, ...size } };
1042
+ };
1043
+ /**
1044
+ * Resolves page sizes
1045
+ *
1046
+ * @param root -Document root
1047
+ * @returns Document root with resolved page sizes
1048
+ */
1049
+ const resolvePageSizes = (root) => {
1050
+ if (!root.children)
1051
+ return root;
1052
+ const children = root.children.map(resolvePageSize);
1053
+ return Object.assign({}, root, { children });
1054
+ };
1055
+
1056
+ const isFixed = (node) => {
1057
+ if (!node.props)
1058
+ return false;
1059
+ return 'fixed' in node.props ? node.props.fixed === true : false;
1060
+ };
1061
+
1062
+ /**
1063
+ * Get line index at given height
1064
+ *
1065
+ * @param node
1066
+ * @param height
1067
+ */
1068
+ const lineIndexAtHeight = (node, height) => {
1069
+ let y = 0;
1070
+ if (!node.lines)
1071
+ return 0;
1072
+ for (let i = 0; i < node.lines.length; i += 1) {
1073
+ const line = node.lines[i];
1074
+ if (y + line.box.height > height)
1075
+ return i;
1076
+ y += line.box.height;
1077
+ }
1078
+ return node.lines.length;
1079
+ };
1080
+
1081
+ /**
1082
+ * Get height for given text line index
1083
+ *
1084
+ * @param node
1085
+ * @param index
1086
+ */
1087
+ const heightAtLineIndex = (node, index) => {
1088
+ let counter = 0;
1089
+ if (!node.lines)
1090
+ return counter;
1091
+ for (let i = 0; i < index; i += 1) {
1092
+ const line = node.lines[i];
1093
+ if (!line)
1094
+ break;
1095
+ counter += line.box.height;
1096
+ }
1097
+ return counter;
1098
+ };
1099
+
1100
+ const getLineBreak = (node, height) => {
1101
+ const top = node.box?.top || 0;
1102
+ const widows = node.props.widows || 2;
1103
+ const orphans = node.props.orphans || 2;
1104
+ const linesQuantity = node.lines.length;
1105
+ const slicedLine = lineIndexAtHeight(node, height - top);
1106
+ if (slicedLine === 0) {
1107
+ return 0;
1108
+ }
1109
+ if (linesQuantity < orphans) {
1110
+ return linesQuantity;
1111
+ }
1112
+ if (slicedLine < orphans || linesQuantity < orphans + widows) {
1113
+ return 0;
1114
+ }
1115
+ if (linesQuantity === orphans + widows) {
1116
+ return orphans;
1117
+ }
1118
+ if (linesQuantity - slicedLine < widows) {
1119
+ return linesQuantity - widows;
1120
+ }
1121
+ return slicedLine;
1122
+ };
1123
+ // Also receives contentArea in case it's needed
1124
+ const splitText = (node, height) => {
1125
+ const slicedLineIndex = getLineBreak(node, height);
1126
+ const currentHeight = heightAtLineIndex(node, slicedLineIndex);
1127
+ const nextHeight = node.box.height - currentHeight;
1128
+ const current = Object.assign({}, node, {
1129
+ box: {
1130
+ ...node.box,
1131
+ height: currentHeight,
1132
+ borderBottomWidth: 0,
1133
+ },
1134
+ style: {
1135
+ ...node.style,
1136
+ marginBottom: 0,
1137
+ paddingBottom: 0,
1138
+ borderBottomWidth: 0,
1139
+ borderBottomLeftRadius: 0,
1140
+ borderBottomRightRadius: 0,
1141
+ },
1142
+ lines: node.lines.slice(0, slicedLineIndex),
1143
+ });
1144
+ const next = Object.assign({}, node, {
1145
+ box: {
1146
+ ...node.box,
1147
+ top: 0,
1148
+ height: nextHeight,
1149
+ borderTopWidth: 0,
1150
+ },
1151
+ style: {
1152
+ ...node.style,
1153
+ marginTop: 0,
1154
+ paddingTop: 0,
1155
+ borderTopWidth: 0,
1156
+ borderTopLeftRadius: 0,
1157
+ borderTopRightRadius: 0,
1158
+ },
1159
+ lines: node.lines.slice(slicedLineIndex),
1160
+ });
1161
+ return [current, next];
1162
+ };
1163
+
1164
+ const getTop$1 = (node) => node.box?.top || 0;
1165
+ const hasFixedHeight = (node) => !isNil(node.style?.height);
1166
+ const splitNode = (node, height) => {
1167
+ if (!node)
1168
+ return [null, null];
1169
+ const nodeTop = getTop$1(node);
1170
+ const current = Object.assign({}, node, {
1171
+ box: {
1172
+ ...node.box,
1173
+ borderBottomWidth: 0,
1174
+ },
1175
+ style: {
1176
+ ...node.style,
1177
+ marginBottom: 0,
1178
+ paddingBottom: 0,
1179
+ borderBottomWidth: 0,
1180
+ borderBottomLeftRadius: 0,
1181
+ borderBottomRightRadius: 0,
1182
+ },
1183
+ });
1184
+ current.style.height = height - nodeTop;
1185
+ const nextHeight = hasFixedHeight(node)
1186
+ ? node.box.height - (height - nodeTop)
1187
+ : null;
1188
+ const next = Object.assign({}, node, {
1189
+ box: {
1190
+ ...node.box,
1191
+ top: 0,
1192
+ borderTopWidth: 0,
1193
+ },
1194
+ style: {
1195
+ ...node.style,
1196
+ marginTop: 0,
1197
+ paddingTop: 0,
1198
+ borderTopWidth: 0,
1199
+ borderTopLeftRadius: 0,
1200
+ borderTopRightRadius: 0,
1201
+ },
1202
+ });
1203
+ if (nextHeight) {
1204
+ next.style.height = nextHeight;
1205
+ }
1206
+ return [current, next];
1207
+ };
1208
+
1209
+ const NON_WRAP_TYPES = [P.Svg, P.Note, P.Image, P.Canvas];
1210
+ const canCauseBlankSpace = (node, prevNode, currentChildren) => {
1211
+ if (!('preventBlankSpace' in node.props))
1212
+ return false;
1213
+ const prevNodeHasHeightOne = prevNode?.box?.height === 1;
1214
+ const childrenIsEmpty = currentChildren?.length === 0;
1215
+ // padding/margin case
1216
+ if (prevNodeHasHeightOne === true && childrenIsEmpty === false) {
1217
+ return true;
1218
+ }
1219
+ // gap between content case
1220
+ if (node.box.height > 680 && childrenIsEmpty === true) {
1221
+ return true;
1222
+ }
1223
+ return false;
1224
+ };
1225
+ const getWrap = (node, prevNode, currentChildren) => {
1226
+ if (NON_WRAP_TYPES.includes(node.type))
1227
+ return false;
1228
+ if (!node.props)
1229
+ return true;
1230
+ if (canCauseBlankSpace(node, prevNode, currentChildren))
1231
+ return true;
1232
+ return 'wrap' in node.props ? node.props.wrap : true;
1233
+ };
1234
+
1235
+ const getComputedPadding = (node, edge) => {
1236
+ const { yogaNode } = node;
1237
+ return yogaNode ? yogaNode.getComputedPadding(edge) : null;
1238
+ };
1239
+ /**
1240
+ * Get Yoga computed paddings. Zero otherwise
1241
+ *
1242
+ * @param node
1243
+ * @returns paddings
1244
+ */
1245
+ const getPadding = (node) => {
1246
+ const { style, box } = node;
1247
+ const paddingTop = getComputedPadding(node, Yoga.Edge.Top) ||
1248
+ box?.paddingTop ||
1249
+ style?.paddingTop ||
1250
+ 0;
1251
+ const paddingRight = getComputedPadding(node, Yoga.Edge.Right) ||
1252
+ box?.paddingRight ||
1253
+ style?.paddingRight ||
1254
+ 0;
1255
+ const paddingBottom = getComputedPadding(node, Yoga.Edge.Bottom) ||
1256
+ box?.paddingBottom ||
1257
+ style?.paddingBottom ||
1258
+ 0;
1259
+ const paddingLeft = getComputedPadding(node, Yoga.Edge.Left) ||
1260
+ box?.paddingLeft ||
1261
+ style?.paddingLeft ||
1262
+ 0;
1263
+ return { paddingTop, paddingRight, paddingBottom, paddingLeft };
1264
+ };
1265
+
1266
+ const getWrapArea = (page) => {
1267
+ const height = page.style?.height;
1268
+ const { paddingBottom } = getPadding(page);
1269
+ return height - paddingBottom;
1270
+ };
1271
+
1272
+ const getContentArea = (page) => {
1273
+ const height = page.style?.height;
1274
+ const { paddingTop, paddingBottom } = getPadding(page);
1275
+ return height - paddingBottom - paddingTop;
1276
+ };
1277
+
1278
+ const isString = (value) => typeof value === 'string';
1279
+ const isNumber = (value) => typeof value === 'number';
1280
+ const isBoolean = (value) => typeof value === 'boolean';
1281
+ const isFragment = (value) => value && value.type === Symbol.for('react.fragment');
1282
+ /**
1283
+ * Transforms a react element instance to internal element format.
1284
+ *
1285
+ * Can return multiple instances in the case of arrays or fragments.
1286
+ *
1287
+ * @param element - React element
1288
+ * @returns Parsed React elements
1289
+ */
1290
+ const createInstances = (element) => {
1291
+ if (!element)
1292
+ return [];
1293
+ if (Array.isArray(element)) {
1294
+ return element.reduce((acc, el) => acc.concat(createInstances(el)), []);
1295
+ }
1296
+ if (isBoolean(element)) {
1297
+ return [];
1298
+ }
1299
+ if (isString(element) || isNumber(element)) {
1300
+ return [{ type: P.TextInstance, value: `${element}` }];
1301
+ }
1302
+ if (isFragment(element)) {
1303
+ // @ts-expect-error figure out why this is complains
1304
+ return createInstances(element.props.children);
1305
+ }
1306
+ if (!isString(element.type)) {
1307
+ // @ts-expect-error figure out why this is complains
1308
+ return createInstances(element.type(element.props));
1309
+ }
1310
+ const { type, props: { style = {}, children, ...props }, } = element;
1311
+ const nextChildren = castArray(children).reduce((acc, child) => acc.concat(createInstances(child)), []);
1312
+ return [
1313
+ {
1314
+ type,
1315
+ style,
1316
+ props,
1317
+ children: nextChildren,
1318
+ },
1319
+ ];
1320
+ };
1321
+
1322
+ const getBreak = (node) => 'break' in node.props ? node.props.break : false;
1323
+ const getMinPresenceAhead = (node) => 'minPresenceAhead' in node.props ? node.props.minPresenceAhead : 0;
1324
+ const getFurthestEnd = (elements) => Math.max(...elements.map((node) => node.box.top + node.box.height));
1325
+ const getEndOfMinPresenceAhead = (child) => {
1326
+ return (child.box.top +
1327
+ child.box.height +
1328
+ child.box.marginBottom +
1329
+ getMinPresenceAhead(child));
1330
+ };
1331
+ const getEndOfPresence = (child, futureElements) => {
1332
+ const afterMinPresenceAhead = getEndOfMinPresenceAhead(child);
1333
+ const endOfFurthestFutureElement = getFurthestEnd(futureElements.filter((node) => !('fixed' in node.props)));
1334
+ return Math.min(afterMinPresenceAhead, endOfFurthestFutureElement);
1335
+ };
1336
+ const shouldBreak = (child, futureElements, height, previousElements) => {
1337
+ if ('fixed' in child.props)
1338
+ return false;
1339
+ const shouldSplit = height < child.box.top + child.box.height;
1340
+ const canWrap = getWrap(child, child, []);
1341
+ // Calculate the y coordinate where the desired presence of the child ends
1342
+ const endOfPresence = getEndOfPresence(child, futureElements);
1343
+ // If the child is already at the top of the page, breaking won't improve its presence
1344
+ // (as long as react-pdf does not support breaking into differently sized containers)
1345
+ const breakingImprovesPresence = previousElements.filter((node) => !isFixed(node)).length > 0;
1346
+ return (getBreak(child) ||
1347
+ (shouldSplit && !canWrap) ||
1348
+ (!shouldSplit && endOfPresence > height && breakingImprovesPresence));
1349
+ };
1350
+
1351
+ const IGNORABLE_CODEPOINTS = [
1352
+ 8232, // LINE_SEPARATOR
1353
+ 8233, // PARAGRAPH_SEPARATOR
1354
+ ];
1355
+ const buildSubsetForFont = (font) => IGNORABLE_CODEPOINTS.reduce((acc, codePoint) => {
1356
+ if (font &&
1357
+ font.hasGlyphForCodePoint &&
1358
+ font.hasGlyphForCodePoint(codePoint)) {
1359
+ return acc;
1360
+ }
1361
+ return [...acc, String.fromCharCode(codePoint)];
1362
+ }, []);
1363
+ const ignoreChars = (fragments) => fragments.map((fragment) => {
1364
+ const charSubset = buildSubsetForFont(fragment.attributes.font[0]);
1365
+ const subsetRegex = new RegExp(charSubset.join('|'));
1366
+ return {
1367
+ string: fragment.string.replace(subsetRegex, ''),
1368
+ attributes: fragment.attributes,
1369
+ };
1370
+ });
1371
+
1372
+ const PREPROCESSORS = [ignoreChars, embedEmojis];
1373
+ const isImage$1 = (node) => node.type === P.Image;
1374
+ const isTextInstance$2 = (node) => node.type === P.TextInstance;
1375
+ /**
1376
+ * Get textkit fragments of given node object
1377
+ *
1378
+ * @param fontStore - Font store
1379
+ * @param instance - Node
1380
+ * @param parentLink - Parent link
1381
+ * @param level - Fragment level
1382
+ * @returns Text fragments
1383
+ */
1384
+ const getFragments = (fontStore, instance, parentLink = null, level = 0) => {
1385
+ if (!instance)
1386
+ return [{ string: '' }];
1387
+ let fragments = [];
1388
+ const { color = 'black', direction = 'ltr', fontFamily = 'Helvetica', fontWeight, fontStyle, fontSize = 18, textAlign, lineHeight, textDecoration, textDecorationColor, textDecorationStyle, textTransform, letterSpacing, textIndent, opacity, verticalAlign, } = instance.style;
1389
+ const fontFamilies = typeof fontFamily === 'string' ? [fontFamily] : [...(fontFamily || [])];
1390
+ // Fallback font
1391
+ fontFamilies.push('Helvetica');
1392
+ const font = fontFamilies.map((fontFamilyName) => {
1393
+ const opts = { fontFamily: fontFamilyName, fontWeight, fontStyle };
1394
+ const obj = fontStore.getFont(opts);
1395
+ return obj?.data;
1396
+ });
1397
+ // Don't pass main background color to textkit. Will be rendered by the render package instead
1398
+ const backgroundColor = level === 0 ? null : instance.style.backgroundColor;
1399
+ const attributes = {
1400
+ font,
1401
+ color,
1402
+ opacity,
1403
+ fontSize,
1404
+ lineHeight,
1405
+ direction,
1406
+ verticalAlign,
1407
+ backgroundColor,
1408
+ indent: textIndent,
1409
+ characterSpacing: letterSpacing,
1410
+ strikeStyle: textDecorationStyle,
1411
+ underlineStyle: textDecorationStyle,
1412
+ underline: textDecoration === 'underline' ||
1413
+ textDecoration === 'underline line-through' ||
1414
+ textDecoration === 'line-through underline',
1415
+ strike: textDecoration === 'line-through' ||
1416
+ textDecoration === 'underline line-through' ||
1417
+ textDecoration === 'line-through underline',
1418
+ strikeColor: textDecorationColor || color,
1419
+ underlineColor: textDecorationColor || color,
1420
+ // @ts-expect-error allow this props access
1421
+ link: parentLink || instance.props?.src || instance.props?.href,
1422
+ align: textAlign || (direction === 'rtl' ? 'right' : 'left'),
1423
+ };
1424
+ for (let i = 0; i < instance.children.length; i += 1) {
1425
+ const child = instance.children[i];
1426
+ if (isImage$1(child)) {
1427
+ fragments.push({
1428
+ string: String.fromCharCode(0xfffc),
1429
+ attributes: {
1430
+ ...attributes,
1431
+ attachment: {
1432
+ width: (child.style.width || fontSize),
1433
+ height: (child.style.height || fontSize),
1434
+ image: child.image.data,
1435
+ },
1436
+ },
1437
+ });
1438
+ }
1439
+ else if (isTextInstance$2(child)) {
1440
+ fragments.push({
1441
+ string: transformText(child.value, textTransform),
1442
+ attributes,
1443
+ });
1444
+ }
1445
+ else if (child) {
1446
+ fragments.push(...getFragments(fontStore, child, attributes.link, level + 1));
1447
+ }
1448
+ }
1449
+ for (let i = 0; i < PREPROCESSORS.length; i += 1) {
1450
+ const preprocessor = PREPROCESSORS[i];
1451
+ fragments = preprocessor(fragments);
1452
+ }
1453
+ return fragments;
1454
+ };
1455
+ /**
1456
+ * Get textkit attributed string from text node
1457
+ *
1458
+ * @param fontStore - Font store
1459
+ * @param instance Node
1460
+ * @returns Attributed string
1461
+ */
1462
+ const getAttributedString = (fontStore, instance) => {
1463
+ const fragments = getFragments(fontStore, instance);
1464
+ return fromFragments(fragments);
1465
+ };
1466
+
1467
+ const engines = {
1468
+ bidi,
1469
+ linebreaker,
1470
+ justification,
1471
+ textDecoration,
1472
+ scriptItemizer,
1473
+ wordHyphenation,
1474
+ fontSubstitution,
1475
+ };
1476
+ const engine = layoutEngine(engines);
1477
+ const getMaxLines = (node) => node.style?.maxLines;
1478
+ const getTextOverflow = (node) => node.style?.textOverflow;
1479
+ /**
1480
+ * Get layout container for specific text node
1481
+ *
1482
+ * @param {number} width
1483
+ * @param {number} height
1484
+ * @param {Object} node
1485
+ * @returns {Object} layout container
1486
+ */
1487
+ const getContainer = (width, height, node) => {
1488
+ const maxLines = getMaxLines(node);
1489
+ const textOverflow = getTextOverflow(node);
1490
+ return {
1491
+ x: 0,
1492
+ y: 0,
1493
+ width,
1494
+ maxLines,
1495
+ height: height || Infinity,
1496
+ truncateMode: textOverflow,
1497
+ };
1498
+ };
1499
+ /**
1500
+ * Get text layout options for specific text node
1501
+ *
1502
+ * @param {Object} node instance
1503
+ * @returns {Object} layout options
1504
+ */
1505
+ const getLayoutOptions = (fontStore, node) => ({
1506
+ hyphenationPenalty: node.props.hyphenationPenalty,
1507
+ shrinkWhitespaceFactor: { before: -0.5, after: -0.5 },
1508
+ hyphenationCallback: node.props.hyphenationCallback ||
1509
+ fontStore?.getHyphenationCallback() ||
1510
+ null,
1511
+ });
1512
+ /**
1513
+ * Get text lines for given node
1514
+ *
1515
+ * @param node - Node
1516
+ * @param width - Container width
1517
+ * @param height - Container height
1518
+ * @param fontStore - Font store
1519
+ * @returns Layout lines
1520
+ */
1521
+ const layoutText = (node, width, height, fontStore) => {
1522
+ const attributedString = getAttributedString(fontStore, node);
1523
+ const container = getContainer(width, height, node);
1524
+ const options = getLayoutOptions(fontStore, node);
1525
+ const lines = engine(attributedString, container, options);
1526
+ return lines.reduce((acc, line) => [...acc, ...line], []);
1527
+ };
1528
+
1529
+ const isSvg$2 = (node) => node.type === P.Svg;
1530
+ const isText$4 = (node) => node.type === P.Text;
1531
+ const shouldIterate = (node) => !isSvg$2(node) && !isText$4(node);
1532
+ const shouldLayoutText = (node) => isText$4(node) && !node.lines;
1533
+ /**
1534
+ * Performs text layout on text node if wasn't calculated before.
1535
+ * Text layout is usually performed on Yoga's layout process (via setMeasureFunc),
1536
+ * but we need to layout those nodes with fixed width and height.
1537
+ *
1538
+ * @param node
1539
+ * @returns Layout node
1540
+ */
1541
+ const resolveTextLayout = (node, fontStore) => {
1542
+ if (shouldLayoutText(node)) {
1543
+ const width = node.box.width - (node.box.paddingRight + node.box.paddingLeft);
1544
+ const height = node.box.height - (node.box.paddingTop + node.box.paddingBottom);
1545
+ node.lines = layoutText(node, width, height, fontStore);
1546
+ }
1547
+ if (shouldIterate(node)) {
1548
+ if (!node.children)
1549
+ return node;
1550
+ const mapChild = (child) => resolveTextLayout(child, fontStore);
1551
+ const children = node.children.map(mapChild);
1552
+ return Object.assign({}, node, { children });
1553
+ }
1554
+ return node;
1555
+ };
1556
+
1557
+ const BASE_INHERITABLE_PROPERTIES = [
1558
+ 'color',
1559
+ 'fontFamily',
1560
+ 'fontSize',
1561
+ 'fontStyle',
1562
+ 'fontWeight',
1563
+ 'letterSpacing',
1564
+ 'opacity',
1565
+ 'textDecoration',
1566
+ 'textTransform',
1567
+ 'lineHeight',
1568
+ 'textAlign',
1569
+ 'visibility',
1570
+ 'wordSpacing',
1571
+ ];
1572
+ const TEXT_INHERITABLE_PROPERTIES = [
1573
+ ...BASE_INHERITABLE_PROPERTIES,
1574
+ 'backgroundColor',
1575
+ ];
1576
+ const isType$2 = (type) => (node) => node.type === type;
1577
+ const isSvg$1 = isType$2(P.Svg);
1578
+ const isText$3 = isType$2(P.Text);
1579
+ // Merge style values
1580
+ const mergeValues = (styleName, value, inheritedValue) => {
1581
+ switch (styleName) {
1582
+ case 'textDecoration': {
1583
+ // merge not none and not false textDecoration values to one rule
1584
+ return [inheritedValue, value].filter((v) => v && v !== 'none').join(' ');
1585
+ }
1586
+ default:
1587
+ return value;
1588
+ }
1589
+ };
1590
+ // Merge inherited and node styles
1591
+ const merge = (inheritedStyles, style) => {
1592
+ const mergedStyles = { ...inheritedStyles };
1593
+ Object.entries(style).forEach(([styleName, value]) => {
1594
+ mergedStyles[styleName] = mergeValues(styleName, value, inheritedStyles[styleName]);
1595
+ });
1596
+ return mergedStyles;
1597
+ };
1598
+ /**
1599
+ * Merges styles with node
1600
+ *
1601
+ * @param inheritedStyles - Style object
1602
+ * @returns Merge styles function
1603
+ */
1604
+ const mergeStyles = (inheritedStyles) => (node) => {
1605
+ const style = merge(inheritedStyles, node.style || {});
1606
+ return Object.assign({}, node, { style });
1607
+ };
1608
+ /**
1609
+ * Inherit style values from the root to the leafs
1610
+ *
1611
+ * @param node - Document root
1612
+ * @returns Document root with inheritance
1613
+ *
1614
+ */
1615
+ const resolveInheritance = (node) => {
1616
+ if (isSvg$1(node))
1617
+ return node;
1618
+ if (!('children' in node))
1619
+ return node;
1620
+ const inheritableProperties = isText$3(node)
1621
+ ? TEXT_INHERITABLE_PROPERTIES
1622
+ : BASE_INHERITABLE_PROPERTIES;
1623
+ const inheritStyles = pick(inheritableProperties, node.style || {});
1624
+ const resolveChild = compose(resolveInheritance, mergeStyles(inheritStyles));
1625
+ const children = node.children.map(resolveChild);
1626
+ return Object.assign({}, node, { children });
1627
+ };
1628
+
1629
+ const getComputedMargin = (node, edge) => {
1630
+ const { yogaNode } = node;
1631
+ return yogaNode ? yogaNode.getComputedMargin(edge) : null;
1632
+ };
1633
+ /**
1634
+ * Get Yoga computed magins. Zero otherwise
1635
+ *
1636
+ * @param node
1637
+ * @returns Margins
1638
+ */
1639
+ const getMargin = (node) => {
1640
+ const { style, box } = node;
1641
+ const marginTop = getComputedMargin(node, Yoga.Edge.Top) ||
1642
+ box?.marginTop ||
1643
+ style?.marginTop ||
1644
+ 0;
1645
+ const marginRight = getComputedMargin(node, Yoga.Edge.Right) ||
1646
+ box?.marginRight ||
1647
+ style?.marginRight ||
1648
+ 0;
1649
+ const marginBottom = getComputedMargin(node, Yoga.Edge.Bottom) ||
1650
+ box?.marginBottom ||
1651
+ style?.marginBottom ||
1652
+ 0;
1653
+ const marginLeft = getComputedMargin(node, Yoga.Edge.Left) ||
1654
+ box?.marginLeft ||
1655
+ style?.marginLeft ||
1656
+ 0;
1657
+ return { marginTop, marginRight, marginBottom, marginLeft };
1658
+ };
1659
+
1660
+ /**
1661
+ * Get Yoga computed position. Zero otherwise
1662
+ *
1663
+ * @param node
1664
+ * @returns Position
1665
+ */
1666
+ const getPosition = (node) => {
1667
+ const { yogaNode } = node;
1668
+ return {
1669
+ top: yogaNode?.getComputedTop() || 0,
1670
+ right: yogaNode?.getComputedRight() || 0,
1671
+ bottom: yogaNode?.getComputedBottom() || 0,
1672
+ left: yogaNode?.getComputedLeft() || 0,
1673
+ };
1674
+ };
1675
+
1676
+ const DEFAULT_DIMENSION = {
1677
+ width: 0,
1678
+ height: 0,
1679
+ };
1680
+ /**
1681
+ * Get Yoga computed dimensions. Zero otherwise
1682
+ *
1683
+ * @param node
1684
+ * @returns Dimensions
1685
+ */
1686
+ const getDimension = (node) => {
1687
+ const { yogaNode } = node;
1688
+ if (!yogaNode)
1689
+ return DEFAULT_DIMENSION;
1690
+ return {
1691
+ width: yogaNode.getComputedWidth(),
1692
+ height: yogaNode.getComputedHeight(),
1693
+ };
1694
+ };
1695
+
1696
+ const getComputedBorder = (yogaNode, edge) => (yogaNode ? yogaNode.getComputedBorder(edge) : 0);
1697
+ /**
1698
+ * Get Yoga computed border width. Zero otherwise
1699
+ *
1700
+ * @param node
1701
+ * @returns Border widths
1702
+ */
1703
+ const getBorderWidth = (node) => {
1704
+ const { yogaNode } = node;
1705
+ return {
1706
+ borderTopWidth: getComputedBorder(yogaNode, Yoga.Edge.Top),
1707
+ borderRightWidth: getComputedBorder(yogaNode, Yoga.Edge.Right),
1708
+ borderBottomWidth: getComputedBorder(yogaNode, Yoga.Edge.Bottom),
1709
+ borderLeftWidth: getComputedBorder(yogaNode, Yoga.Edge.Left),
1710
+ };
1711
+ };
1712
+
1713
+ /**
1714
+ * Set display attribute to node's Yoga instance
1715
+ *
1716
+ * @param value - Display
1717
+ * @returns Node instance wrapper
1718
+ */
1719
+ const setDisplay = (value) => (node) => {
1720
+ const { yogaNode } = node;
1721
+ if (yogaNode) {
1722
+ yogaNode.setDisplay(value === 'none' ? Yoga.Display.None : Yoga.Display.Flex);
1723
+ }
1724
+ return node;
1725
+ };
1726
+
1727
+ const OVERFLOW = {
1728
+ hidden: Yoga.Overflow.Hidden,
1729
+ scroll: Yoga.Overflow.Scroll,
1730
+ };
1731
+ /**
1732
+ * Set overflow attribute to node's Yoga instance
1733
+ *
1734
+ * @param value - Overflow value
1735
+ * @returns Node instance wrapper
1736
+ */
1737
+ const setOverflow = (value) => (node) => {
1738
+ const { yogaNode } = node;
1739
+ if (!isNil(value) && yogaNode) {
1740
+ const overflow = OVERFLOW[value] || Yoga.Overflow.Visible;
1741
+ yogaNode.setOverflow(overflow);
1742
+ }
1743
+ return node;
1744
+ };
1745
+
1746
+ const FLEX_WRAP = {
1747
+ wrap: Yoga.Wrap.Wrap,
1748
+ 'wrap-reverse': Yoga.Wrap.WrapReverse,
1749
+ };
1750
+ /**
1751
+ * Set flex wrap attribute to node's Yoga instance
1752
+ *
1753
+ * @param value - Flex wrap value
1754
+ * @returns Node instance wrapper
1755
+ */
1756
+ const setFlexWrap = (value) => (node) => {
1757
+ const { yogaNode } = node;
1758
+ if (yogaNode) {
1759
+ const flexWrap = FLEX_WRAP[value] || Yoga.Wrap.NoWrap;
1760
+ yogaNode.setFlexWrap(flexWrap);
1761
+ }
1762
+ return node;
1763
+ };
1764
+
1765
+ /**
1766
+ * Set generic yoga attribute to node's Yoga instance, handing `auto`, edges and percentage cases
1767
+ *
1768
+ * @param attr - Property
1769
+ * @param edge - Edge
1770
+ * @returns Node instance wrapper
1771
+ */
1772
+ const setYogaValue = (attr, edge) => (value) => (node) => {
1773
+ const { yogaNode } = node;
1774
+ if (!isNil(value) && yogaNode) {
1775
+ const hasEdge = !isNil(edge);
1776
+ const fixedMethod = `set${upperFirst(attr)}`;
1777
+ const autoMethod = `${fixedMethod}Auto`;
1778
+ const percentMethod = `${fixedMethod}Percent`;
1779
+ const percent = matchPercent(value);
1780
+ if (percent && !yogaNode[percentMethod]) {
1781
+ throw new Error(`You can't pass percentage values to ${attr} property`);
1782
+ }
1783
+ if (percent) {
1784
+ if (hasEdge) {
1785
+ yogaNode[percentMethod]?.(edge, percent.value);
1786
+ }
1787
+ else {
1788
+ yogaNode[percentMethod]?.(percent.value);
1789
+ }
1790
+ }
1791
+ else if (value === 'auto') {
1792
+ if (hasEdge) {
1793
+ yogaNode[autoMethod]?.(edge);
1794
+ }
1795
+ else {
1796
+ yogaNode[autoMethod]?.();
1797
+ }
1798
+ }
1799
+ else if (hasEdge) {
1800
+ yogaNode[fixedMethod]?.(edge, value);
1801
+ }
1802
+ else {
1803
+ yogaNode[fixedMethod]?.(value);
1804
+ }
1805
+ }
1806
+ return node;
1807
+ };
1808
+
1809
+ /**
1810
+ * Set flex grow attribute to node's Yoga instance
1811
+ *
1812
+ * @param value - Flex grow value
1813
+ * @returns Node instance wrapper
1814
+ */
1815
+ const setFlexGrow = (value) => (node) => {
1816
+ return setYogaValue('flexGrow')(value || 0)(node);
1817
+ };
1818
+
1819
+ /**
1820
+ * Set flex basis attribute to node's Yoga instance
1821
+ *
1822
+ * @param flex - Basis value
1823
+ * @param node - Node instance
1824
+ * @returns Node instance
1825
+ */
1826
+ const setFlexBasis = setYogaValue('flexBasis');
1827
+
1828
+ const ALIGN = {
1829
+ 'flex-start': Yoga.Align.FlexStart,
1830
+ center: Yoga.Align.Center,
1831
+ 'flex-end': Yoga.Align.FlexEnd,
1832
+ stretch: Yoga.Align.Stretch,
1833
+ baseline: Yoga.Align.Baseline,
1834
+ 'space-between': Yoga.Align.SpaceBetween,
1835
+ 'space-around': Yoga.Align.SpaceAround,
1836
+ 'space-evenly': Yoga.Align.SpaceEvenly,
1837
+ };
1838
+ /**
1839
+ * Set generic align attribute to node's Yoga instance
1840
+ *
1841
+ * @param attr - Specific align property
1842
+ * @param value - Specific align value
1843
+ * @param node - Node
1844
+ * @returns Node
1845
+ */
1846
+ const setAlign = (attr) => (value) => (node) => {
1847
+ const { yogaNode } = node;
1848
+ const defaultValue = attr === 'items' ? Yoga.Align.Stretch : Yoga.Align.Auto;
1849
+ if (yogaNode) {
1850
+ const align = ALIGN[value] || defaultValue;
1851
+ yogaNode[`setAlign${upperFirst(attr)}`](align);
1852
+ }
1853
+ return node;
1854
+ };
1855
+
1856
+ /**
1857
+ * Set align self attribute to node's Yoga instance
1858
+ *
1859
+ * @param align - Value
1860
+ * @param node - Node instance
1861
+ * @returns Node instance
1862
+ */
1863
+ const setAlignSelf = setAlign('self');
1864
+
1865
+ /**
1866
+ * Set align items attribute to node's Yoga instance
1867
+ *
1868
+ * @param align - Value
1869
+ * @param node - Node instance
1870
+ * @returns Node instance
1871
+ */
1872
+ const setAlignItems = setAlign('items');
1873
+
1874
+ /**
1875
+ * Set flex shrink attribute to node's Yoga instance
1876
+ *
1877
+ * @param value - Flex shrink value
1878
+ * @returns Node instance wrapper
1879
+ */
1880
+ const setFlexShrink = (value) => (node) => {
1881
+ return setYogaValue('flexShrink')(value || 1)(node);
1882
+ };
1883
+
1884
+ /**
1885
+ * Set aspect ratio attribute to node's Yoga instance
1886
+ *
1887
+ * @param value - Ratio
1888
+ * @returns Node instance
1889
+ */
1890
+ const setAspectRatio = (value) => (node) => {
1891
+ const { yogaNode } = node;
1892
+ if (!isNil(value) && yogaNode) {
1893
+ yogaNode.setAspectRatio(value);
1894
+ }
1895
+ return node;
1896
+ };
1897
+
1898
+ /**
1899
+ * Set align content attribute to node's Yoga instance
1900
+ *
1901
+ * @param align - Value
1902
+ * @param node - Instance
1903
+ * @returns Node instance
1904
+ */
1905
+ const setAlignContent = setAlign('content');
1906
+
1907
+ const POSITION = {
1908
+ absolute: Yoga.PositionType.Absolute,
1909
+ relative: Yoga.PositionType.Relative,
1910
+ static: Yoga.PositionType.Static,
1911
+ };
1912
+ /**
1913
+ * Set position type attribute to node's Yoga instance
1914
+ *
1915
+ * @param value - Position position type
1916
+ * @returns Node instance
1917
+ */
1918
+ const setPositionType = (value) => (node) => {
1919
+ const { yogaNode } = node;
1920
+ if (!isNil(value) && yogaNode) {
1921
+ yogaNode.setPositionType(POSITION[value]);
1922
+ }
1923
+ return node;
1924
+ };
1925
+
1926
+ const FLEX_DIRECTIONS = {
1927
+ row: Yoga.FlexDirection.Row,
1928
+ 'row-reverse': Yoga.FlexDirection.RowReverse,
1929
+ 'column-reverse': Yoga.FlexDirection.ColumnReverse,
1930
+ };
1931
+ /**
1932
+ * Set flex direction attribute to node's Yoga instance
1933
+ *
1934
+ * @param value - Flex direction value
1935
+ * @returns Node instance wrapper
1936
+ */
1937
+ const setFlexDirection = (value) => (node) => {
1938
+ const { yogaNode } = node;
1939
+ if (yogaNode) {
1940
+ const flexDirection = FLEX_DIRECTIONS[value] || Yoga.FlexDirection.Column;
1941
+ yogaNode.setFlexDirection(flexDirection);
1942
+ }
1943
+ return node;
1944
+ };
1945
+
1946
+ const JUSTIFY_CONTENT = {
1947
+ center: Yoga.Justify.Center,
1948
+ 'flex-end': Yoga.Justify.FlexEnd,
1949
+ 'space-between': Yoga.Justify.SpaceBetween,
1950
+ 'space-around': Yoga.Justify.SpaceAround,
1951
+ 'space-evenly': Yoga.Justify.SpaceEvenly,
1952
+ };
1953
+ /**
1954
+ * Set justify content attribute to node's Yoga instance
1955
+ *
1956
+ * @param value - Justify content value
1957
+ * @returns Node instance wrapper
1958
+ */
1959
+ const setJustifyContent = (value) => (node) => {
1960
+ const { yogaNode } = node;
1961
+ if (!isNil(value) && yogaNode) {
1962
+ const justifyContent = JUSTIFY_CONTENT[value] || Yoga.Justify.FlexStart;
1963
+ yogaNode.setJustifyContent(justifyContent);
1964
+ }
1965
+ return node;
1966
+ };
1967
+
1968
+ /**
1969
+ * Set margin top attribute to node's Yoga instance
1970
+ *
1971
+ * @param margin - Margin top
1972
+ * @param node - Node instance
1973
+ * @returns Node instance
1974
+ */
1975
+ const setMarginTop = setYogaValue('margin', Yoga.Edge.Top);
1976
+ /**
1977
+ * Set margin right attribute to node's Yoga instance
1978
+ *
1979
+ * @param margin - Margin right
1980
+ * @param node - Node instance
1981
+ * @returns Node instance
1982
+ */
1983
+ const setMarginRight = setYogaValue('margin', Yoga.Edge.Right);
1984
+ /**
1985
+ * Set margin bottom attribute to node's Yoga instance
1986
+ *
1987
+ * @param margin - Margin bottom
1988
+ * @param node - Node instance
1989
+ * @returns Node instance
1990
+ */
1991
+ const setMarginBottom = setYogaValue('margin', Yoga.Edge.Bottom);
1992
+ /**
1993
+ * Set margin left attribute to node's Yoga instance
1994
+ *
1995
+ * @param margin - Margin left
1996
+ * @param node - Node instance
1997
+ * @returns Node instance
1998
+ */
1999
+ const setMarginLeft = setYogaValue('margin', Yoga.Edge.Left);
2000
+
2001
+ /**
2002
+ * Set padding top attribute to node's Yoga instance
2003
+ *
2004
+ * @param padding - Padding top
2005
+ * @param node - Node instance
2006
+ * @returns Node instance
2007
+ */
2008
+ const setPaddingTop = setYogaValue('padding', Yoga.Edge.Top);
2009
+ /**
2010
+ * Set padding right attribute to node's Yoga instance
2011
+ *
2012
+ * @param padding - Padding right
2013
+ * @param node - Node instance
2014
+ * @returns Node instance
2015
+ */
2016
+ const setPaddingRight = setYogaValue('padding', Yoga.Edge.Right);
2017
+ /**
2018
+ * Set padding bottom attribute to node's Yoga instance
2019
+ *
2020
+ * @param padding - Padding bottom
2021
+ * @param node Node instance
2022
+ * @returns Node instance
2023
+ */
2024
+ const setPaddingBottom = setYogaValue('padding', Yoga.Edge.Bottom);
2025
+ /**
2026
+ * Set padding left attribute to node's Yoga instance
2027
+ *
2028
+ * @param padding - Padding left
2029
+ * @param node - Node instance
2030
+ * @returns Node instance
2031
+ */
2032
+ const setPaddingLeft = setYogaValue('padding', Yoga.Edge.Left);
2033
+
2034
+ /**
2035
+ * Set border top attribute to node's Yoga instance
2036
+ *
2037
+ * @param border - Border top width
2038
+ * @param node - Node instance
2039
+ * @returns Node instance
2040
+ */
2041
+ const setBorderTop = setYogaValue('border', Yoga.Edge.Top);
2042
+ /**
2043
+ * Set border right attribute to node's Yoga instance
2044
+ *
2045
+ * @param border - Border right width
2046
+ * @param node - Node instance
2047
+ * @returns Node instance
2048
+ */
2049
+ const setBorderRight = setYogaValue('border', Yoga.Edge.Right);
2050
+ /**
2051
+ * Set border bottom attribute to node's Yoga instance
2052
+ *
2053
+ * @param border - Border bottom width
2054
+ * @param node - Node instance
2055
+ * @returns Node instance
2056
+ */
2057
+ const setBorderBottom = setYogaValue('border', Yoga.Edge.Bottom);
2058
+ /**
2059
+ * Set border left attribute to node's Yoga instance
2060
+ *
2061
+ * @param border - Border left width
2062
+ * @param node - Node instance
2063
+ * @returns Node instance
2064
+ */
2065
+ const setBorderLeft = setYogaValue('border', Yoga.Edge.Left);
2066
+
2067
+ /**
2068
+ * Set position top attribute to node's Yoga instance
2069
+ *
2070
+ * @param position - Position top
2071
+ * @param node - Node instance
2072
+ * @returns Node instance
2073
+ */
2074
+ const setPositionTop = setYogaValue('position', Yoga.Edge.Top);
2075
+ /**
2076
+ * Set position right attribute to node's Yoga instance
2077
+ *
2078
+ * @param position - Position right
2079
+ * @param node - Node instance
2080
+ * @returns Node instance
2081
+ */
2082
+ const setPositionRight = setYogaValue('position', Yoga.Edge.Right);
2083
+ /**
2084
+ * Set position bottom attribute to node's Yoga instance
2085
+ *
2086
+ * @param position - Position bottom
2087
+ * @param node - Node instance
2088
+ * @returns Node instance
2089
+ */
2090
+ const setPositionBottom = setYogaValue('position', Yoga.Edge.Bottom);
2091
+ /**
2092
+ * Set position left attribute to node's Yoga instance
2093
+ *
2094
+ * @param position - Position left
2095
+ * @param node - Node instance
2096
+ * @returns Node instance
2097
+ */
2098
+ const setPositionLeft = setYogaValue('position', Yoga.Edge.Left);
2099
+
2100
+ /**
2101
+ * Set width to node's Yoga instance
2102
+ *
2103
+ * @param width - Width
2104
+ * @param node - Node instance
2105
+ * @returns Node instance
2106
+ */
2107
+ const setWidth = setYogaValue('width');
2108
+ /**
2109
+ * Set min width to node's Yoga instance
2110
+ *
2111
+ * @param min - Width
2112
+ * @param node - Node instance
2113
+ * @returns Node instance
2114
+ */
2115
+ const setMinWidth = setYogaValue('minWidth');
2116
+ /**
2117
+ * Set max width to node's Yoga instance
2118
+ *
2119
+ * @param max - Width
2120
+ * @param node - Node instance
2121
+ * @returns Node instance
2122
+ */
2123
+ const setMaxWidth = setYogaValue('maxWidth');
2124
+ /**
2125
+ * Set height to node's Yoga instance
2126
+ *
2127
+ * @param height - Height
2128
+ * @param node - Node instance
2129
+ * @returns Node instance
2130
+ */
2131
+ const setHeight = setYogaValue('height');
2132
+ /**
2133
+ * Set min height to node's Yoga instance
2134
+ *
2135
+ * @param min - Height
2136
+ * @param node - Node instance
2137
+ * @returns Node instance
2138
+ */
2139
+ const setMinHeight = setYogaValue('minHeight');
2140
+ /**
2141
+ * Set max height to node's Yoga instance
2142
+ *
2143
+ * @param max - Height
2144
+ * @param node - Node instance
2145
+ * @returns Node instance
2146
+ */
2147
+ const setMaxHeight = setYogaValue('maxHeight');
2148
+
2149
+ /**
2150
+ * Set rowGap value to node's Yoga instance
2151
+ *
2152
+ * @param value - Gap value
2153
+ * @returns Node instance wrapper
2154
+ */
2155
+ const setRowGap = setYogaValue('gap', Yoga.Gutter.Row);
2156
+ /**
2157
+ * Set columnGap value to node's Yoga instance
2158
+ *
2159
+ * @param value - Gap value
2160
+ * @returns Node instance wrapper
2161
+ */
2162
+ const setColumnGap = setYogaValue('gap', Yoga.Gutter.Column);
2163
+
2164
+ const getAspectRatio = (viewbox) => {
2165
+ if (!viewbox)
2166
+ return null;
2167
+ if (typeof viewbox === 'string')
2168
+ return null;
2169
+ return (viewbox.maxX - viewbox.minX) / (viewbox.maxY - viewbox.minY);
2170
+ };
2171
+ /**
2172
+ * Yoga svg measure function
2173
+ *
2174
+ * @param page
2175
+ * @param node
2176
+ * @returns Measure svg
2177
+ */
2178
+ const measureCanvas$1 = (page, node) => (width, widthMode, height, heightMode) => {
2179
+ const aspectRatio = getAspectRatio(node.props.viewBox) || 1;
2180
+ if (widthMode === Yoga.MeasureMode.Exactly ||
2181
+ widthMode === Yoga.MeasureMode.AtMost) {
2182
+ return { width, height: width / aspectRatio };
2183
+ }
2184
+ if (heightMode === Yoga.MeasureMode.Exactly) {
2185
+ return { width: height * aspectRatio };
2186
+ }
2187
+ return {};
2188
+ };
2189
+
2190
+ /**
2191
+ * Get lines width (if any)
2192
+ *
2193
+ * @param node
2194
+ * @returns Lines width
2195
+ */
2196
+ const linesWidth = (node) => {
2197
+ if (!node.lines)
2198
+ return 0;
2199
+ return Math.max(0, ...node.lines.map((line) => line.xAdvance));
2200
+ };
2201
+
2202
+ /**
2203
+ * Get lines height (if any)
2204
+ *
2205
+ * @param node
2206
+ * @returns Lines height
2207
+ */
2208
+ const linesHeight = (node) => {
2209
+ if (!node.lines)
2210
+ return -1;
2211
+ return node.lines.reduce((acc, line) => acc + line.box.height, 0);
2212
+ };
2213
+
2214
+ const ALIGNMENT_FACTORS = { center: 0.5, right: 1 };
2215
+ /**
2216
+ * Yoga text measure function
2217
+ *
2218
+ * @param page
2219
+ * @param node
2220
+ * @param fontStore
2221
+ * @returns {MeasureText} measure text function
2222
+ */
2223
+ const measureText = (page, node, fontStore) => (width, widthMode, height) => {
2224
+ if (widthMode === Yoga.MeasureMode.Exactly) {
2225
+ if (!node.lines)
2226
+ node.lines = layoutText(node, width, height, fontStore);
2227
+ return { height: linesHeight(node), width };
2228
+ }
2229
+ if (widthMode === Yoga.MeasureMode.AtMost) {
2230
+ const alignFactor = ALIGNMENT_FACTORS[node.style?.textAlign] || 0;
2231
+ if (!node.lines) {
2232
+ node.lines = layoutText(node, width, height, fontStore);
2233
+ node.alignOffset = (width - linesWidth(node)) * alignFactor; // Compensate align in variable width containers
2234
+ }
2235
+ return {
2236
+ height: linesHeight(node),
2237
+ width: Math.min(width, linesWidth(node)),
2238
+ };
2239
+ }
2240
+ return {};
2241
+ };
2242
+
2243
+ /**
2244
+ * Get image ratio
2245
+ *
2246
+ * @param node - Image node
2247
+ * @returns Image ratio
2248
+ */
2249
+ const getRatio = (node) => {
2250
+ return node.image?.data ? node.image.width / node.image.height : 1;
2251
+ };
2252
+
2253
+ /**
2254
+ * Checks if page has auto height
2255
+ *
2256
+ * @param page
2257
+ * @returns Is page height auto
2258
+ */
2259
+ const isHeightAuto = (page) => isNil(page.box?.height);
2260
+
2261
+ const SAFETY_HEIGHT$1 = 10;
2262
+ /**
2263
+ * Yoga image measure function
2264
+ *
2265
+ * @param page - Page
2266
+ * @param node - Node
2267
+ * @returns Measure image
2268
+ */
2269
+ const measureImage = (page, node) => (width, widthMode, height, heightMode) => {
2270
+ const imageRatio = getRatio(node);
2271
+ const imageMargin = getMargin(node);
2272
+ const pagePadding = getPadding(page);
2273
+ // TODO: Check image percentage margins
2274
+ const pageArea = isHeightAuto(page)
2275
+ ? Infinity
2276
+ : (page.box?.height || 0) -
2277
+ pagePadding.paddingTop -
2278
+ pagePadding.paddingBottom -
2279
+ imageMargin.marginTop -
2280
+ imageMargin.marginBottom -
2281
+ SAFETY_HEIGHT$1;
2282
+ // Skip measure if image data not present yet
2283
+ if (!node.image)
2284
+ return { width: 0, height: 0 };
2285
+ if (widthMode === Yoga.MeasureMode.Exactly &&
2286
+ heightMode === Yoga.MeasureMode.Undefined) {
2287
+ const scaledHeight = width / imageRatio;
2288
+ return { height: Math.min(pageArea, scaledHeight) };
2289
+ }
2290
+ if (heightMode === Yoga.MeasureMode.Exactly &&
2291
+ (widthMode === Yoga.MeasureMode.AtMost ||
2292
+ widthMode === Yoga.MeasureMode.Undefined)) {
2293
+ return { width: Math.min(height * imageRatio, width) };
2294
+ }
2295
+ if (widthMode === Yoga.MeasureMode.Exactly &&
2296
+ heightMode === Yoga.MeasureMode.AtMost) {
2297
+ const scaledHeight = width / imageRatio;
2298
+ return { height: Math.min(height, pageArea, scaledHeight) };
2299
+ }
2300
+ if (widthMode === Yoga.MeasureMode.AtMost &&
2301
+ heightMode === Yoga.MeasureMode.AtMost) {
2302
+ if (imageRatio > 1) {
2303
+ return {
2304
+ width,
2305
+ height: Math.min(width / imageRatio, height),
2306
+ };
2307
+ }
2308
+ return {
2309
+ height,
2310
+ width: Math.min(height * imageRatio, width),
2311
+ };
2312
+ }
2313
+ return { height, width };
2314
+ };
2315
+
2316
+ const SAFETY_HEIGHT = 10;
2317
+ const getMax = (values) => Math.max(-Infinity, ...values);
2318
+ /**
2319
+ * Helper object to predict canvas size
2320
+ * TODO: Implement remaining functions (as close as possible);
2321
+ */
2322
+ const measureCtx = () => {
2323
+ const ctx = {};
2324
+ const points = [];
2325
+ const nil = () => ctx;
2326
+ const addPoint = (x, y) => points.push([x, y]);
2327
+ const moveTo = (x, y) => {
2328
+ addPoint(x, y);
2329
+ return ctx;
2330
+ };
2331
+ const rect = (x, y, w, h) => {
2332
+ addPoint(x, y);
2333
+ addPoint(x + w, y);
2334
+ addPoint(x, y + h);
2335
+ addPoint(x + w, y + h);
2336
+ return ctx;
2337
+ };
2338
+ const ellipse = (x, y, rx, ry) => {
2339
+ ry = ry || rx;
2340
+ addPoint(x - rx, y - ry);
2341
+ addPoint(x + rx, y - ry);
2342
+ addPoint(x + rx, y + ry);
2343
+ addPoint(x - rx, y + ry);
2344
+ return ctx;
2345
+ };
2346
+ const polygon = (...pts) => {
2347
+ points.push(...pts);
2348
+ return ctx;
2349
+ };
2350
+ // Change dimensions
2351
+ ctx.rect = rect;
2352
+ ctx.moveTo = moveTo;
2353
+ ctx.lineTo = moveTo;
2354
+ ctx.circle = ellipse;
2355
+ ctx.polygon = polygon;
2356
+ ctx.ellipse = ellipse;
2357
+ ctx.roundedRect = rect;
2358
+ // To be implemented
2359
+ ctx.text = nil;
2360
+ ctx.path = nil;
2361
+ ctx.lineWidth = nil;
2362
+ ctx.bezierCurveTo = nil;
2363
+ ctx.quadraticCurveTo = nil;
2364
+ ctx.scale = nil;
2365
+ ctx.rotate = nil;
2366
+ ctx.translate = nil;
2367
+ // These don't change dimensions
2368
+ ctx.dash = nil;
2369
+ ctx.clip = nil;
2370
+ ctx.save = nil;
2371
+ ctx.fill = nil;
2372
+ ctx.font = nil;
2373
+ ctx.stroke = nil;
2374
+ ctx.lineCap = nil;
2375
+ ctx.opacity = nil;
2376
+ ctx.restore = nil;
2377
+ ctx.lineJoin = nil;
2378
+ ctx.fontSize = nil;
2379
+ ctx.fillColor = nil;
2380
+ ctx.miterLimit = nil;
2381
+ ctx.strokeColor = nil;
2382
+ ctx.fillOpacity = nil;
2383
+ ctx.strokeOpacity = nil;
2384
+ ctx.linearGradient = nil;
2385
+ ctx.radialGradient = nil;
2386
+ ctx.getWidth = () => getMax(points.map((p) => p[0]));
2387
+ ctx.getHeight = () => getMax(points.map((p) => p[1]));
2388
+ return ctx;
2389
+ };
2390
+ /**
2391
+ * @typedef {Function} MeasureCanvas
2392
+ * @returns {{ width: number, height: number }} canvas width and height
2393
+ */
2394
+ /**
2395
+ * Yoga canvas measure function
2396
+ *
2397
+ * @param {Object} page
2398
+ * @param {Object} node
2399
+ * @returns {MeasureCanvas} measure canvas
2400
+ */
2401
+ const measureCanvas = (page, node) => () => {
2402
+ const imageMargin = getMargin(node);
2403
+ const pagePadding = getPadding(page);
2404
+ // TODO: Check image percentage margins
2405
+ const pageArea = isHeightAuto(page)
2406
+ ? Infinity
2407
+ : (page.box?.height || 0) -
2408
+ pagePadding.paddingTop -
2409
+ pagePadding.paddingBottom -
2410
+ imageMargin.marginTop -
2411
+ imageMargin.marginBottom -
2412
+ SAFETY_HEIGHT;
2413
+ const ctx = measureCtx();
2414
+ node.props.paint(ctx);
2415
+ const width = ctx.getWidth();
2416
+ const height = Math.min(pageArea, ctx.getHeight());
2417
+ return { width, height };
2418
+ };
2419
+
2420
+ const isType$1 = (type) => (node) => node.type === type;
2421
+ const isSvg = isType$1(P.Svg);
2422
+ const isText$2 = isType$1(P.Text);
2423
+ const isNote = isType$1(P.Note);
2424
+ const isPage = isType$1(P.Page);
2425
+ const isImage = isType$1(P.Image);
2426
+ const isCanvas = isType$1(P.Canvas);
2427
+ const isTextInstance$1 = isType$1(P.TextInstance);
2428
+ const setNodeHeight = (node) => {
2429
+ const value = isPage(node) ? node.box?.height : node.style?.height;
2430
+ return setHeight(value);
2431
+ };
2432
+ /**
2433
+ * Set styles valeus into yoga node before layout calculation
2434
+ *
2435
+ * @param node
2436
+ */
2437
+ const setYogaValues = (node) => {
2438
+ compose(setNodeHeight(node), setWidth(node.style.width), setMinWidth(node.style.minWidth), setMaxWidth(node.style.maxWidth), setMinHeight(node.style.minHeight), setMaxHeight(node.style.maxHeight), setMarginTop(node.style.marginTop), setMarginRight(node.style.marginRight), setMarginBottom(node.style.marginBottom), setMarginLeft(node.style.marginLeft), setPaddingTop(node.style.paddingTop), setPaddingRight(node.style.paddingRight), setPaddingBottom(node.style.paddingBottom), setPaddingLeft(node.style.paddingLeft), setPositionType(node.style.position), setPositionTop(node.style.top), setPositionRight(node.style.right), setPositionBottom(node.style.bottom), setPositionLeft(node.style.left), setBorderTop(node.style.borderTopWidth), setBorderRight(node.style.borderRightWidth), setBorderBottom(node.style.borderBottomWidth), setBorderLeft(node.style.borderLeftWidth), setDisplay(node.style.display), setFlexDirection(node.style.flexDirection), setAlignSelf(node.style.alignSelf), setAlignContent(node.style.alignContent), setAlignItems(node.style.alignItems), setJustifyContent(node.style.justifyContent), setFlexWrap(node.style.flexWrap), setOverflow(node.style.overflow), setAspectRatio(node.style.aspectRatio), setFlexBasis(node.style.flexBasis), setFlexGrow(node.style.flexGrow), setFlexShrink(node.style.flexShrink), setRowGap(node.style.rowGap), setColumnGap(node.style.columnGap))(node);
2439
+ };
2440
+ /**
2441
+ * Inserts child into parent' yoga node
2442
+ *
2443
+ * @param parent parent
2444
+ * @returns Insert yoga nodes
2445
+ */
2446
+ const insertYogaNodes = (parent) => (child) => {
2447
+ parent.insertChild(child.yogaNode, parent.getChildCount());
2448
+ return child;
2449
+ };
2450
+ const setMeasureFunc = (node, page, fontStore) => {
2451
+ const { yogaNode } = node;
2452
+ if (isText$2(node)) {
2453
+ yogaNode.setMeasureFunc(measureText(page, node, fontStore));
2454
+ }
2455
+ if (isImage(node)) {
2456
+ yogaNode.setMeasureFunc(measureImage(page, node));
2457
+ }
2458
+ if (isCanvas(node)) {
2459
+ yogaNode.setMeasureFunc(measureCanvas(page, node));
2460
+ }
2461
+ if (isSvg(node)) {
2462
+ yogaNode.setMeasureFunc(measureCanvas$1(page, node));
2463
+ }
2464
+ return node;
2465
+ };
2466
+ const isLayoutElement = (node) => !isText$2(node) && !isNote(node) && !isSvg(node);
2467
+ /**
2468
+ * @typedef {Function} CreateYogaNodes
2469
+ * @param {Object} node
2470
+ * @returns {Object} node with appended yoga node
2471
+ */
2472
+ /**
2473
+ * Creates and add yoga node to document tree
2474
+ * Handles measure function for text and image nodes
2475
+ *
2476
+ * @returns Create yoga nodes
2477
+ */
2478
+ const createYogaNodes = (page, fontStore, yoga) => (node) => {
2479
+ const yogaNode = yoga.node.create();
2480
+ const result = Object.assign({}, node, { yogaNode });
2481
+ setYogaValues(result);
2482
+ if (isLayoutElement(node) && node.children) {
2483
+ const resolveChild = compose(insertYogaNodes(yogaNode), createYogaNodes(page, fontStore, yoga));
2484
+ result.children = node.children.map(resolveChild);
2485
+ }
2486
+ setMeasureFunc(result, page, fontStore);
2487
+ return result;
2488
+ };
2489
+ /**
2490
+ * Performs yoga calculation
2491
+ *
2492
+ * @param page - Page node
2493
+ * @returns Page node
2494
+ */
2495
+ const calculateLayout = (page) => {
2496
+ page.yogaNode.calculateLayout();
2497
+ return page;
2498
+ };
2499
+ /**
2500
+ * Saves Yoga layout result into 'box' attribute of node
2501
+ *
2502
+ * @param node
2503
+ * @returns Node with box data
2504
+ */
2505
+ const persistDimensions = (node) => {
2506
+ if (isTextInstance$1(node))
2507
+ return node;
2508
+ const box = Object.assign(getPadding(node), getMargin(node), getBorderWidth(node), getPosition(node), getDimension(node));
2509
+ const newNode = Object.assign({}, node, { box });
2510
+ if (!node.children)
2511
+ return newNode;
2512
+ const children = node.children.map(persistDimensions);
2513
+ return Object.assign({}, newNode, { children });
2514
+ };
2515
+ /**
2516
+ * Removes yoga node from document tree
2517
+ *
2518
+ * @param node
2519
+ * @returns Node without yoga node
2520
+ */
2521
+ const destroyYogaNodes = (node) => {
2522
+ const newNode = Object.assign({}, node);
2523
+ delete newNode.yogaNode;
2524
+ if (!node.children)
2525
+ return newNode;
2526
+ const children = node.children.map(destroyYogaNodes);
2527
+ return Object.assign({}, newNode, { children });
2528
+ };
2529
+ /**
2530
+ * Free yoga node from document tree
2531
+ *
2532
+ * @param node
2533
+ * @returns Node without yoga node
2534
+ */
2535
+ const freeYogaNodes = (node) => {
2536
+ if (node.yogaNode)
2537
+ node.yogaNode.freeRecursive();
2538
+ return node;
2539
+ };
2540
+ /**
2541
+ * Calculates page object layout using Yoga.
2542
+ * Takes node values from 'box' and 'style' attributes, and persist them back into 'box'
2543
+ * Destroy yoga values at the end.
2544
+ *
2545
+ * @param page - Object
2546
+ * @returns Page object with correct 'box' layout attributes
2547
+ */
2548
+ const resolvePageDimensions = (page, fontStore, yoga) => {
2549
+ if (isNil(page))
2550
+ return null;
2551
+ return compose(destroyYogaNodes, freeYogaNodes, persistDimensions, calculateLayout, createYogaNodes(page, fontStore, yoga))(page);
2552
+ };
2553
+ /**
2554
+ * Calculates root object layout using Yoga.
2555
+ *
2556
+ * @param node - Root object
2557
+ * @param fontStore - Font store
2558
+ * @returns Root object with correct 'box' layout attributes
2559
+ */
2560
+ const resolveDimensions = (node, fontStore) => {
2561
+ if (!node.children)
2562
+ return node;
2563
+ const resolveChild = (child) => resolvePageDimensions(child, fontStore, node.yoga);
2564
+ const children = node.children.map(resolveChild);
2565
+ return Object.assign({}, node, { children });
2566
+ };
2567
+
2568
+ const isText$1 = (node) => node.type === P.Text;
2569
+ // Prevent splitting elements by low decimal numbers
2570
+ const SAFETY_THRESHOLD = 0.001;
2571
+ const assingChildren = (children, node) => Object.assign({}, node, { children });
2572
+ const getTop = (node) => node.box?.top || 0;
2573
+ const allFixed = (nodes) => nodes.every(isFixed);
2574
+ const isDynamic = (node) => node.props && 'render' in node.props;
2575
+ const relayoutPage = compose(resolveTextLayout, resolvePageDimensions, resolveInheritance, resolvePageStyles);
2576
+ const warnUnavailableSpace = (node) => {
2577
+ // eslint-disable-next-line no-console
2578
+ console.warn(`Node of type ${node.type} can't wrap between pages and it's bigger than available page height`);
2579
+ };
2580
+ const splitNodes = (height, contentArea, nodes) => {
2581
+ const currentChildren = [];
2582
+ const nextChildren = [];
2583
+ for (let i = 0; i < nodes.length; i += 1) {
2584
+ const child = nodes[i];
2585
+ const futureNodes = nodes.slice(i + 1);
2586
+ const futureFixedNodes = futureNodes.filter(isFixed);
2587
+ const nodeTop = getTop(child);
2588
+ const nodeHeight = child.box.height;
2589
+ const isOutside = height <= nodeTop;
2590
+ const shouldBreak$1 = shouldBreak(child, futureNodes, height, currentChildren);
2591
+ const prevChild = nodes.length > 0 && i > 0 ? nodes[i - 1] : undefined;
2592
+ const shouldSplit = height + SAFETY_THRESHOLD < nodeTop + nodeHeight;
2593
+ const canWrap = getWrap(child, prevChild, currentChildren);
2594
+ const fitsInsidePage = nodeHeight <= contentArea;
2595
+ if (isFixed(child)) {
2596
+ nextChildren.push(child);
2597
+ currentChildren.push(child);
2598
+ continue;
2599
+ }
2600
+ if (isOutside) {
2601
+ const box = Object.assign({}, child.box, { top: child.box.top - height });
2602
+ const next = Object.assign({}, child, { box });
2603
+ nextChildren.push(next);
2604
+ continue;
2605
+ }
2606
+ if (!fitsInsidePage && !canWrap) {
2607
+ if (NON_WRAP_TYPES.includes(child.type)) {
2608
+ currentChildren.push(child);
2609
+ nextChildren.push(...futureNodes);
2610
+ warnUnavailableSpace(child);
2611
+ }
2612
+ else {
2613
+ // We don't want to break non wrapable nodes, so we just let them be.
2614
+ const box = Object.assign({}, child.box, {
2615
+ top: child.box.top - height,
2616
+ });
2617
+ const props = Object.assign({}, child.props, {
2618
+ wrap: true,
2619
+ break: false,
2620
+ });
2621
+ const next = Object.assign({}, child, { box, props });
2622
+ currentChildren.push(...futureFixedNodes);
2623
+ nextChildren.push(next, ...futureNodes);
2624
+ }
2625
+ break;
2626
+ }
2627
+ if (shouldBreak$1) {
2628
+ const box = Object.assign({}, child.box, { top: child.box.top - height });
2629
+ const props = Object.assign({}, child.props, {
2630
+ wrap: true,
2631
+ break: false,
2632
+ });
2633
+ const next = Object.assign({}, child, { box, props });
2634
+ currentChildren.push(...futureFixedNodes);
2635
+ nextChildren.push(next, ...futureNodes);
2636
+ break;
2637
+ }
2638
+ if (shouldSplit) {
2639
+ const [currentChild, nextChild] = split(child, height, contentArea);
2640
+ // All children are moved to the next page, it doesn't make sense to show the parent on the current page
2641
+ if (child.children.length > 0 && currentChild.children.length === 0) {
2642
+ // But if the current page is empty then we can just include the parent on the current page
2643
+ // if (currentChildren.length === 0) {
2644
+ // currentChildren.push(child, ...futureFixedNodes);
2645
+ // nextChildren.push(...futureNodes);
2646
+ // } else {
2647
+ const box = Object.assign({}, child.box, {
2648
+ top: child.box.top - height,
2649
+ });
2650
+ const next = Object.assign({}, child, { box });
2651
+ currentChildren.push(...futureFixedNodes);
2652
+ nextChildren.push(next, ...futureNodes);
2653
+ // }
2654
+ break;
2655
+ }
2656
+ if (currentChild)
2657
+ currentChildren.push(currentChild);
2658
+ if (nextChild)
2659
+ nextChildren.push(nextChild);
2660
+ continue;
2661
+ }
2662
+ currentChildren.push(child);
2663
+ }
2664
+ return [currentChildren, nextChildren];
2665
+ };
2666
+ const splitChildren = (height, contentArea, node) => {
2667
+ const children = node.children || [];
2668
+ const availableHeight = height - getTop(node);
2669
+ return splitNodes(availableHeight, contentArea, children);
2670
+ };
2671
+ const splitView = (node, height, contentArea) => {
2672
+ const [currentNode, nextNode] = splitNode(node, height);
2673
+ const [currentChilds, nextChildren] = splitChildren(height, contentArea, node);
2674
+ return [
2675
+ assingChildren(currentChilds, currentNode),
2676
+ assingChildren(nextChildren, nextNode),
2677
+ ];
2678
+ };
2679
+ const split = (node, height, contentArea) => isText$1(node) ? splitText(node, height) : splitView(node, height, contentArea);
2680
+ const shouldResolveDynamicNodes = (node) => {
2681
+ const children = node.children || [];
2682
+ return isDynamic(node) || children.some(shouldResolveDynamicNodes);
2683
+ };
2684
+ const resolveDynamicNodes = (props, node) => {
2685
+ const isNodeDynamic = isDynamic(node);
2686
+ // Call render prop on dynamic nodes and append result to children
2687
+ const resolveChildren = (children = []) => {
2688
+ if (isNodeDynamic) {
2689
+ const res = node.props.render(props);
2690
+ return (createInstances(res)
2691
+ .filter(Boolean)
2692
+ // @ts-expect-error rework dynamic nodes. conflicting types
2693
+ .map((n) => resolveDynamicNodes(props, n)));
2694
+ }
2695
+ return children.map((c) => resolveDynamicNodes(props, c));
2696
+ };
2697
+ // We reset dynamic text box so it can be computed again later on
2698
+ const resetHeight = isNodeDynamic && isText$1(node);
2699
+ const box = resetHeight ? { ...node.box, height: 0 } : node.box;
2700
+ const children = resolveChildren(node.children);
2701
+ // @ts-expect-error handle text here specifically
2702
+ const lines = isNodeDynamic ? null : node.lines;
2703
+ return Object.assign({}, node, { box, lines, children });
2704
+ };
2705
+ const resolveDynamicPage = (props, page, fontStore, yoga) => {
2706
+ if (shouldResolveDynamicNodes(page)) {
2707
+ const resolvedPage = resolveDynamicNodes(props, page);
2708
+ return relayoutPage(resolvedPage, fontStore, yoga);
2709
+ }
2710
+ return page;
2711
+ };
2712
+ const splitPage = (page, pageNumber, fontStore, yoga) => {
2713
+ const wrapArea = getWrapArea(page);
2714
+ const contentArea = getContentArea(page);
2715
+ const dynamicPage = resolveDynamicPage({ pageNumber }, page, fontStore, yoga);
2716
+ const height = page.style.height;
2717
+ const [currentChilds, nextChilds] = splitNodes(wrapArea, contentArea, dynamicPage.children);
2718
+ const relayout = (node) =>
2719
+ // @ts-expect-error rework pagination
2720
+ relayoutPage(node, fontStore, yoga);
2721
+ const currentBox = { ...page.box, height };
2722
+ const currentPage = relayout(Object.assign({}, page, { box: currentBox, children: currentChilds }));
2723
+ if (nextChilds.length === 0 || allFixed(nextChilds))
2724
+ return [currentPage, null];
2725
+ const nextBox = omit('height', page.box);
2726
+ const nextProps = omit('bookmark', page.props);
2727
+ const nextPage = relayout(Object.assign({}, page, {
2728
+ props: nextProps,
2729
+ box: nextBox,
2730
+ children: nextChilds,
2731
+ }));
2732
+ return [currentPage, nextPage];
2733
+ };
2734
+ const resolvePageIndices = (fontStore, yoga, page, pageNumber, pages) => {
2735
+ const totalPages = pages.length;
2736
+ const props = {
2737
+ totalPages,
2738
+ pageNumber: pageNumber + 1,
2739
+ subPageNumber: page.subPageNumber + 1,
2740
+ subPageTotalPages: page.subPageTotalPages,
2741
+ };
2742
+ return resolveDynamicPage(props, page, fontStore, yoga);
2743
+ };
2744
+ const assocSubPageData = (subpages) => {
2745
+ return subpages.map((page, i) => ({
2746
+ ...page,
2747
+ subPageNumber: i,
2748
+ subPageTotalPages: subpages.length,
2749
+ }));
2750
+ };
2751
+ const dissocSubPageData = (page) => {
2752
+ return omit(['subPageNumber', 'subPageTotalPages'], page);
2753
+ };
2754
+ const paginate = (page, pageNumber, fontStore, yoga) => {
2755
+ if (!page)
2756
+ return [];
2757
+ if (page.props?.wrap === false)
2758
+ return [page];
2759
+ let splittedPage = splitPage(page, pageNumber, fontStore, yoga);
2760
+ const pages = [splittedPage[0]];
2761
+ let nextPage = splittedPage[1];
2762
+ while (nextPage !== null) {
2763
+ splittedPage = splitPage(nextPage, pageNumber + pages.length, fontStore, yoga);
2764
+ pages.push(splittedPage[0]);
2765
+ nextPage = splittedPage[1];
2766
+ }
2767
+ return pages;
2768
+ };
2769
+ /**
2770
+ * Performs pagination. This is the step responsible of breaking the whole document
2771
+ * into pages following pagiation rules, such as `fixed`, `break` and dynamic nodes.
2772
+ *
2773
+ * @param root - Document node
2774
+ * @param fontStore - Font store
2775
+ * @returns Layout node
2776
+ */
2777
+ const resolvePagination = (root, fontStore) => {
2778
+ let pages = [];
2779
+ let pageNumber = 1;
2780
+ for (let i = 0; i < root.children.length; i += 1) {
2781
+ const page = root.children[i];
2782
+ let subpages = paginate(page, pageNumber, fontStore, root.yoga);
2783
+ subpages = assocSubPageData(subpages);
2784
+ pageNumber += subpages.length;
2785
+ pages = pages.concat(subpages);
2786
+ }
2787
+ pages = pages.map((...args) => dissocSubPageData(resolvePageIndices(fontStore, root.yoga, ...args)));
2788
+ return assingChildren(pages, root);
2789
+ };
2790
+
2791
+ /**
2792
+ * Translates page percentage horizontal paddings in fixed ones
2793
+ *
2794
+ * @param container - Page container
2795
+ * @returns Resolve page horizontal padding
2796
+ */
2797
+ const resolvePageHorizontalPadding = (container) => (value) => {
2798
+ const match = matchPercent(value);
2799
+ const width = container.width;
2800
+ return match ? match.percent * width : value;
2801
+ };
2802
+ /**
2803
+ * Translates page percentage vertical paddings in fixed ones
2804
+ *
2805
+ * @param container - Page container
2806
+ * @returns Resolve page vertical padding
2807
+ */
2808
+ const resolvePageVerticalPadding = (container) => (value) => {
2809
+ const match = matchPercent(value);
2810
+ const height = container.height;
2811
+ return match ? match.percent * height : value;
2812
+ };
2813
+ /**
2814
+ * Translates page percentage paddings in fixed ones
2815
+ *
2816
+ * @param page
2817
+ * @returns Page with fixed paddings
2818
+ */
2819
+ const resolvePagePaddings = (page) => {
2820
+ const container = page.style;
2821
+ const style = evolve({
2822
+ paddingTop: resolvePageVerticalPadding(container),
2823
+ paddingLeft: resolvePageHorizontalPadding(container),
2824
+ paddingRight: resolvePageHorizontalPadding(container),
2825
+ paddingBottom: resolvePageVerticalPadding(container),
2826
+ }, page.style);
2827
+ return Object.assign({}, page, { style });
2828
+ };
2829
+ /**
2830
+ * Translates all pages percentage paddings in fixed ones
2831
+ * This has to be computed from pages calculated size and not by Yoga
2832
+ * because at this point we didn't performed pagination yet.
2833
+ *
2834
+ * @param root - Document root
2835
+ * @returns Document root with translated page paddings
2836
+ */
2837
+ const resolvePagesPaddings = (root) => {
2838
+ if (!root.children)
2839
+ return root;
2840
+ const children = root.children.map(resolvePagePaddings);
2841
+ return Object.assign({}, root, { children });
2842
+ };
2843
+
2844
+ const resolveRadius = (box) => (value) => {
2845
+ if (!value)
2846
+ return undefined;
2847
+ const match = matchPercent(value);
2848
+ return match ? match.percent * Math.min(box.width, box.height) : value;
2849
+ };
2850
+ /**
2851
+ * Transforms percent border radius into fixed values
2852
+ *
2853
+ * @param node
2854
+ * @returns Node
2855
+ */
2856
+ const resolvePercentRadius = (node) => {
2857
+ const style = evolve({
2858
+ borderTopLeftRadius: resolveRadius(node.box),
2859
+ borderTopRightRadius: resolveRadius(node.box),
2860
+ borderBottomRightRadius: resolveRadius(node.box),
2861
+ borderBottomLeftRadius: resolveRadius(node.box),
2862
+ }, node.style || {});
2863
+ const newNode = Object.assign({}, node, { style });
2864
+ if (!node.children)
2865
+ return newNode;
2866
+ const children = node.children.map(resolvePercentRadius);
2867
+ return Object.assign({}, newNode, { children });
2868
+ };
2869
+
2870
+ /**
2871
+ * Transform percent height into fixed
2872
+ *
2873
+ * @param height
2874
+ * @returns Height
2875
+ */
2876
+ const transformHeight = (pageArea, height) => {
2877
+ const match = matchPercent(height);
2878
+ return match ? match.percent * pageArea : height;
2879
+ };
2880
+ /**
2881
+ * Get page area (height minus paddings)
2882
+ *
2883
+ * @param page
2884
+ * @returns Page area
2885
+ */
2886
+ const getPageArea = (page) => {
2887
+ const pageHeight = page.style.height;
2888
+ const pagePaddingTop = (page.style?.paddingTop || 0);
2889
+ const pagePaddingBottom = (page.style?.paddingBottom || 0);
2890
+ return pageHeight - pagePaddingTop - pagePaddingBottom;
2891
+ };
2892
+ /**
2893
+ * Transform node percent height to fixed
2894
+ *
2895
+ * @param page
2896
+ * @param node
2897
+ * @returns Transformed node
2898
+ */
2899
+ const resolveNodePercentHeight = (page, node) => {
2900
+ if (isNil(page.style?.height))
2901
+ return node;
2902
+ if (isNil(node.style?.height))
2903
+ return node;
2904
+ const pageArea = getPageArea(page);
2905
+ const height = transformHeight(pageArea, node.style.height);
2906
+ const style = Object.assign({}, node.style, { height });
2907
+ return Object.assign({}, node, { style });
2908
+ };
2909
+ /**
2910
+ * Transform page immediate children with percent height to fixed
2911
+ *
2912
+ * @param page
2913
+ * @returns Transformed page
2914
+ */
2915
+ const resolvePagePercentHeight = (page) => {
2916
+ if (!page.children)
2917
+ return page;
2918
+ const resolveChild = (child) => resolveNodePercentHeight(page, child);
2919
+ const children = page.children.map(resolveChild);
2920
+ return Object.assign({}, page, { children });
2921
+ };
2922
+ /**
2923
+ * Transform all page immediate children with percent height to fixed.
2924
+ * This is needed for computing correct dimensions on pre-pagination layout.
2925
+ *
2926
+ * @param root - Document root
2927
+ * @returns Transformed document root
2928
+ */
2929
+ const resolvePercentHeight = (root) => {
2930
+ if (!root.children)
2931
+ return root;
2932
+ const children = root.children.map(resolvePagePercentHeight);
2933
+ return Object.assign({}, root, { children });
2934
+ };
2935
+
2936
+ const isType = (type) => (node) => node.type === type;
2937
+ const isLink = isType(P.Link);
2938
+ const isText = isType(P.Text);
2939
+ const isTextInstance = isType(P.TextInstance);
2940
+ /**
2941
+ * Checks if node has render prop
2942
+ *
2943
+ * @param node
2944
+ * @returns Has render prop?
2945
+ */
2946
+ const hasRenderProp = (node) => 'render' in node.props;
2947
+ /**
2948
+ * Checks if node is text type (Text or TextInstance)
2949
+ *
2950
+ * @param node
2951
+ * @returns Are all children text instances?
2952
+ */
2953
+ const isTextType = (node) => isText(node) || isTextInstance(node);
2954
+ /**
2955
+ * Checks if is tet link that needs to be wrapped in Text
2956
+ *
2957
+ * @param node
2958
+ * @returns Are all children text instances?
2959
+ */
2960
+ const isTextLink = (node) => {
2961
+ const children = node.children || [];
2962
+ // Text string inside a Link
2963
+ if (children.every(isTextInstance))
2964
+ return true;
2965
+ // Text node inside a Link
2966
+ if (children.every(isText))
2967
+ return false;
2968
+ return children.every(isTextType);
2969
+ };
2970
+ /**
2971
+ * Wraps node children inside Text node
2972
+ *
2973
+ * @param node
2974
+ * @returns Node with intermediate Text child
2975
+ */
2976
+ const wrapText = (node) => {
2977
+ const textElement = {
2978
+ type: P.Text,
2979
+ props: {},
2980
+ style: {},
2981
+ box: {},
2982
+ children: node.children,
2983
+ };
2984
+ return Object.assign({}, node, { children: [textElement] });
2985
+ };
2986
+ const transformLink = (node) => {
2987
+ if (!isLink(node))
2988
+ return node;
2989
+ // If has render prop substitute the instance by a Text, that will
2990
+ // ultimately render the inline Link via the textkit PDF renderer.
2991
+ if (hasRenderProp(node))
2992
+ return Object.assign({}, node, { type: P.Text });
2993
+ // If is a text link (either contains Text or TextInstance), wrap it
2994
+ // inside a Text element so styles are applied correctly
2995
+ if (isTextLink(node))
2996
+ return wrapText(node);
2997
+ return node;
2998
+ };
2999
+ /**
3000
+ * Transforms Link layout to correctly render text and dynamic rendered links
3001
+ *
3002
+ * @param node
3003
+ * @returns Node with link substitution
3004
+ */
3005
+ const resolveLinkSubstitution = (node) => {
3006
+ if (!node.children)
3007
+ return node;
3008
+ const resolveChild = compose(transformLink, resolveLinkSubstitution);
3009
+ const children = node.children.map(resolveChild);
3010
+ return Object.assign({}, node, { children });
3011
+ };
3012
+
3013
+ const layout = asyncCompose(resolveZIndex, resolveOrigin, resolveAssets, resolvePagination, resolveTextLayout, resolvePercentRadius, resolveDimensions, resolveSvg, resolveAssets, resolveInheritance, resolvePercentHeight, resolvePagesPaddings, resolveStyles, resolveLinkSubstitution, resolveBookmarks, resolvePageSizes, resolveYoga);
3014
+
3015
+ export { layout as default };