@rnichi11/react-column-pdf-layout 4.4.3

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,3518 @@
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 '@rnichi11/react-column-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$8 = (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$8(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$8(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 isText$7 = (node) => node.type === P.Text;
1057
+ /**
1058
+ * Clear lines from Text nodes so they get relayout with the column width.
1059
+ * Text nodes retain their previous layout (full-width) unless we clear lines,
1060
+ * which forces the measure function to run layoutText again with the correct width.
1061
+ *
1062
+ * Skip clearing for split results (__preserveLines) - they already have correctly
1063
+ * sliced lines from splitTextAtWidth; clearing would cause relayout of full text
1064
+ * and freeze/overlap.
1065
+ */
1066
+ const clearTextLinesForRelayout = (nodes) => nodes.map((child) => {
1067
+ if (isText$7(child) &&
1068
+ !child.props?.__preserveLines) {
1069
+ return Object.assign({}, child, { lines: undefined });
1070
+ }
1071
+ if (child.children) {
1072
+ return Object.assign({}, child, {
1073
+ children: clearTextLinesForRelayout(child.children),
1074
+ });
1075
+ }
1076
+ return child;
1077
+ });
1078
+ /**
1079
+ * Create column wrapper View nodes for multi-column layout.
1080
+ * Each column View has a fixed width so children (especially Text) get correct measurements.
1081
+ *
1082
+ * @param parent - The parent View with columns
1083
+ * @param colChildren - Array of child arrays, one per column
1084
+ * @param colWidth - Width of each column
1085
+ * @returns Array of View nodes, one per column
1086
+ */
1087
+ const FLEX_PROPS = ['flex', 'flexGrow', 'flexShrink', 'flexBasis'];
1088
+ const omitFlexFromStyle = (style = {}) => {
1089
+ const result = { ...style };
1090
+ FLEX_PROPS.forEach((prop) => delete result[prop]);
1091
+ return result;
1092
+ };
1093
+ const createColumnViews = (parent, colChildren, colWidth) => {
1094
+ return colChildren.map((children) => {
1095
+ const childrenWithClearedText = clearTextLinesForRelayout(children);
1096
+ const columnView = {
1097
+ type: P.View,
1098
+ props: {
1099
+ ...parent.props,
1100
+ wrap: true,
1101
+ columns: undefined,
1102
+ columnGap: undefined,
1103
+ },
1104
+ style: {
1105
+ ...omitFlexFromStyle(parent.style),
1106
+ width: colWidth,
1107
+ minWidth: colWidth,
1108
+ maxWidth: colWidth,
1109
+ flexDirection: 'column',
1110
+ flexShrink: 0,
1111
+ },
1112
+ box: undefined,
1113
+ children: childrenWithClearedText,
1114
+ };
1115
+ return columnView;
1116
+ });
1117
+ };
1118
+
1119
+ const isString = (value) => typeof value === 'string';
1120
+ const isNumber = (value) => typeof value === 'number';
1121
+ const isBoolean = (value) => typeof value === 'boolean';
1122
+ const isFragment = (value) => value && value.type === Symbol.for('react.fragment');
1123
+ /**
1124
+ * Transforms a react element instance to internal element format.
1125
+ *
1126
+ * Can return multiple instances in the case of arrays or fragments.
1127
+ *
1128
+ * @param element - React element
1129
+ * @returns Parsed React elements
1130
+ */
1131
+ const createInstances = (element) => {
1132
+ if (!element)
1133
+ return [];
1134
+ if (Array.isArray(element)) {
1135
+ return element.reduce((acc, el) => acc.concat(createInstances(el)), []);
1136
+ }
1137
+ if (isBoolean(element)) {
1138
+ return [];
1139
+ }
1140
+ if (isString(element) || isNumber(element)) {
1141
+ return [{ type: P.TextInstance, value: `${element}` }];
1142
+ }
1143
+ if (isFragment(element)) {
1144
+ // @ts-expect-error figure out why this is complains
1145
+ return createInstances(element.props.children);
1146
+ }
1147
+ if (!isString(element.type)) {
1148
+ // @ts-expect-error figure out why this is complains
1149
+ return createInstances(element.type(element.props));
1150
+ }
1151
+ const { type, props: { style = {}, children, ...props }, } = element;
1152
+ const nextChildren = castArray(children).reduce((acc, child) => acc.concat(createInstances(child)), []);
1153
+ return [
1154
+ {
1155
+ type,
1156
+ style,
1157
+ props,
1158
+ children: nextChildren,
1159
+ },
1160
+ ];
1161
+ };
1162
+
1163
+ const NON_WRAP_TYPES = [P.Svg, P.Note, P.Image, P.Canvas];
1164
+ const getWrap = (node) => {
1165
+ if (NON_WRAP_TYPES.includes(node.type))
1166
+ return false;
1167
+ if (!node.props)
1168
+ return true;
1169
+ return 'wrap' in node.props ? node.props.wrap : true;
1170
+ };
1171
+
1172
+ const isFixed = (node) => {
1173
+ if (!node.props)
1174
+ return false;
1175
+ return 'fixed' in node.props ? node.props.fixed === true : false;
1176
+ };
1177
+
1178
+ const getBreak$1 = (node) => 'break' in node.props ? node.props.break : false;
1179
+ const getMinPresenceAhead = (node) => 'minPresenceAhead' in node.props ? node.props.minPresenceAhead : 0;
1180
+ const getFurthestEnd = (elements) => Math.max(...elements.map((node) => node.box.top + node.box.height));
1181
+ const getEndOfMinPresenceAhead = (child) => {
1182
+ return (child.box.top +
1183
+ child.box.height +
1184
+ child.box.marginBottom +
1185
+ getMinPresenceAhead(child));
1186
+ };
1187
+ const getEndOfPresence = (child, futureElements) => {
1188
+ const afterMinPresenceAhead = getEndOfMinPresenceAhead(child);
1189
+ const endOfFurthestFutureElement = getFurthestEnd(futureElements.filter((node) => !('fixed' in node.props)));
1190
+ return Math.min(afterMinPresenceAhead, endOfFurthestFutureElement);
1191
+ };
1192
+ const shouldBreak = (child, futureElements, height, previousElements) => {
1193
+ if ('fixed' in child.props)
1194
+ return false;
1195
+ const shouldSplit = height < child.box.top + child.box.height;
1196
+ const canWrap = getWrap(child);
1197
+ // Calculate the y coordinate where the desired presence of the child ends
1198
+ const endOfPresence = getEndOfPresence(child, futureElements);
1199
+ // If the child is already at the top of the page, breaking won't improve its presence
1200
+ // (as long as react-pdf does not support breaking into differently sized containers)
1201
+ const breakingImprovesPresence = previousElements.filter((node) => !isFixed(node)).length > 0;
1202
+ return (getBreak$1(child) ||
1203
+ (shouldSplit && !canWrap) ||
1204
+ (!shouldSplit && endOfPresence > height && breakingImprovesPresence));
1205
+ };
1206
+
1207
+ const getTop$1 = (node) => node.box?.top || 0;
1208
+ const hasFixedHeight = (node) => !isNil(node.style?.height);
1209
+ const splitNode = (node, height) => {
1210
+ if (!node)
1211
+ return [null, null];
1212
+ const nodeTop = getTop$1(node);
1213
+ const current = Object.assign({}, node, {
1214
+ box: {
1215
+ ...node.box,
1216
+ borderBottomWidth: 0,
1217
+ },
1218
+ style: {
1219
+ ...node.style,
1220
+ marginBottom: 0,
1221
+ paddingBottom: 0,
1222
+ borderBottomWidth: 0,
1223
+ borderBottomLeftRadius: 0,
1224
+ borderBottomRightRadius: 0,
1225
+ },
1226
+ });
1227
+ current.style.height = height - nodeTop;
1228
+ const nextHeight = hasFixedHeight(node)
1229
+ ? node.box.height - (height - nodeTop)
1230
+ : null;
1231
+ const next = Object.assign({}, node, {
1232
+ box: {
1233
+ ...node.box,
1234
+ top: 0,
1235
+ borderTopWidth: 0,
1236
+ },
1237
+ style: {
1238
+ ...node.style,
1239
+ marginTop: 0,
1240
+ paddingTop: 0,
1241
+ borderTopWidth: 0,
1242
+ borderTopLeftRadius: 0,
1243
+ borderTopRightRadius: 0,
1244
+ },
1245
+ props: {
1246
+ ...node.props,
1247
+ bookmark: null,
1248
+ },
1249
+ });
1250
+ if (nextHeight) {
1251
+ next.style.height = nextHeight;
1252
+ }
1253
+ return [current, next];
1254
+ };
1255
+
1256
+ const getComputedPadding = (node, edge) => {
1257
+ const { yogaNode } = node;
1258
+ return yogaNode ? yogaNode.getComputedPadding(edge) : null;
1259
+ };
1260
+ /**
1261
+ * Get Yoga computed paddings. Zero otherwise
1262
+ *
1263
+ * @param node
1264
+ * @returns paddings
1265
+ */
1266
+ const getPadding = (node) => {
1267
+ const { style, box } = node;
1268
+ const paddingTop = getComputedPadding(node, Yoga.Edge.Top) ||
1269
+ box?.paddingTop ||
1270
+ style?.paddingTop ||
1271
+ 0;
1272
+ const paddingRight = getComputedPadding(node, Yoga.Edge.Right) ||
1273
+ box?.paddingRight ||
1274
+ style?.paddingRight ||
1275
+ 0;
1276
+ const paddingBottom = getComputedPadding(node, Yoga.Edge.Bottom) ||
1277
+ box?.paddingBottom ||
1278
+ style?.paddingBottom ||
1279
+ 0;
1280
+ const paddingLeft = getComputedPadding(node, Yoga.Edge.Left) ||
1281
+ box?.paddingLeft ||
1282
+ style?.paddingLeft ||
1283
+ 0;
1284
+ return { paddingTop, paddingRight, paddingBottom, paddingLeft };
1285
+ };
1286
+
1287
+ const getContentArea = (page) => {
1288
+ const height = page.style?.height;
1289
+ const { paddingTop, paddingBottom } = getPadding(page);
1290
+ return height - paddingBottom - paddingTop;
1291
+ };
1292
+
1293
+ /** A4 width in points - fallback when page dimensions unknown */
1294
+ const DEFAULT_PAGE_WIDTH = 595.28;
1295
+ /**
1296
+ * Get the content width of a page (width minus horizontal padding).
1297
+ * Uses getSize when box/style width is not yet resolved (e.g. before Yoga layout).
1298
+ */
1299
+ const getContentWidth = (page) => {
1300
+ const size = getSize(page);
1301
+ const width = page.style?.width ?? size.width ?? DEFAULT_PAGE_WIDTH;
1302
+ const { paddingLeft, paddingRight } = getPadding(page);
1303
+ return Math.max(0, width - paddingLeft - paddingRight);
1304
+ };
1305
+
1306
+ const getWrapArea = (page) => {
1307
+ const height = page.style?.height;
1308
+ const { paddingBottom } = getPadding(page);
1309
+ return height - paddingBottom;
1310
+ };
1311
+
1312
+ /**
1313
+ * Get line index at given height
1314
+ *
1315
+ * @param node
1316
+ * @param height
1317
+ */
1318
+ const lineIndexAtHeight = (node, height) => {
1319
+ let y = 0;
1320
+ if (!node.lines)
1321
+ return 0;
1322
+ for (let i = 0; i < node.lines.length; i += 1) {
1323
+ const line = node.lines[i];
1324
+ if (y + line.box.height > height)
1325
+ return i;
1326
+ y += line.box.height;
1327
+ }
1328
+ return node.lines.length;
1329
+ };
1330
+
1331
+ /**
1332
+ * Get height for given text line index
1333
+ *
1334
+ * @param node
1335
+ * @param index
1336
+ */
1337
+ const heightAtLineIndex = (node, index) => {
1338
+ let counter = 0;
1339
+ if (!node.lines)
1340
+ return counter;
1341
+ for (let i = 0; i < index; i += 1) {
1342
+ const line = node.lines[i];
1343
+ if (!line)
1344
+ break;
1345
+ counter += line.box.height;
1346
+ }
1347
+ return counter;
1348
+ };
1349
+
1350
+ const getLineBreak = (node, height) => {
1351
+ const top = node.box?.top || 0;
1352
+ const widows = node.props.widows || 2;
1353
+ const orphans = node.props.orphans || 2;
1354
+ const linesQuantity = node.lines.length;
1355
+ const slicedLine = lineIndexAtHeight(node, height - top);
1356
+ if (slicedLine === 0) {
1357
+ return 0;
1358
+ }
1359
+ if (linesQuantity < orphans) {
1360
+ return linesQuantity;
1361
+ }
1362
+ if (slicedLine < orphans || linesQuantity < orphans + widows) {
1363
+ return 0;
1364
+ }
1365
+ if (linesQuantity === orphans + widows) {
1366
+ return orphans;
1367
+ }
1368
+ if (linesQuantity - slicedLine < widows) {
1369
+ return linesQuantity - widows;
1370
+ }
1371
+ return slicedLine;
1372
+ };
1373
+ // Also receives contentArea in case it's needed
1374
+ const splitText = (node, height) => {
1375
+ const slicedLineIndex = getLineBreak(node, height);
1376
+ const currentHeight = heightAtLineIndex(node, slicedLineIndex);
1377
+ const nextHeight = node.box.height - currentHeight;
1378
+ const current = Object.assign({}, node, {
1379
+ box: {
1380
+ ...node.box,
1381
+ height: currentHeight,
1382
+ borderBottomWidth: 0,
1383
+ },
1384
+ style: {
1385
+ ...node.style,
1386
+ marginBottom: 0,
1387
+ paddingBottom: 0,
1388
+ borderBottomWidth: 0,
1389
+ borderBottomLeftRadius: 0,
1390
+ borderBottomRightRadius: 0,
1391
+ },
1392
+ lines: node.lines.slice(0, slicedLineIndex),
1393
+ });
1394
+ const next = Object.assign({}, node, {
1395
+ box: {
1396
+ ...node.box,
1397
+ top: 0,
1398
+ height: nextHeight,
1399
+ borderTopWidth: 0,
1400
+ },
1401
+ style: {
1402
+ ...node.style,
1403
+ marginTop: 0,
1404
+ paddingTop: 0,
1405
+ borderTopWidth: 0,
1406
+ borderTopLeftRadius: 0,
1407
+ borderTopRightRadius: 0,
1408
+ },
1409
+ lines: node.lines.slice(slicedLineIndex),
1410
+ });
1411
+ return [current, next];
1412
+ };
1413
+
1414
+ const IGNORABLE_CODEPOINTS = [
1415
+ 8232, // LINE_SEPARATOR
1416
+ 8233, // PARAGRAPH_SEPARATOR
1417
+ ];
1418
+ const buildSubsetForFont = (font) => IGNORABLE_CODEPOINTS.reduce((acc, codePoint) => {
1419
+ if (font &&
1420
+ font.hasGlyphForCodePoint &&
1421
+ font.hasGlyphForCodePoint(codePoint)) {
1422
+ return acc;
1423
+ }
1424
+ return [...acc, String.fromCharCode(codePoint)];
1425
+ }, []);
1426
+ const ignoreChars = (fragments) => fragments.map((fragment) => {
1427
+ const charSubset = buildSubsetForFont(fragment.attributes.font[0]);
1428
+ const subsetRegex = new RegExp(charSubset.join('|'));
1429
+ return {
1430
+ string: fragment.string.replace(subsetRegex, ''),
1431
+ attributes: fragment.attributes,
1432
+ };
1433
+ });
1434
+
1435
+ const PREPROCESSORS = [ignoreChars, embedEmojis];
1436
+ const isImage$1 = (node) => node.type === P.Image;
1437
+ const isTextInstance$2 = (node) => node.type === P.TextInstance;
1438
+ /**
1439
+ * Get textkit fragments of given node object
1440
+ *
1441
+ * @param fontStore - Font store
1442
+ * @param instance - Node
1443
+ * @param parentLink - Parent link
1444
+ * @param level - Fragment level
1445
+ * @returns Text fragments
1446
+ */
1447
+ const getFragments = (fontStore, instance, parentLink = null, level = 0) => {
1448
+ if (!instance)
1449
+ return [{ string: '' }];
1450
+ let fragments = [];
1451
+ const { color = 'black', direction = 'ltr', fontFamily = 'Helvetica', fontWeight, fontStyle, fontSize = 18, textAlign, lineHeight, textDecoration, textDecorationColor, textDecorationStyle, textTransform, letterSpacing, textIndent, opacity, verticalAlign, } = instance.style;
1452
+ const fontFamilies = typeof fontFamily === 'string' ? [fontFamily] : [...(fontFamily || [])];
1453
+ // Fallback font
1454
+ fontFamilies.push('Helvetica');
1455
+ const font = fontFamilies.map((fontFamilyName) => {
1456
+ const opts = { fontFamily: fontFamilyName, fontWeight, fontStyle };
1457
+ const obj = fontStore.getFont(opts);
1458
+ return obj?.data;
1459
+ });
1460
+ // Don't pass main background color to textkit. Will be rendered by the render package instead
1461
+ const backgroundColor = level === 0 ? null : instance.style.backgroundColor;
1462
+ // Resolve unitless lineHeight to points (textkit expects absolute values)
1463
+ const resolvedLineHeight = typeof lineHeight === 'number' && lineHeight > 0 && lineHeight <= 5
1464
+ ? fontSize * lineHeight
1465
+ : lineHeight;
1466
+ const attributes = {
1467
+ font,
1468
+ color,
1469
+ opacity,
1470
+ fontSize,
1471
+ lineHeight: resolvedLineHeight,
1472
+ direction,
1473
+ verticalAlign,
1474
+ backgroundColor,
1475
+ indent: textIndent,
1476
+ characterSpacing: letterSpacing,
1477
+ strikeStyle: textDecorationStyle,
1478
+ underlineStyle: textDecorationStyle,
1479
+ underline: textDecoration === 'underline' ||
1480
+ textDecoration === 'underline line-through' ||
1481
+ textDecoration === 'line-through underline',
1482
+ strike: textDecoration === 'line-through' ||
1483
+ textDecoration === 'underline line-through' ||
1484
+ textDecoration === 'line-through underline',
1485
+ strikeColor: textDecorationColor || color,
1486
+ underlineColor: textDecorationColor || color,
1487
+ // @ts-expect-error allow this props access
1488
+ link: parentLink || instance.props?.src || instance.props?.href,
1489
+ align: textAlign || (direction === 'rtl' ? 'right' : 'left'),
1490
+ };
1491
+ for (let i = 0; i < instance.children.length; i += 1) {
1492
+ const child = instance.children[i];
1493
+ if (isImage$1(child)) {
1494
+ fragments.push({
1495
+ string: String.fromCharCode(0xfffc),
1496
+ attributes: {
1497
+ ...attributes,
1498
+ attachment: {
1499
+ width: (child.style.width || fontSize),
1500
+ height: (child.style.height || fontSize),
1501
+ image: child.image.data,
1502
+ },
1503
+ },
1504
+ });
1505
+ }
1506
+ else if (isTextInstance$2(child)) {
1507
+ fragments.push({
1508
+ string: transformText(child.value, textTransform),
1509
+ attributes,
1510
+ });
1511
+ }
1512
+ else if (child) {
1513
+ fragments.push(...getFragments(fontStore, child, attributes.link, level + 1));
1514
+ }
1515
+ }
1516
+ for (let i = 0; i < PREPROCESSORS.length; i += 1) {
1517
+ const preprocessor = PREPROCESSORS[i];
1518
+ fragments = preprocessor(fragments);
1519
+ }
1520
+ return fragments;
1521
+ };
1522
+ /**
1523
+ * Get textkit attributed string from text node
1524
+ *
1525
+ * @param fontStore - Font store
1526
+ * @param instance Node
1527
+ * @returns Attributed string
1528
+ */
1529
+ const getAttributedString = (fontStore, instance) => {
1530
+ const fragments = getFragments(fontStore, instance);
1531
+ return fromFragments(fragments);
1532
+ };
1533
+
1534
+ const engines = {
1535
+ bidi,
1536
+ linebreaker,
1537
+ justification,
1538
+ textDecoration,
1539
+ scriptItemizer,
1540
+ wordHyphenation,
1541
+ fontSubstitution,
1542
+ };
1543
+ const engine = layoutEngine(engines);
1544
+ const getMaxLines = (node) => node.style?.maxLines;
1545
+ const getTextOverflow = (node) => node.style?.textOverflow;
1546
+ /**
1547
+ * Get layout container for specific text node
1548
+ *
1549
+ * @param {number} width
1550
+ * @param {number} height
1551
+ * @param {Object} node
1552
+ * @returns {Object} layout container
1553
+ */
1554
+ const getContainer = (width, height, node) => {
1555
+ const maxLines = getMaxLines(node);
1556
+ const textOverflow = getTextOverflow(node);
1557
+ const columns = node.props?.columns ?? 1;
1558
+ const columnGap = node.props?.columnGap ?? 18;
1559
+ const container = {
1560
+ x: 0,
1561
+ y: 0,
1562
+ width,
1563
+ maxLines,
1564
+ height: height || Infinity,
1565
+ truncateMode: textOverflow,
1566
+ };
1567
+ if (columns > 1) {
1568
+ const availableHeight = height || Infinity;
1569
+ return {
1570
+ ...container,
1571
+ columns,
1572
+ columnGap,
1573
+ effectiveHeight: availableHeight !== Infinity ? availableHeight * columns : undefined,
1574
+ };
1575
+ }
1576
+ return container;
1577
+ };
1578
+ /**
1579
+ * Get text layout options for specific text node
1580
+ *
1581
+ * @param {Object} node instance
1582
+ * @returns {Object} layout options
1583
+ */
1584
+ const getLayoutOptions = (fontStore, node) => ({
1585
+ hyphenationPenalty: node.props.hyphenationPenalty,
1586
+ shrinkWhitespaceFactor: { before: -0.5, after: -0.5 },
1587
+ hyphenationCallback: node.props.hyphenationCallback ||
1588
+ fontStore?.getHyphenationCallback() ||
1589
+ null,
1590
+ });
1591
+ /**
1592
+ * Get text lines for given node
1593
+ *
1594
+ * @param node - Node
1595
+ * @param width - Container width
1596
+ * @param height - Container height
1597
+ * @param fontStore - Font store
1598
+ * @returns Layout lines
1599
+ */
1600
+ const layoutText = (node, width, height, fontStore) => {
1601
+ const attributedString = getAttributedString(fontStore, node);
1602
+ const container = getContainer(width, height, node);
1603
+ const options = getLayoutOptions(fontStore, node);
1604
+ const lines = engine(attributedString, container, options);
1605
+ return lines.reduce((acc, line) => [...acc, ...line], []);
1606
+ };
1607
+
1608
+ /**
1609
+ * Get line break index for given lines and height, with orphan/widow handling.
1610
+ * Mirrors getLineBreak from splitText but operates on pre-laid-out lines.
1611
+ */
1612
+ const getLineBreakForLines = (lines, height, orphans, widows) => {
1613
+ if (!lines?.length)
1614
+ return 0;
1615
+ let y = 0;
1616
+ let slicedLine = lines.length;
1617
+ for (let i = 0; i < lines.length; i += 1) {
1618
+ const line = lines[i];
1619
+ const lineHeight = line?.box?.height ?? 0;
1620
+ if (lineHeight <= 0)
1621
+ continue;
1622
+ if (y + lineHeight > height) {
1623
+ slicedLine = i;
1624
+ break;
1625
+ }
1626
+ y += lineHeight;
1627
+ }
1628
+ const linesQuantity = lines.length;
1629
+ if (slicedLine === 0)
1630
+ return 0;
1631
+ if (linesQuantity < orphans)
1632
+ return linesQuantity;
1633
+ // Relax orphans: when strict rules would return 0 but at least one line fits,
1634
+ // allow minimal split to avoid moving whole block to next column
1635
+ if (slicedLine < orphans || linesQuantity < orphans + widows) {
1636
+ if (slicedLine >= 1)
1637
+ return 1;
1638
+ return 0;
1639
+ }
1640
+ if (linesQuantity === orphans + widows)
1641
+ return orphans;
1642
+ if (linesQuantity - slicedLine < widows)
1643
+ return linesQuantity - widows;
1644
+ return slicedLine;
1645
+ };
1646
+ /**
1647
+ * Sum height of lines from start to index (exclusive).
1648
+ */
1649
+ const heightAtLineIndexForLines = (lines, index) => {
1650
+ let counter = 0;
1651
+ for (let i = 0; i < index && i < lines.length; i += 1) {
1652
+ counter += lines[i]?.box?.height ?? 0;
1653
+ }
1654
+ return counter;
1655
+ };
1656
+ /**
1657
+ * Sum total height of all lines.
1658
+ */
1659
+ const totalLinesHeight = (lines) => (lines || []).reduce((acc, line) => acc + (line?.box?.height ?? 0), 0);
1660
+ /**
1661
+ * Split text at the given width, using layout at that width for accurate line breaking.
1662
+ * Used for column distribution where text is laid out at column width, not full page width.
1663
+ *
1664
+ * @param node - Text node to split
1665
+ * @param colWidth - Width constraint (column width)
1666
+ * @param height - Available vertical space (remaining in column)
1667
+ * @param fontStore - Font store for text layout
1668
+ * @returns [currentPart, nextPart] - same structure as splitText
1669
+ */
1670
+ const splitTextAtWidth = (node, colWidth, height, fontStore) => {
1671
+ const preserveLines = node.props
1672
+ ?.__preserveLines;
1673
+ const existingLines = node.lines;
1674
+ let lines;
1675
+ if (preserveLines && existingLines?.length) {
1676
+ lines = existingLines;
1677
+ }
1678
+ else {
1679
+ let rawLines;
1680
+ try {
1681
+ rawLines = layoutText(node, colWidth, Infinity, fontStore);
1682
+ }
1683
+ catch {
1684
+ const empty = Object.assign({}, node, {
1685
+ box: { ...node.box, height: 0 },
1686
+ lines: [],
1687
+ });
1688
+ return [empty, empty];
1689
+ }
1690
+ lines = Array.isArray(rawLines) ? rawLines : [];
1691
+ }
1692
+ if (!lines.length) {
1693
+ const empty = Object.assign({}, node, {
1694
+ box: { ...node.box, height: 0 },
1695
+ lines: [],
1696
+ });
1697
+ return [empty, empty];
1698
+ }
1699
+ const widows = node.props?.widows ?? 2;
1700
+ const orphans = node.props?.orphans ?? 2;
1701
+ let slicedLineIndex = getLineBreakForLines(lines, height, orphans, widows);
1702
+ // Force minimal split when first line doesn't fit - prevents infinite pagination
1703
+ if (slicedLineIndex === 0 && lines.length > 0) {
1704
+ slicedLineIndex = 1;
1705
+ }
1706
+ const currentHeight = heightAtLineIndexForLines(lines, slicedLineIndex);
1707
+ const totalHeight = totalLinesHeight(lines);
1708
+ const nextHeight = totalHeight - currentHeight;
1709
+ const current = Object.assign({}, node, {
1710
+ box: {
1711
+ ...node.box,
1712
+ height: currentHeight,
1713
+ borderBottomWidth: 0,
1714
+ },
1715
+ style: {
1716
+ ...node.style,
1717
+ marginBottom: 0,
1718
+ paddingBottom: 0,
1719
+ borderBottomWidth: 0,
1720
+ borderBottomLeftRadius: 0,
1721
+ borderBottomRightRadius: 0,
1722
+ },
1723
+ lines: lines.slice(0, slicedLineIndex),
1724
+ props: {
1725
+ ...node.props,
1726
+ __preserveLines: true,
1727
+ },
1728
+ });
1729
+ const next = Object.assign({}, node, {
1730
+ box: {
1731
+ ...node.box,
1732
+ top: 0,
1733
+ height: nextHeight,
1734
+ borderTopWidth: 0,
1735
+ },
1736
+ style: {
1737
+ ...node.style,
1738
+ marginTop: 0,
1739
+ paddingTop: 0,
1740
+ borderTopWidth: 0,
1741
+ borderTopLeftRadius: 0,
1742
+ borderTopRightRadius: 0,
1743
+ },
1744
+ lines: lines.slice(slicedLineIndex),
1745
+ props: {
1746
+ ...node.props,
1747
+ __preserveLines: true,
1748
+ },
1749
+ });
1750
+ return [current, next];
1751
+ };
1752
+
1753
+ const getComputedMargin = (node, edge) => {
1754
+ const { yogaNode } = node;
1755
+ return yogaNode ? yogaNode.getComputedMargin(edge) : null;
1756
+ };
1757
+ /**
1758
+ * Get Yoga computed magins. Zero otherwise
1759
+ *
1760
+ * @param node
1761
+ * @returns Margins
1762
+ */
1763
+ const getMargin = (node) => {
1764
+ const { style, box } = node;
1765
+ const marginTop = getComputedMargin(node, Yoga.Edge.Top) ||
1766
+ box?.marginTop ||
1767
+ style?.marginTop ||
1768
+ 0;
1769
+ const marginRight = getComputedMargin(node, Yoga.Edge.Right) ||
1770
+ box?.marginRight ||
1771
+ style?.marginRight ||
1772
+ 0;
1773
+ const marginBottom = getComputedMargin(node, Yoga.Edge.Bottom) ||
1774
+ box?.marginBottom ||
1775
+ style?.marginBottom ||
1776
+ 0;
1777
+ const marginLeft = getComputedMargin(node, Yoga.Edge.Left) ||
1778
+ box?.marginLeft ||
1779
+ style?.marginLeft ||
1780
+ 0;
1781
+ return { marginTop, marginRight, marginBottom, marginLeft };
1782
+ };
1783
+
1784
+ /**
1785
+ * Get Yoga computed position. Zero otherwise
1786
+ *
1787
+ * @param node
1788
+ * @returns Position
1789
+ */
1790
+ const getPosition = (node) => {
1791
+ const { yogaNode } = node;
1792
+ return {
1793
+ top: yogaNode?.getComputedTop() || 0,
1794
+ right: yogaNode?.getComputedRight() || 0,
1795
+ bottom: yogaNode?.getComputedBottom() || 0,
1796
+ left: yogaNode?.getComputedLeft() || 0,
1797
+ };
1798
+ };
1799
+
1800
+ const DEFAULT_DIMENSION = {
1801
+ width: 0,
1802
+ height: 0,
1803
+ };
1804
+ /**
1805
+ * Get Yoga computed dimensions. Zero otherwise
1806
+ *
1807
+ * @param node
1808
+ * @returns Dimensions
1809
+ */
1810
+ const getDimension = (node) => {
1811
+ const { yogaNode } = node;
1812
+ if (!yogaNode)
1813
+ return DEFAULT_DIMENSION;
1814
+ return {
1815
+ width: yogaNode.getComputedWidth(),
1816
+ height: yogaNode.getComputedHeight(),
1817
+ };
1818
+ };
1819
+
1820
+ const getComputedBorder = (yogaNode, edge) => (yogaNode ? yogaNode.getComputedBorder(edge) : 0);
1821
+ /**
1822
+ * Get Yoga computed border width. Zero otherwise
1823
+ *
1824
+ * @param node
1825
+ * @returns Border widths
1826
+ */
1827
+ const getBorderWidth = (node) => {
1828
+ const { yogaNode } = node;
1829
+ return {
1830
+ borderTopWidth: getComputedBorder(yogaNode, Yoga.Edge.Top),
1831
+ borderRightWidth: getComputedBorder(yogaNode, Yoga.Edge.Right),
1832
+ borderBottomWidth: getComputedBorder(yogaNode, Yoga.Edge.Bottom),
1833
+ borderLeftWidth: getComputedBorder(yogaNode, Yoga.Edge.Left),
1834
+ };
1835
+ };
1836
+
1837
+ /**
1838
+ * Set display attribute to node's Yoga instance
1839
+ *
1840
+ * @param value - Display
1841
+ * @returns Node instance wrapper
1842
+ */
1843
+ const setDisplay = (value) => (node) => {
1844
+ const { yogaNode } = node;
1845
+ if (yogaNode) {
1846
+ yogaNode.setDisplay(value === 'none' ? Yoga.Display.None : Yoga.Display.Flex);
1847
+ }
1848
+ return node;
1849
+ };
1850
+
1851
+ const OVERFLOW = {
1852
+ hidden: Yoga.Overflow.Hidden,
1853
+ scroll: Yoga.Overflow.Scroll,
1854
+ };
1855
+ /**
1856
+ * Set overflow attribute to node's Yoga instance
1857
+ *
1858
+ * @param value - Overflow value
1859
+ * @returns Node instance wrapper
1860
+ */
1861
+ const setOverflow = (value) => (node) => {
1862
+ const { yogaNode } = node;
1863
+ if (!isNil(value) && yogaNode) {
1864
+ const overflow = OVERFLOW[value] || Yoga.Overflow.Visible;
1865
+ yogaNode.setOverflow(overflow);
1866
+ }
1867
+ return node;
1868
+ };
1869
+
1870
+ const FLEX_WRAP = {
1871
+ wrap: Yoga.Wrap.Wrap,
1872
+ 'wrap-reverse': Yoga.Wrap.WrapReverse,
1873
+ };
1874
+ /**
1875
+ * Set flex wrap attribute to node's Yoga instance
1876
+ *
1877
+ * @param value - Flex wrap value
1878
+ * @returns Node instance wrapper
1879
+ */
1880
+ const setFlexWrap = (value) => (node) => {
1881
+ const { yogaNode } = node;
1882
+ if (yogaNode) {
1883
+ const flexWrap = FLEX_WRAP[value] || Yoga.Wrap.NoWrap;
1884
+ yogaNode.setFlexWrap(flexWrap);
1885
+ }
1886
+ return node;
1887
+ };
1888
+
1889
+ /**
1890
+ * Set generic yoga attribute to node's Yoga instance, handing `auto`, edges and percentage cases
1891
+ *
1892
+ * @param attr - Property
1893
+ * @param edge - Edge
1894
+ * @returns Node instance wrapper
1895
+ */
1896
+ const setYogaValue = (attr, edge) => (value) => (node) => {
1897
+ const { yogaNode } = node;
1898
+ if (!isNil(value) && yogaNode) {
1899
+ const hasEdge = !isNil(edge);
1900
+ const fixedMethod = `set${upperFirst(attr)}`;
1901
+ const autoMethod = `${fixedMethod}Auto`;
1902
+ const percentMethod = `${fixedMethod}Percent`;
1903
+ const percent = matchPercent(value);
1904
+ if (percent && !yogaNode[percentMethod]) {
1905
+ throw new Error(`You can't pass percentage values to ${attr} property`);
1906
+ }
1907
+ if (percent) {
1908
+ if (hasEdge) {
1909
+ yogaNode[percentMethod]?.(edge, percent.value);
1910
+ }
1911
+ else {
1912
+ yogaNode[percentMethod]?.(percent.value);
1913
+ }
1914
+ }
1915
+ else if (value === 'auto') {
1916
+ if (hasEdge) {
1917
+ yogaNode[autoMethod]?.(edge);
1918
+ }
1919
+ else {
1920
+ yogaNode[autoMethod]?.();
1921
+ }
1922
+ }
1923
+ else if (hasEdge) {
1924
+ yogaNode[fixedMethod]?.(edge, value);
1925
+ }
1926
+ else {
1927
+ yogaNode[fixedMethod]?.(value);
1928
+ }
1929
+ }
1930
+ return node;
1931
+ };
1932
+
1933
+ /**
1934
+ * Set flex grow attribute to node's Yoga instance
1935
+ *
1936
+ * @param value - Flex grow value
1937
+ * @returns Node instance wrapper
1938
+ */
1939
+ const setFlexGrow = (value) => (node) => {
1940
+ return setYogaValue('flexGrow')(value || 0)(node);
1941
+ };
1942
+
1943
+ /**
1944
+ * Set flex basis attribute to node's Yoga instance
1945
+ *
1946
+ * @param flex - Basis value
1947
+ * @param node - Node instance
1948
+ * @returns Node instance
1949
+ */
1950
+ const setFlexBasis = setYogaValue('flexBasis');
1951
+
1952
+ const ALIGN = {
1953
+ 'flex-start': Yoga.Align.FlexStart,
1954
+ center: Yoga.Align.Center,
1955
+ 'flex-end': Yoga.Align.FlexEnd,
1956
+ stretch: Yoga.Align.Stretch,
1957
+ baseline: Yoga.Align.Baseline,
1958
+ 'space-between': Yoga.Align.SpaceBetween,
1959
+ 'space-around': Yoga.Align.SpaceAround,
1960
+ 'space-evenly': Yoga.Align.SpaceEvenly,
1961
+ };
1962
+ /**
1963
+ * Set generic align attribute to node's Yoga instance
1964
+ *
1965
+ * @param attr - Specific align property
1966
+ * @param value - Specific align value
1967
+ * @param node - Node
1968
+ * @returns Node
1969
+ */
1970
+ const setAlign = (attr) => (value) => (node) => {
1971
+ const { yogaNode } = node;
1972
+ const defaultValue = attr === 'items' ? Yoga.Align.Stretch : Yoga.Align.Auto;
1973
+ if (yogaNode) {
1974
+ const align = ALIGN[value] || defaultValue;
1975
+ yogaNode[`setAlign${upperFirst(attr)}`](align);
1976
+ }
1977
+ return node;
1978
+ };
1979
+
1980
+ /**
1981
+ * Set align self attribute to node's Yoga instance
1982
+ *
1983
+ * @param align - Value
1984
+ * @param node - Node instance
1985
+ * @returns Node instance
1986
+ */
1987
+ const setAlignSelf = setAlign('self');
1988
+
1989
+ /**
1990
+ * Set align items attribute to node's Yoga instance
1991
+ *
1992
+ * @param align - Value
1993
+ * @param node - Node instance
1994
+ * @returns Node instance
1995
+ */
1996
+ const setAlignItems = setAlign('items');
1997
+
1998
+ /**
1999
+ * Set flex shrink attribute to node's Yoga instance
2000
+ *
2001
+ * @param value - Flex shrink value
2002
+ * @returns Node instance wrapper
2003
+ */
2004
+ const setFlexShrink = (value) => (node) => {
2005
+ return setYogaValue('flexShrink')(value || 1)(node);
2006
+ };
2007
+
2008
+ /**
2009
+ * Set aspect ratio attribute to node's Yoga instance
2010
+ *
2011
+ * @param value - Ratio
2012
+ * @returns Node instance
2013
+ */
2014
+ const setAspectRatio = (value) => (node) => {
2015
+ const { yogaNode } = node;
2016
+ if (!isNil(value) && yogaNode) {
2017
+ yogaNode.setAspectRatio(value);
2018
+ }
2019
+ return node;
2020
+ };
2021
+
2022
+ /**
2023
+ * Set align content attribute to node's Yoga instance
2024
+ *
2025
+ * @param align - Value
2026
+ * @param node - Instance
2027
+ * @returns Node instance
2028
+ */
2029
+ const setAlignContent = setAlign('content');
2030
+
2031
+ const POSITION = {
2032
+ absolute: Yoga.PositionType.Absolute,
2033
+ relative: Yoga.PositionType.Relative,
2034
+ static: Yoga.PositionType.Static,
2035
+ };
2036
+ /**
2037
+ * Set position type attribute to node's Yoga instance
2038
+ *
2039
+ * @param value - Position position type
2040
+ * @returns Node instance
2041
+ */
2042
+ const setPositionType = (value) => (node) => {
2043
+ const { yogaNode } = node;
2044
+ if (!isNil(value) && yogaNode) {
2045
+ yogaNode.setPositionType(POSITION[value]);
2046
+ }
2047
+ return node;
2048
+ };
2049
+
2050
+ const FLEX_DIRECTIONS = {
2051
+ row: Yoga.FlexDirection.Row,
2052
+ 'row-reverse': Yoga.FlexDirection.RowReverse,
2053
+ 'column-reverse': Yoga.FlexDirection.ColumnReverse,
2054
+ };
2055
+ /**
2056
+ * Set flex direction attribute to node's Yoga instance
2057
+ *
2058
+ * @param value - Flex direction value
2059
+ * @returns Node instance wrapper
2060
+ */
2061
+ const setFlexDirection = (value) => (node) => {
2062
+ const { yogaNode } = node;
2063
+ if (yogaNode) {
2064
+ const flexDirection = FLEX_DIRECTIONS[value] || Yoga.FlexDirection.Column;
2065
+ yogaNode.setFlexDirection(flexDirection);
2066
+ }
2067
+ return node;
2068
+ };
2069
+
2070
+ const JUSTIFY_CONTENT = {
2071
+ center: Yoga.Justify.Center,
2072
+ 'flex-end': Yoga.Justify.FlexEnd,
2073
+ 'space-between': Yoga.Justify.SpaceBetween,
2074
+ 'space-around': Yoga.Justify.SpaceAround,
2075
+ 'space-evenly': Yoga.Justify.SpaceEvenly,
2076
+ };
2077
+ /**
2078
+ * Set justify content attribute to node's Yoga instance
2079
+ *
2080
+ * @param value - Justify content value
2081
+ * @returns Node instance wrapper
2082
+ */
2083
+ const setJustifyContent = (value) => (node) => {
2084
+ const { yogaNode } = node;
2085
+ if (!isNil(value) && yogaNode) {
2086
+ const justifyContent = JUSTIFY_CONTENT[value] || Yoga.Justify.FlexStart;
2087
+ yogaNode.setJustifyContent(justifyContent);
2088
+ }
2089
+ return node;
2090
+ };
2091
+
2092
+ /**
2093
+ * Set margin top attribute to node's Yoga instance
2094
+ *
2095
+ * @param margin - Margin top
2096
+ * @param node - Node instance
2097
+ * @returns Node instance
2098
+ */
2099
+ const setMarginTop = setYogaValue('margin', Yoga.Edge.Top);
2100
+ /**
2101
+ * Set margin right attribute to node's Yoga instance
2102
+ *
2103
+ * @param margin - Margin right
2104
+ * @param node - Node instance
2105
+ * @returns Node instance
2106
+ */
2107
+ const setMarginRight = setYogaValue('margin', Yoga.Edge.Right);
2108
+ /**
2109
+ * Set margin bottom attribute to node's Yoga instance
2110
+ *
2111
+ * @param margin - Margin bottom
2112
+ * @param node - Node instance
2113
+ * @returns Node instance
2114
+ */
2115
+ const setMarginBottom = setYogaValue('margin', Yoga.Edge.Bottom);
2116
+ /**
2117
+ * Set margin left attribute to node's Yoga instance
2118
+ *
2119
+ * @param margin - Margin left
2120
+ * @param node - Node instance
2121
+ * @returns Node instance
2122
+ */
2123
+ const setMarginLeft = setYogaValue('margin', Yoga.Edge.Left);
2124
+
2125
+ /**
2126
+ * Set padding top attribute to node's Yoga instance
2127
+ *
2128
+ * @param padding - Padding top
2129
+ * @param node - Node instance
2130
+ * @returns Node instance
2131
+ */
2132
+ const setPaddingTop = setYogaValue('padding', Yoga.Edge.Top);
2133
+ /**
2134
+ * Set padding right attribute to node's Yoga instance
2135
+ *
2136
+ * @param padding - Padding right
2137
+ * @param node - Node instance
2138
+ * @returns Node instance
2139
+ */
2140
+ const setPaddingRight = setYogaValue('padding', Yoga.Edge.Right);
2141
+ /**
2142
+ * Set padding bottom attribute to node's Yoga instance
2143
+ *
2144
+ * @param padding - Padding bottom
2145
+ * @param node Node instance
2146
+ * @returns Node instance
2147
+ */
2148
+ const setPaddingBottom = setYogaValue('padding', Yoga.Edge.Bottom);
2149
+ /**
2150
+ * Set padding left attribute to node's Yoga instance
2151
+ *
2152
+ * @param padding - Padding left
2153
+ * @param node - Node instance
2154
+ * @returns Node instance
2155
+ */
2156
+ const setPaddingLeft = setYogaValue('padding', Yoga.Edge.Left);
2157
+
2158
+ /**
2159
+ * Set border top attribute to node's Yoga instance
2160
+ *
2161
+ * @param border - Border top width
2162
+ * @param node - Node instance
2163
+ * @returns Node instance
2164
+ */
2165
+ const setBorderTop = setYogaValue('border', Yoga.Edge.Top);
2166
+ /**
2167
+ * Set border right attribute to node's Yoga instance
2168
+ *
2169
+ * @param border - Border right width
2170
+ * @param node - Node instance
2171
+ * @returns Node instance
2172
+ */
2173
+ const setBorderRight = setYogaValue('border', Yoga.Edge.Right);
2174
+ /**
2175
+ * Set border bottom attribute to node's Yoga instance
2176
+ *
2177
+ * @param border - Border bottom width
2178
+ * @param node - Node instance
2179
+ * @returns Node instance
2180
+ */
2181
+ const setBorderBottom = setYogaValue('border', Yoga.Edge.Bottom);
2182
+ /**
2183
+ * Set border left attribute to node's Yoga instance
2184
+ *
2185
+ * @param border - Border left width
2186
+ * @param node - Node instance
2187
+ * @returns Node instance
2188
+ */
2189
+ const setBorderLeft = setYogaValue('border', Yoga.Edge.Left);
2190
+
2191
+ /**
2192
+ * Set position top attribute to node's Yoga instance
2193
+ *
2194
+ * @param position - Position top
2195
+ * @param node - Node instance
2196
+ * @returns Node instance
2197
+ */
2198
+ const setPositionTop = setYogaValue('position', Yoga.Edge.Top);
2199
+ /**
2200
+ * Set position right attribute to node's Yoga instance
2201
+ *
2202
+ * @param position - Position right
2203
+ * @param node - Node instance
2204
+ * @returns Node instance
2205
+ */
2206
+ const setPositionRight = setYogaValue('position', Yoga.Edge.Right);
2207
+ /**
2208
+ * Set position bottom attribute to node's Yoga instance
2209
+ *
2210
+ * @param position - Position bottom
2211
+ * @param node - Node instance
2212
+ * @returns Node instance
2213
+ */
2214
+ const setPositionBottom = setYogaValue('position', Yoga.Edge.Bottom);
2215
+ /**
2216
+ * Set position left attribute to node's Yoga instance
2217
+ *
2218
+ * @param position - Position left
2219
+ * @param node - Node instance
2220
+ * @returns Node instance
2221
+ */
2222
+ const setPositionLeft = setYogaValue('position', Yoga.Edge.Left);
2223
+
2224
+ /**
2225
+ * Set width to node's Yoga instance
2226
+ *
2227
+ * @param width - Width
2228
+ * @param node - Node instance
2229
+ * @returns Node instance
2230
+ */
2231
+ const setWidth = setYogaValue('width');
2232
+ /**
2233
+ * Set min width to node's Yoga instance
2234
+ *
2235
+ * @param min - Width
2236
+ * @param node - Node instance
2237
+ * @returns Node instance
2238
+ */
2239
+ const setMinWidth = setYogaValue('minWidth');
2240
+ /**
2241
+ * Set max width to node's Yoga instance
2242
+ *
2243
+ * @param max - Width
2244
+ * @param node - Node instance
2245
+ * @returns Node instance
2246
+ */
2247
+ const setMaxWidth = setYogaValue('maxWidth');
2248
+ /**
2249
+ * Set height to node's Yoga instance
2250
+ *
2251
+ * @param height - Height
2252
+ * @param node - Node instance
2253
+ * @returns Node instance
2254
+ */
2255
+ const setHeight = setYogaValue('height');
2256
+ /**
2257
+ * Set min height to node's Yoga instance
2258
+ *
2259
+ * @param min - Height
2260
+ * @param node - Node instance
2261
+ * @returns Node instance
2262
+ */
2263
+ const setMinHeight = setYogaValue('minHeight');
2264
+ /**
2265
+ * Set max height to node's Yoga instance
2266
+ *
2267
+ * @param max - Height
2268
+ * @param node - Node instance
2269
+ * @returns Node instance
2270
+ */
2271
+ const setMaxHeight = setYogaValue('maxHeight');
2272
+
2273
+ /**
2274
+ * Set rowGap value to node's Yoga instance
2275
+ *
2276
+ * @param value - Gap value
2277
+ * @returns Node instance wrapper
2278
+ */
2279
+ const setRowGap = setYogaValue('gap', Yoga.Gutter.Row);
2280
+ /**
2281
+ * Set columnGap value to node's Yoga instance
2282
+ *
2283
+ * @param value - Gap value
2284
+ * @returns Node instance wrapper
2285
+ */
2286
+ const setColumnGap = setYogaValue('gap', Yoga.Gutter.Column);
2287
+
2288
+ const getAspectRatio = (viewbox) => {
2289
+ if (!viewbox)
2290
+ return null;
2291
+ if (typeof viewbox === 'string')
2292
+ return null;
2293
+ return (viewbox.maxX - viewbox.minX) / (viewbox.maxY - viewbox.minY);
2294
+ };
2295
+ /**
2296
+ * Yoga svg measure function
2297
+ *
2298
+ * @param page
2299
+ * @param node
2300
+ * @returns Measure svg
2301
+ */
2302
+ const measureCanvas$1 = (page, node) => (width, widthMode, height, heightMode) => {
2303
+ const aspectRatio = getAspectRatio(node.props.viewBox) || 1;
2304
+ if (widthMode === Yoga.MeasureMode.Exactly ||
2305
+ widthMode === Yoga.MeasureMode.AtMost) {
2306
+ return { width, height: width / aspectRatio };
2307
+ }
2308
+ if (heightMode === Yoga.MeasureMode.Exactly) {
2309
+ return { width: height * aspectRatio };
2310
+ }
2311
+ return {};
2312
+ };
2313
+
2314
+ /**
2315
+ * Get lines width (if any)
2316
+ *
2317
+ * @param node
2318
+ * @returns Lines width
2319
+ */
2320
+ const linesWidth = (node) => {
2321
+ if (!node.lines)
2322
+ return 0;
2323
+ return Math.max(0, ...node.lines.map((line) => line.xAdvance));
2324
+ };
2325
+
2326
+ /**
2327
+ * Get lines height (if any)
2328
+ *
2329
+ * @param node
2330
+ * @returns Lines height
2331
+ */
2332
+ const linesHeight = (node) => {
2333
+ if (!node.lines)
2334
+ return -1;
2335
+ return node.lines.reduce((acc, line) => acc + line.box.height, 0);
2336
+ };
2337
+
2338
+ const ALIGNMENT_FACTORS = { center: 0.5, right: 1 };
2339
+ /**
2340
+ * Yoga text measure function
2341
+ *
2342
+ * @param page
2343
+ * @param node
2344
+ * @param fontStore
2345
+ * @returns {MeasureText} measure text function
2346
+ */
2347
+ const measureText = (page, node, fontStore) => (width, widthMode, height) => {
2348
+ if (widthMode === Yoga.MeasureMode.Exactly) {
2349
+ if (!node.lines)
2350
+ node.lines = layoutText(node, width, height, fontStore);
2351
+ return { height: linesHeight(node), width };
2352
+ }
2353
+ if (widthMode === Yoga.MeasureMode.AtMost) {
2354
+ const alignFactor = ALIGNMENT_FACTORS[node.style?.textAlign] || 0;
2355
+ if (!node.lines) {
2356
+ node.lines = layoutText(node, width, height, fontStore);
2357
+ node.alignOffset = (width - linesWidth(node)) * alignFactor; // Compensate align in variable width containers
2358
+ }
2359
+ return {
2360
+ height: linesHeight(node),
2361
+ width: Math.min(width, linesWidth(node)),
2362
+ };
2363
+ }
2364
+ return {};
2365
+ };
2366
+
2367
+ /**
2368
+ * Get image ratio
2369
+ *
2370
+ * @param node - Image node
2371
+ * @returns Image ratio
2372
+ */
2373
+ const getRatio = (node) => {
2374
+ return node.image?.data ? node.image.width / node.image.height : 1;
2375
+ };
2376
+
2377
+ /**
2378
+ * Checks if page has auto height
2379
+ *
2380
+ * @param page
2381
+ * @returns Is page height auto
2382
+ */
2383
+ const isHeightAuto = (page) => isNil(page.box?.height);
2384
+
2385
+ const SAFETY_HEIGHT$1 = 10;
2386
+ /**
2387
+ * Yoga image measure function
2388
+ *
2389
+ * @param page - Page
2390
+ * @param node - Node
2391
+ * @returns Measure image
2392
+ */
2393
+ const measureImage = (page, node) => (width, widthMode, height, heightMode) => {
2394
+ const imageRatio = getRatio(node);
2395
+ const imageMargin = getMargin(node);
2396
+ const pagePadding = getPadding(page);
2397
+ // TODO: Check image percentage margins
2398
+ const pageArea = isHeightAuto(page)
2399
+ ? Infinity
2400
+ : (page.box?.height || 0) -
2401
+ pagePadding.paddingTop -
2402
+ pagePadding.paddingBottom -
2403
+ imageMargin.marginTop -
2404
+ imageMargin.marginBottom -
2405
+ SAFETY_HEIGHT$1;
2406
+ // Skip measure if image data not present yet
2407
+ if (!node.image)
2408
+ return { width: 0, height: 0 };
2409
+ if (widthMode === Yoga.MeasureMode.Exactly &&
2410
+ heightMode === Yoga.MeasureMode.Undefined) {
2411
+ const scaledHeight = width / imageRatio;
2412
+ return { height: Math.min(pageArea, scaledHeight) };
2413
+ }
2414
+ if (heightMode === Yoga.MeasureMode.Exactly &&
2415
+ (widthMode === Yoga.MeasureMode.AtMost ||
2416
+ widthMode === Yoga.MeasureMode.Undefined)) {
2417
+ return { width: Math.min(height * imageRatio, width) };
2418
+ }
2419
+ if (widthMode === Yoga.MeasureMode.Exactly &&
2420
+ heightMode === Yoga.MeasureMode.AtMost) {
2421
+ const scaledHeight = width / imageRatio;
2422
+ return { height: Math.min(height, pageArea, scaledHeight) };
2423
+ }
2424
+ if (widthMode === Yoga.MeasureMode.AtMost &&
2425
+ heightMode === Yoga.MeasureMode.AtMost) {
2426
+ if (imageRatio > 1) {
2427
+ return {
2428
+ width,
2429
+ height: Math.min(width / imageRatio, height),
2430
+ };
2431
+ }
2432
+ return {
2433
+ height,
2434
+ width: Math.min(height * imageRatio, width),
2435
+ };
2436
+ }
2437
+ return { height, width };
2438
+ };
2439
+
2440
+ const SAFETY_HEIGHT = 10;
2441
+ const getMax = (values) => Math.max(-Infinity, ...values);
2442
+ /**
2443
+ * Helper object to predict canvas size
2444
+ * TODO: Implement remaining functions (as close as possible);
2445
+ */
2446
+ const measureCtx = () => {
2447
+ const ctx = {};
2448
+ const points = [];
2449
+ const nil = () => ctx;
2450
+ const addPoint = (x, y) => points.push([x, y]);
2451
+ const moveTo = (x, y) => {
2452
+ addPoint(x, y);
2453
+ return ctx;
2454
+ };
2455
+ const rect = (x, y, w, h) => {
2456
+ addPoint(x, y);
2457
+ addPoint(x + w, y);
2458
+ addPoint(x, y + h);
2459
+ addPoint(x + w, y + h);
2460
+ return ctx;
2461
+ };
2462
+ const ellipse = (x, y, rx, ry) => {
2463
+ ry = ry || rx;
2464
+ addPoint(x - rx, y - ry);
2465
+ addPoint(x + rx, y - ry);
2466
+ addPoint(x + rx, y + ry);
2467
+ addPoint(x - rx, y + ry);
2468
+ return ctx;
2469
+ };
2470
+ const polygon = (...pts) => {
2471
+ points.push(...pts);
2472
+ return ctx;
2473
+ };
2474
+ // Change dimensions
2475
+ ctx.rect = rect;
2476
+ ctx.moveTo = moveTo;
2477
+ ctx.lineTo = moveTo;
2478
+ ctx.circle = ellipse;
2479
+ ctx.polygon = polygon;
2480
+ ctx.ellipse = ellipse;
2481
+ ctx.roundedRect = rect;
2482
+ // To be implemented
2483
+ ctx.text = nil;
2484
+ ctx.path = nil;
2485
+ ctx.lineWidth = nil;
2486
+ ctx.bezierCurveTo = nil;
2487
+ ctx.quadraticCurveTo = nil;
2488
+ ctx.scale = nil;
2489
+ ctx.rotate = nil;
2490
+ ctx.translate = nil;
2491
+ // These don't change dimensions
2492
+ ctx.dash = nil;
2493
+ ctx.clip = nil;
2494
+ ctx.save = nil;
2495
+ ctx.fill = nil;
2496
+ ctx.font = nil;
2497
+ ctx.stroke = nil;
2498
+ ctx.lineCap = nil;
2499
+ ctx.opacity = nil;
2500
+ ctx.restore = nil;
2501
+ ctx.lineJoin = nil;
2502
+ ctx.fontSize = nil;
2503
+ ctx.fillColor = nil;
2504
+ ctx.miterLimit = nil;
2505
+ ctx.strokeColor = nil;
2506
+ ctx.fillOpacity = nil;
2507
+ ctx.strokeOpacity = nil;
2508
+ ctx.linearGradient = nil;
2509
+ ctx.radialGradient = nil;
2510
+ ctx.getWidth = () => getMax(points.map((p) => p[0]));
2511
+ ctx.getHeight = () => getMax(points.map((p) => p[1]));
2512
+ return ctx;
2513
+ };
2514
+ /**
2515
+ * @typedef {Function} MeasureCanvas
2516
+ * @returns {{ width: number, height: number }} canvas width and height
2517
+ */
2518
+ /**
2519
+ * Yoga canvas measure function
2520
+ *
2521
+ * @param {Object} page
2522
+ * @param {Object} node
2523
+ * @returns {MeasureCanvas} measure canvas
2524
+ */
2525
+ const measureCanvas = (page, node) => () => {
2526
+ const imageMargin = getMargin(node);
2527
+ const pagePadding = getPadding(page);
2528
+ // TODO: Check image percentage margins
2529
+ const pageArea = isHeightAuto(page)
2530
+ ? Infinity
2531
+ : (page.box?.height || 0) -
2532
+ pagePadding.paddingTop -
2533
+ pagePadding.paddingBottom -
2534
+ imageMargin.marginTop -
2535
+ imageMargin.marginBottom -
2536
+ SAFETY_HEIGHT;
2537
+ const ctx = measureCtx();
2538
+ node.props.paint(ctx);
2539
+ const width = ctx.getWidth();
2540
+ const height = Math.min(pageArea, ctx.getHeight());
2541
+ return { width, height };
2542
+ };
2543
+
2544
+ const isType$2 = (type) => (node) => node.type === type;
2545
+ const isSvg$2 = isType$2(P.Svg);
2546
+ const isText$6 = isType$2(P.Text);
2547
+ const isNote = isType$2(P.Note);
2548
+ const isPage = isType$2(P.Page);
2549
+ const isImage = isType$2(P.Image);
2550
+ const isCanvas = isType$2(P.Canvas);
2551
+ const isTextInstance$1 = isType$2(P.TextInstance);
2552
+ const setNodeHeight = (node) => {
2553
+ const value = isPage(node) ? node.box?.height : node.style?.height;
2554
+ return setHeight(value);
2555
+ };
2556
+ /**
2557
+ * Set styles valeus into yoga node before layout calculation
2558
+ *
2559
+ * @param node
2560
+ */
2561
+ const setYogaValues = (node) => {
2562
+ 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);
2563
+ };
2564
+ /**
2565
+ * Inserts child into parent' yoga node
2566
+ *
2567
+ * @param parent parent
2568
+ * @returns Insert yoga nodes
2569
+ */
2570
+ const insertYogaNodes = (parent) => (child) => {
2571
+ parent.insertChild(child.yogaNode, parent.getChildCount());
2572
+ return child;
2573
+ };
2574
+ const setMeasureFunc = (node, page, fontStore) => {
2575
+ const { yogaNode } = node;
2576
+ if (isText$6(node)) {
2577
+ yogaNode.setMeasureFunc(measureText(page, node, fontStore));
2578
+ }
2579
+ if (isImage(node)) {
2580
+ yogaNode.setMeasureFunc(measureImage(page, node));
2581
+ }
2582
+ if (isCanvas(node)) {
2583
+ yogaNode.setMeasureFunc(measureCanvas(page, node));
2584
+ }
2585
+ if (isSvg$2(node)) {
2586
+ yogaNode.setMeasureFunc(measureCanvas$1(page, node));
2587
+ }
2588
+ return node;
2589
+ };
2590
+ const isLayoutElement = (node) => !isText$6(node) && !isNote(node) && !isSvg$2(node);
2591
+ /**
2592
+ * @typedef {Function} CreateYogaNodes
2593
+ * @param {Object} node
2594
+ * @returns {Object} node with appended yoga node
2595
+ */
2596
+ /**
2597
+ * Creates and add yoga node to document tree
2598
+ * Handles measure function for text and image nodes
2599
+ *
2600
+ * @returns Create yoga nodes
2601
+ */
2602
+ const createYogaNodes = (page, fontStore, yoga) => (node) => {
2603
+ const yogaNode = yoga.node.create();
2604
+ const result = Object.assign({}, node, { yogaNode });
2605
+ setYogaValues(result);
2606
+ if (isLayoutElement(node) && node.children) {
2607
+ const resolveChild = compose(insertYogaNodes(yogaNode), createYogaNodes(page, fontStore, yoga));
2608
+ result.children = node.children.map(resolveChild);
2609
+ }
2610
+ setMeasureFunc(result, page, fontStore);
2611
+ return result;
2612
+ };
2613
+ /**
2614
+ * Performs yoga calculation
2615
+ *
2616
+ * @param page - Page node
2617
+ * @returns Page node
2618
+ */
2619
+ const calculateLayout = (page) => {
2620
+ page.yogaNode.calculateLayout();
2621
+ return page;
2622
+ };
2623
+ /**
2624
+ * Saves Yoga layout result into 'box' attribute of node
2625
+ *
2626
+ * @param node
2627
+ * @returns Node with box data
2628
+ */
2629
+ const persistDimensions = (node) => {
2630
+ if (isTextInstance$1(node))
2631
+ return node;
2632
+ const box = Object.assign(getPadding(node), getMargin(node), getBorderWidth(node), getPosition(node), getDimension(node));
2633
+ const newNode = Object.assign({}, node, { box });
2634
+ if (!node.children)
2635
+ return newNode;
2636
+ const children = node.children.map(persistDimensions);
2637
+ return Object.assign({}, newNode, { children });
2638
+ };
2639
+ /**
2640
+ * Removes yoga node from document tree
2641
+ *
2642
+ * @param node
2643
+ * @returns Node without yoga node
2644
+ */
2645
+ const destroyYogaNodes = (node) => {
2646
+ const newNode = Object.assign({}, node);
2647
+ delete newNode.yogaNode;
2648
+ if (!node.children)
2649
+ return newNode;
2650
+ const children = node.children.map(destroyYogaNodes);
2651
+ return Object.assign({}, newNode, { children });
2652
+ };
2653
+ /**
2654
+ * Free yoga node from document tree
2655
+ *
2656
+ * @param node
2657
+ * @returns Node without yoga node
2658
+ */
2659
+ const freeYogaNodes = (node) => {
2660
+ if (node.yogaNode)
2661
+ node.yogaNode.freeRecursive();
2662
+ return node;
2663
+ };
2664
+ /**
2665
+ * Calculates page object layout using Yoga.
2666
+ * Takes node values from 'box' and 'style' attributes, and persist them back into 'box'
2667
+ * Destroy yoga values at the end.
2668
+ *
2669
+ * @param page - Object
2670
+ * @returns Page object with correct 'box' layout attributes
2671
+ */
2672
+ const resolvePageDimensions = (page, fontStore, yoga) => {
2673
+ if (isNil(page))
2674
+ return null;
2675
+ return compose(destroyYogaNodes, freeYogaNodes, persistDimensions, calculateLayout, createYogaNodes(page, fontStore, yoga))(page);
2676
+ };
2677
+ /**
2678
+ * Calculates root object layout using Yoga.
2679
+ *
2680
+ * @param node - Root object
2681
+ * @param fontStore - Font store
2682
+ * @returns Root object with correct 'box' layout attributes
2683
+ */
2684
+ const resolveDimensions = (node, fontStore) => {
2685
+ if (!node.children)
2686
+ return node;
2687
+ const resolveChild = (child) => resolvePageDimensions(child, fontStore, node.yoga);
2688
+ const children = node.children.map(resolveChild);
2689
+ return Object.assign({}, node, { children });
2690
+ };
2691
+
2692
+ const BASE_INHERITABLE_PROPERTIES = [
2693
+ 'color',
2694
+ 'fontFamily',
2695
+ 'fontSize',
2696
+ 'fontStyle',
2697
+ 'fontWeight',
2698
+ 'letterSpacing',
2699
+ 'opacity',
2700
+ 'textDecoration',
2701
+ 'textTransform',
2702
+ 'lineHeight',
2703
+ 'textAlign',
2704
+ 'visibility',
2705
+ 'wordSpacing',
2706
+ ];
2707
+ const TEXT_INHERITABLE_PROPERTIES = [
2708
+ ...BASE_INHERITABLE_PROPERTIES,
2709
+ 'backgroundColor',
2710
+ ];
2711
+ const isType$1 = (type) => (node) => node.type === type;
2712
+ const isSvg$1 = isType$1(P.Svg);
2713
+ const isText$5 = isType$1(P.Text);
2714
+ // Merge style values
2715
+ const mergeValues = (styleName, value, inheritedValue) => {
2716
+ switch (styleName) {
2717
+ case 'textDecoration': {
2718
+ // merge not none and not false textDecoration values to one rule
2719
+ return [inheritedValue, value].filter((v) => v && v !== 'none').join(' ');
2720
+ }
2721
+ default:
2722
+ return value;
2723
+ }
2724
+ };
2725
+ // Merge inherited and node styles
2726
+ const merge = (inheritedStyles, style) => {
2727
+ const mergedStyles = { ...inheritedStyles };
2728
+ Object.entries(style).forEach(([styleName, value]) => {
2729
+ mergedStyles[styleName] = mergeValues(styleName, value, inheritedStyles[styleName]);
2730
+ });
2731
+ return mergedStyles;
2732
+ };
2733
+ /**
2734
+ * Merges styles with node
2735
+ *
2736
+ * @param inheritedStyles - Style object
2737
+ * @returns Merge styles function
2738
+ */
2739
+ const mergeStyles = (inheritedStyles) => (node) => {
2740
+ const style = merge(inheritedStyles, node.style || {});
2741
+ return Object.assign({}, node, { style });
2742
+ };
2743
+ /**
2744
+ * Inherit style values from the root to the leafs
2745
+ *
2746
+ * @param node - Document root
2747
+ * @returns Document root with inheritance
2748
+ *
2749
+ */
2750
+ const resolveInheritance = (node) => {
2751
+ if (isSvg$1(node))
2752
+ return node;
2753
+ if (!('children' in node))
2754
+ return node;
2755
+ const inheritableProperties = isText$5(node)
2756
+ ? TEXT_INHERITABLE_PROPERTIES
2757
+ : BASE_INHERITABLE_PROPERTIES;
2758
+ const inheritStyles = pick(inheritableProperties, node.style || {});
2759
+ const resolveChild = compose(resolveInheritance, mergeStyles(inheritStyles));
2760
+ const children = node.children.map(resolveChild);
2761
+ return Object.assign({}, node, { children });
2762
+ };
2763
+
2764
+ const isSvg = (node) => node.type === P.Svg;
2765
+ const isText$4 = (node) => node.type === P.Text;
2766
+ const shouldIterate = (node) => !isSvg(node) && !isText$4(node);
2767
+ const shouldLayoutText = (node) => isText$4(node) && !node.lines;
2768
+ /**
2769
+ * Performs text layout on text node if wasn't calculated before.
2770
+ * Text layout is usually performed on Yoga's layout process (via setMeasureFunc),
2771
+ * but we need to layout those nodes with fixed width and height.
2772
+ *
2773
+ * @param node
2774
+ * @returns Layout node
2775
+ */
2776
+ const resolveTextLayout = (node, fontStore) => {
2777
+ if (shouldLayoutText(node)) {
2778
+ const width = node.box.width - (node.box.paddingRight + node.box.paddingLeft);
2779
+ const height = node.box.height - (node.box.paddingTop + node.box.paddingBottom);
2780
+ node.lines = layoutText(node, width, height, fontStore);
2781
+ }
2782
+ if (shouldIterate(node)) {
2783
+ if (!node.children)
2784
+ return node;
2785
+ const mapChild = (child) => resolveTextLayout(child, fontStore);
2786
+ const children = node.children.map(mapChild);
2787
+ return Object.assign({}, node, { children });
2788
+ }
2789
+ return node;
2790
+ };
2791
+
2792
+ const isText$3 = (node) => node.type === P.Text;
2793
+ const getSpacingHeight = (node) => {
2794
+ const { paddingTop, paddingBottom } = getPadding(node);
2795
+ const { marginTop, marginBottom } = getMargin(node);
2796
+ const { borderTopWidth, borderBottomWidth } = getBorderWidth(node);
2797
+ return (paddingTop +
2798
+ paddingBottom +
2799
+ marginTop +
2800
+ marginBottom +
2801
+ (borderTopWidth ?? 0) +
2802
+ (borderBottomWidth ?? 0));
2803
+ };
2804
+ /**
2805
+ * Get the height a node would have when laid out at the given width.
2806
+ * Used for column distribution - nodes are initially laid out at full page width,
2807
+ * so their box.height underestimates the space they need when placed in a column.
2808
+ *
2809
+ * Includes padding, margin, border, and rowGap so distribution matches Yoga layout.
2810
+ *
2811
+ * @param node - Node to measure
2812
+ * @param colWidth - Width constraint (column width)
2813
+ * @param fontStore - Font store for text layout
2814
+ * @returns Height in points
2815
+ */
2816
+ const getNodeHeightAtWidth = (node, colWidth, fontStore) => {
2817
+ // Text and View with children can be measured without box (before Yoga layout)
2818
+ if (isText$3(node)) {
2819
+ const preserveLines = node.props
2820
+ ?.__preserveLines;
2821
+ if (preserveLines && node.lines?.length) {
2822
+ const contentHeight = node.lines.reduce((acc, line) => acc + (line?.box?.height ?? 0), 0);
2823
+ return contentHeight + getSpacingHeight(node);
2824
+ }
2825
+ try {
2826
+ const lines = layoutText(node, colWidth, Infinity, fontStore);
2827
+ const contentHeight = (lines || []).reduce((acc, line) => acc + (line?.box?.height ?? 0), 0);
2828
+ return contentHeight + getSpacingHeight(node);
2829
+ }
2830
+ catch {
2831
+ return node.box?.height ?? 0;
2832
+ }
2833
+ }
2834
+ if (node.type === P.View && node.children?.length) {
2835
+ const { paddingTop, paddingBottom } = getPadding(node);
2836
+ const { borderTopWidth, borderBottomWidth } = getBorderWidth(node);
2837
+ const rowGap = node.style?.rowGap ?? 0;
2838
+ const childHeights = node.children.map((child) => getNodeHeightAtWidth(child, colWidth, fontStore));
2839
+ const contentHeight = childHeights.reduce((a, h) => a + h, 0) +
2840
+ (childHeights.length > 1 ? (childHeights.length - 1) * rowGap : 0);
2841
+ return (paddingTop +
2842
+ paddingBottom +
2843
+ (borderTopWidth ?? 0) +
2844
+ (borderBottomWidth ?? 0) +
2845
+ contentHeight);
2846
+ }
2847
+ // For Image, Svg, etc. we need box from Yoga
2848
+ if (!node.box?.height)
2849
+ return 0;
2850
+ return node.box.height;
2851
+ };
2852
+
2853
+ /** In column context, use break to force moving the node to the next column. */
2854
+ const getBreak = (node) => 'break' in node.props ? node.props.break : false;
2855
+ const isText$2 = (node) => node.type === P.Text;
2856
+ const SAFETY_THRESHOLD$1 = 0.001;
2857
+ const warnUnavailableSpace$1 = (node) => {
2858
+ console.warn(`Node of type ${node.type} can't wrap between pages and it's bigger than available column height`);
2859
+ };
2860
+ /**
2861
+ * Split nodes into columns and next page, using the same wrapping logic as pages.
2862
+ * Fills column 1 first, then column 2, etc., then overflows to next page.
2863
+ *
2864
+ * Uses getNodeHeightAtWidth so distribution is based on height at column width,
2865
+ * not the initial full-width layout height (which would underestimate).
2866
+ *
2867
+ * @param height - Available height from parent (wrap area - parent top)
2868
+ * @param contentArea - Max height per column
2869
+ * @param columns - Number of columns
2870
+ * @param colWidth - Width of each column (for accurate height measurement)
2871
+ * @param nodes - Children to distribute
2872
+ * @param splitFn - Function to split Text or View nodes
2873
+ * @param fontStore - Font store for text layout when measuring
2874
+ */
2875
+ const splitNodesMultiColumn = (height, contentArea, columns, colWidth, nodes, splitFn, fontStore) => {
2876
+ const colChildren = Array.from({ length: columns }, () => []);
2877
+ const nextChildren = [];
2878
+ const colHeights = Array(columns).fill(0);
2879
+ let colIndex = 0;
2880
+ const pending = [...nodes];
2881
+ const maxIterations = Math.max(100, nodes.length * columns * 4);
2882
+ let iterations = 0;
2883
+ while (pending.length > 0) {
2884
+ iterations += 1;
2885
+ if (iterations > maxIterations) {
2886
+ console.warn('splitNodesMultiColumn: iteration limit reached, pushing remaining to next page');
2887
+ nextChildren.push(...pending);
2888
+ break;
2889
+ }
2890
+ const child = pending.shift();
2891
+ const futureNodes = pending;
2892
+ if (isFixed(child)) {
2893
+ // Fixed nodes appear on current page (first column) and next page
2894
+ colChildren[0].push(child);
2895
+ nextChildren.push(child);
2896
+ continue;
2897
+ }
2898
+ // Overflowed all columns (e.g. nextPart from split in last column) → next page
2899
+ if (colIndex >= columns) {
2900
+ nextChildren.push(child);
2901
+ continue;
2902
+ }
2903
+ const nodeHeight = getNodeHeightAtWidth(child, colWidth, fontStore);
2904
+ const shouldBreak = getBreak(child);
2905
+ if (shouldBreak) {
2906
+ const next = Object.assign({}, child, {
2907
+ props: {
2908
+ ...child.props,
2909
+ wrap: true,
2910
+ break: false,
2911
+ },
2912
+ });
2913
+ // Move to next column if available and retry this same node there.
2914
+ if (colIndex < columns - 1) {
2915
+ colIndex += 1;
2916
+ pending.unshift(next);
2917
+ continue;
2918
+ }
2919
+ // Last column: overflow this node to next page.
2920
+ nextChildren.push(next);
2921
+ // Push remaining fixed nodes to current, then break
2922
+ for (const n of futureNodes) {
2923
+ if (isFixed(n)) {
2924
+ colChildren[0].push(n);
2925
+ nextChildren.push(n);
2926
+ }
2927
+ }
2928
+ // Preserve remaining nodes (mirror page-level splitNodes behavior)
2929
+ nextChildren.push(...pending);
2930
+ break;
2931
+ }
2932
+ // Find a column with space
2933
+ let placed = false;
2934
+ for (let c = colIndex; c < columns && !placed; c++) {
2935
+ const remaining = height - colHeights[c];
2936
+ if (nodeHeight <= remaining + SAFETY_THRESHOLD$1) {
2937
+ colChildren[c].push(child);
2938
+ colHeights[c] += nodeHeight;
2939
+ placed = true;
2940
+ colIndex = c; // Next node tries this column first
2941
+ break;
2942
+ }
2943
+ // Doesn't fit - try to split
2944
+ if (getWrap(child)) {
2945
+ // For Text: content space = remaining - node's margin/padding/border
2946
+ const splitSpace = isText$2(child)
2947
+ ? Math.max(0, remaining - getSpacingHeight(child))
2948
+ : remaining;
2949
+ const [currentPart, nextPart] = splitFn(child, splitSpace, height);
2950
+ // Current part might be empty (orphans/widows or no lines fit in remaining)
2951
+ // If empty, don't split - try next column with whole node to avoid infinite loop
2952
+ if (currentPart && currentPart.box.height > 0) {
2953
+ colChildren[c].push(currentPart);
2954
+ colHeights[c] += getNodeHeightAtWidth(currentPart, colWidth, fontStore);
2955
+ placed = true;
2956
+ // Try to place next part in next column
2957
+ if (nextPart && nextPart.box.height > 0) {
2958
+ pending.unshift(nextPart);
2959
+ colIndex = c + 1;
2960
+ }
2961
+ }
2962
+ // else: currentPart empty (slicedLineIndex was 0) - fall through to try next column
2963
+ if (placed)
2964
+ break;
2965
+ }
2966
+ // Can't wrap - try next column, or push to next page if all columns tried
2967
+ if (c === columns - 1) {
2968
+ warnUnavailableSpace$1(child);
2969
+ nextChildren.push(child);
2970
+ placed = true;
2971
+ }
2972
+ }
2973
+ if (!placed) {
2974
+ nextChildren.push(child);
2975
+ }
2976
+ }
2977
+ return { colChildren, nextChildren };
2978
+ };
2979
+
2980
+ const isText$1 = (node) => node.type === P.Text;
2981
+ const isView = (node) => node.type === P.View;
2982
+ // Prevent splitting elements by low decimal numbers
2983
+ const SAFETY_THRESHOLD = 0.001;
2984
+ const assingChildren = (children, node) => Object.assign({}, node, { children });
2985
+ const getTop = (node) => node.box?.top || 0;
2986
+ const allFixed = (nodes) => nodes.every(isFixed);
2987
+ const isDynamic = (node) => node.props && 'render' in node.props;
2988
+ const relayoutPage = compose(resolveTextLayout, resolvePageDimensions, resolveInheritance);
2989
+ const relayoutDynamicPage = compose(resolveTextLayout, resolvePageDimensions, resolveInheritance, resolvePageStyles);
2990
+ const warnUnavailableSpace = (node) => {
2991
+ console.warn(`Node of type ${node.type} can't wrap between pages and it's bigger than available page height`);
2992
+ };
2993
+ const splitNodes = (height, contentArea, nodes, fontStore, containerWidth) => {
2994
+ const currentChildren = [];
2995
+ const nextChildren = [];
2996
+ for (let i = 0; i < nodes.length; i += 1) {
2997
+ const child = nodes[i];
2998
+ const futureNodes = nodes.slice(i + 1);
2999
+ const futureFixedNodes = futureNodes.filter(isFixed);
3000
+ const nodeTop = getTop(child);
3001
+ const nodeHeight = child.box.height;
3002
+ const isOutside = height <= nodeTop;
3003
+ const shouldBreak$1 = shouldBreak(child, futureNodes, height, currentChildren);
3004
+ const shouldSplit = height + SAFETY_THRESHOLD < nodeTop + nodeHeight;
3005
+ const canWrap = getWrap(child);
3006
+ const fitsInsidePage = nodeHeight <= contentArea;
3007
+ if (isFixed(child)) {
3008
+ nextChildren.push(child);
3009
+ currentChildren.push(child);
3010
+ continue;
3011
+ }
3012
+ if (isOutside) {
3013
+ const box = Object.assign({}, child.box, { top: child.box.top - height });
3014
+ const next = Object.assign({}, child, { box });
3015
+ nextChildren.push(next);
3016
+ continue;
3017
+ }
3018
+ if (!fitsInsidePage && !canWrap) {
3019
+ currentChildren.push(child);
3020
+ nextChildren.push(...futureNodes);
3021
+ warnUnavailableSpace(child);
3022
+ break;
3023
+ }
3024
+ if (shouldBreak$1) {
3025
+ const box = Object.assign({}, child.box, { top: child.box.top - height });
3026
+ const props = Object.assign({}, child.props, {
3027
+ wrap: true,
3028
+ break: false,
3029
+ });
3030
+ const next = Object.assign({}, child, { box, props });
3031
+ currentChildren.push(...futureFixedNodes);
3032
+ nextChildren.push(next, ...futureNodes);
3033
+ break;
3034
+ }
3035
+ if (shouldSplit) {
3036
+ const [currentChild, nextChild] = split(child, height, contentArea, fontStore, undefined, containerWidth);
3037
+ // All children are moved to the next page, it doesn't make sense to show the parent on the current page
3038
+ if (child.children.length > 0 && currentChild.children.length === 0) {
3039
+ // But if the current page is empty then we can just include the parent on the current page
3040
+ if (currentChildren.length === 0) {
3041
+ currentChildren.push(child, ...futureFixedNodes);
3042
+ nextChildren.push(...futureNodes);
3043
+ }
3044
+ else {
3045
+ const box = Object.assign({}, child.box, {
3046
+ top: child.box.top - height,
3047
+ });
3048
+ const next = Object.assign({}, child, { box });
3049
+ currentChildren.push(...futureFixedNodes);
3050
+ nextChildren.push(next, ...futureNodes);
3051
+ }
3052
+ break;
3053
+ }
3054
+ if (currentChild)
3055
+ currentChildren.push(currentChild);
3056
+ if (nextChild)
3057
+ nextChildren.push(nextChild);
3058
+ continue;
3059
+ }
3060
+ if (isView(child) &&
3061
+ (child.props?.columns ?? 1) > 1 &&
3062
+ fontStore) {
3063
+ const viewChild = child;
3064
+ const columns = viewChild.props?.columns ?? 1;
3065
+ const columnGap = viewChild.props?.columnGap ?? 18;
3066
+ const parentWidth = viewChild.box?.width ?? 0;
3067
+ const fallbackWidth = (containerWidth ?? 0) > 0 ? containerWidth : 0;
3068
+ const effectiveWidth = parentWidth > 0 ? parentWidth : fallbackWidth;
3069
+ const colWidth = effectiveWidth > 0
3070
+ ? (effectiveWidth - columnGap * (columns - 1)) / columns
3071
+ : 0;
3072
+ // Guard against zero colWidth - layoutText at width 0 causes hang
3073
+ if (colWidth <= 0) {
3074
+ currentChildren.push(child);
3075
+ continue;
3076
+ }
3077
+ const splitFn = (ch, h, cArea) => split(ch, h, cArea, fontStore, colWidth, colWidth);
3078
+ const { colChildren, nextChildren: overflowChildren } = splitNodesMultiColumn(height - nodeTop, contentArea, columns, colWidth, viewChild.children || [], splitFn, fontStore);
3079
+ if (overflowChildren.length > 0) {
3080
+ const columnViews = createColumnViews(viewChild, colChildren, colWidth);
3081
+ const currentViewWithCols = Object.assign({}, viewChild, {
3082
+ style: {
3083
+ ...viewChild.style,
3084
+ flexDirection: 'row',
3085
+ columnGap,
3086
+ alignItems: 'flex-start',
3087
+ },
3088
+ children: columnViews,
3089
+ });
3090
+ const nextViewWithOverflow = Object.assign({}, viewChild, {
3091
+ children: overflowChildren,
3092
+ box: { ...viewChild.box, top: 0 },
3093
+ });
3094
+ currentChildren.push(currentViewWithCols);
3095
+ nextChildren.push(nextViewWithOverflow, ...futureNodes);
3096
+ break;
3097
+ }
3098
+ const childToPush = transformViewToColumns(viewChild, height - nodeTop, contentArea, fontStore, containerWidth);
3099
+ currentChildren.push(childToPush);
3100
+ }
3101
+ else {
3102
+ currentChildren.push(child);
3103
+ }
3104
+ }
3105
+ return [currentChildren, nextChildren];
3106
+ };
3107
+ const splitChildren = (height, contentArea, node, fontStore) => {
3108
+ const children = node.children || [];
3109
+ const availableHeight = height - getTop(node);
3110
+ return splitNodes(availableHeight, contentArea, children, fontStore);
3111
+ };
3112
+ const transformViewToColumns = (node, availableHeight, contentArea, fontStore, containerWidth) => {
3113
+ const columns = node.props?.columns ?? 1;
3114
+ const columnGap = node.props?.columnGap ?? 18;
3115
+ const parentWidth = node.box?.width ?? 0;
3116
+ const fallbackWidth = (containerWidth ?? 0) > 0 ? containerWidth : 0;
3117
+ const effectiveWidth = parentWidth > 0 ? parentWidth : fallbackWidth;
3118
+ const colWidth = effectiveWidth > 0
3119
+ ? (effectiveWidth - columnGap * (columns - 1)) / columns
3120
+ : 0;
3121
+ if (colWidth <= 0) {
3122
+ return node;
3123
+ }
3124
+ const splitFn = (child, h, cArea) => split(child, h, cArea, fontStore, colWidth, colWidth);
3125
+ const { colChildren } = splitNodesMultiColumn(availableHeight, contentArea, columns, colWidth, node.children || [], splitFn, fontStore);
3126
+ const columnViews = createColumnViews(node, colChildren, colWidth);
3127
+ return Object.assign({}, node, {
3128
+ style: {
3129
+ ...node.style,
3130
+ flexDirection: 'row',
3131
+ columnGap,
3132
+ alignItems: 'flex-start',
3133
+ },
3134
+ children: columnViews,
3135
+ });
3136
+ };
3137
+ const splitView = (node, height, contentArea, fontStore, containerWidth) => {
3138
+ const [currentNode, nextNode] = splitNode(node, height);
3139
+ const columns = node.props?.columns ?? 1;
3140
+ const columnGap = node.props?.columnGap ?? 18;
3141
+ if (isView(node) && columns > 1 && fontStore) {
3142
+ const availableHeight = height - getTop(node);
3143
+ const parentWidth = node.box?.width ?? 0;
3144
+ const fallbackWidth = (containerWidth ?? 0) > 0 ? containerWidth : 0;
3145
+ const effectiveWidth = parentWidth > 0 ? parentWidth : fallbackWidth;
3146
+ const colWidth = effectiveWidth > 0
3147
+ ? (effectiveWidth - columnGap * (columns - 1)) / columns
3148
+ : 0;
3149
+ if (colWidth > 0) {
3150
+ const { colChildren, nextChildren } = splitNodesMultiColumn(availableHeight, contentArea, columns, colWidth, node.children || [], (child, h, cArea) => split(child, h, cArea, fontStore, colWidth, colWidth), fontStore);
3151
+ const columnViews = createColumnViews(node, colChildren, colWidth);
3152
+ const currentViewWithRow = Object.assign({}, currentNode, {
3153
+ style: {
3154
+ ...currentNode.style,
3155
+ flexDirection: 'row',
3156
+ columnGap,
3157
+ alignItems: 'flex-start',
3158
+ },
3159
+ });
3160
+ return [
3161
+ assingChildren(columnViews, currentViewWithRow),
3162
+ assingChildren(nextChildren, nextNode),
3163
+ ];
3164
+ }
3165
+ }
3166
+ const [currentChilds, nextChildren] = splitChildren(height, contentArea, node, fontStore);
3167
+ return [
3168
+ assingChildren(currentChilds, currentNode),
3169
+ assingChildren(nextChildren, nextNode),
3170
+ ];
3171
+ };
3172
+ const split = (node, height, contentArea, fontStore, colWidth, containerWidth) => isText$1(node)
3173
+ ? colWidth != null && fontStore
3174
+ ? splitTextAtWidth(node, colWidth, height, fontStore)
3175
+ : splitText(node, height)
3176
+ : splitView(node, height, contentArea, fontStore, containerWidth);
3177
+ const shouldResolveDynamicNodes = (node) => {
3178
+ const children = node.children || [];
3179
+ return isDynamic(node) || children.some(shouldResolveDynamicNodes);
3180
+ };
3181
+ const resolveDynamicNodes = (props, node) => {
3182
+ const isNodeDynamic = isDynamic(node);
3183
+ // Call render prop on dynamic nodes and append result to children
3184
+ const resolveChildren = (children = []) => {
3185
+ if (isNodeDynamic) {
3186
+ const res = node.props.render(props);
3187
+ return (createInstances(res)
3188
+ .filter(Boolean)
3189
+ // @ts-expect-error rework dynamic nodes. conflicting types
3190
+ .map((n) => resolveDynamicNodes(props, n)));
3191
+ }
3192
+ return children.map((c) => resolveDynamicNodes(props, c));
3193
+ };
3194
+ // We reset dynamic text box so it can be computed again later on
3195
+ const resetHeight = isNodeDynamic && isText$1(node);
3196
+ const box = resetHeight ? { ...node.box, height: 0 } : node.box;
3197
+ const children = resolveChildren(node.children);
3198
+ // @ts-expect-error handle text here specifically
3199
+ const lines = isNodeDynamic ? null : node.lines;
3200
+ return Object.assign({}, node, { box, lines, children });
3201
+ };
3202
+ const resolveDynamicPage = (props, page, fontStore, yoga) => {
3203
+ if (shouldResolveDynamicNodes(page)) {
3204
+ const resolvedPage = resolveDynamicNodes(props, page);
3205
+ return relayoutDynamicPage(resolvedPage, fontStore, yoga);
3206
+ }
3207
+ return page;
3208
+ };
3209
+ const splitPage = (page, pageNumber, fontStore, yoga) => {
3210
+ const wrapArea = getWrapArea(page);
3211
+ const contentArea = getContentArea(page);
3212
+ const containerWidth = getContentWidth(page);
3213
+ const dynamicPage = resolveDynamicPage({ pageNumber }, page, fontStore, yoga);
3214
+ const height = page.style.height;
3215
+ const [currentChilds, nextChilds] = splitNodes(wrapArea, contentArea, dynamicPage.children, fontStore, containerWidth);
3216
+ const relayout = (node) =>
3217
+ // @ts-expect-error rework pagination
3218
+ relayoutPage(node, fontStore, yoga);
3219
+ const currentBox = { ...page.box, height };
3220
+ const currentPage = relayout(Object.assign({}, page, { box: currentBox, children: currentChilds }));
3221
+ if (nextChilds.length === 0 || allFixed(nextChilds))
3222
+ return [currentPage, null];
3223
+ const nextBox = omit('height', page.box);
3224
+ const nextProps = omit('bookmark', page.props);
3225
+ const nextPage = relayout(Object.assign({}, page, {
3226
+ props: nextProps,
3227
+ box: nextBox,
3228
+ children: nextChilds,
3229
+ }));
3230
+ return [currentPage, nextPage];
3231
+ };
3232
+ const resolvePageIndices = (fontStore, yoga, page, pageNumber, pages) => {
3233
+ const totalPages = pages.length;
3234
+ const props = {
3235
+ totalPages,
3236
+ pageNumber: pageNumber + 1,
3237
+ subPageNumber: page.subPageNumber + 1,
3238
+ subPageTotalPages: page.subPageTotalPages,
3239
+ };
3240
+ return resolveDynamicPage(props, page, fontStore, yoga);
3241
+ };
3242
+ const assocSubPageData = (subpages) => {
3243
+ return subpages.map((page, i) => ({
3244
+ ...page,
3245
+ subPageNumber: i,
3246
+ subPageTotalPages: subpages.length,
3247
+ }));
3248
+ };
3249
+ const dissocSubPageData = (page) => {
3250
+ return omit(['subPageNumber', 'subPageTotalPages'], page);
3251
+ };
3252
+ const MAX_PAGES = 1000;
3253
+ const paginate = (page, pageNumber, fontStore, yoga) => {
3254
+ if (!page)
3255
+ return [];
3256
+ if (page.props?.wrap === false)
3257
+ return [page];
3258
+ let splittedPage = splitPage(page, pageNumber, fontStore, yoga);
3259
+ const pages = [splittedPage[0]];
3260
+ let nextPage = splittedPage[1];
3261
+ while (nextPage !== null) {
3262
+ if (pages.length >= MAX_PAGES) {
3263
+ console.warn(`resolvePagination: max pages limit (${MAX_PAGES}) reached, stopping pagination`);
3264
+ break;
3265
+ }
3266
+ splittedPage = splitPage(nextPage, pageNumber + pages.length, fontStore, yoga);
3267
+ pages.push(splittedPage[0]);
3268
+ nextPage = splittedPage[1];
3269
+ }
3270
+ return pages;
3271
+ };
3272
+ /**
3273
+ * Performs pagination. This is the step responsible of breaking the whole document
3274
+ * into pages following pagiation rules, such as `fixed`, `break` and dynamic nodes.
3275
+ *
3276
+ * @param root - Document node
3277
+ * @param fontStore - Font store
3278
+ * @returns Layout node
3279
+ */
3280
+ const resolvePagination = (root, fontStore) => {
3281
+ let pages = [];
3282
+ let pageNumber = 1;
3283
+ for (let i = 0; i < root.children.length; i += 1) {
3284
+ const page = root.children[i];
3285
+ let subpages = paginate(page, pageNumber, fontStore, root.yoga);
3286
+ subpages = assocSubPageData(subpages);
3287
+ pageNumber += subpages.length;
3288
+ pages = pages.concat(subpages);
3289
+ }
3290
+ pages = pages.map((...args) => dissocSubPageData(resolvePageIndices(fontStore, root.yoga, ...args)));
3291
+ return assingChildren(pages, root);
3292
+ };
3293
+
3294
+ /**
3295
+ * Translates page percentage horizontal paddings in fixed ones
3296
+ *
3297
+ * @param container - Page container
3298
+ * @returns Resolve page horizontal padding
3299
+ */
3300
+ const resolvePageHorizontalPadding = (container) => (value) => {
3301
+ const match = matchPercent(value);
3302
+ const width = container.width;
3303
+ return match ? match.percent * width : value;
3304
+ };
3305
+ /**
3306
+ * Translates page percentage vertical paddings in fixed ones
3307
+ *
3308
+ * @param container - Page container
3309
+ * @returns Resolve page vertical padding
3310
+ */
3311
+ const resolvePageVerticalPadding = (container) => (value) => {
3312
+ const match = matchPercent(value);
3313
+ const height = container.height;
3314
+ return match ? match.percent * height : value;
3315
+ };
3316
+ /**
3317
+ * Translates page percentage paddings in fixed ones
3318
+ *
3319
+ * @param page
3320
+ * @returns Page with fixed paddings
3321
+ */
3322
+ const resolvePagePaddings = (page) => {
3323
+ const container = page.style;
3324
+ const style = evolve({
3325
+ paddingTop: resolvePageVerticalPadding(container),
3326
+ paddingLeft: resolvePageHorizontalPadding(container),
3327
+ paddingRight: resolvePageHorizontalPadding(container),
3328
+ paddingBottom: resolvePageVerticalPadding(container),
3329
+ }, page.style);
3330
+ return Object.assign({}, page, { style });
3331
+ };
3332
+ /**
3333
+ * Translates all pages percentage paddings in fixed ones
3334
+ * This has to be computed from pages calculated size and not by Yoga
3335
+ * because at this point we didn't performed pagination yet.
3336
+ *
3337
+ * @param root - Document root
3338
+ * @returns Document root with translated page paddings
3339
+ */
3340
+ const resolvePagesPaddings = (root) => {
3341
+ if (!root.children)
3342
+ return root;
3343
+ const children = root.children.map(resolvePagePaddings);
3344
+ return Object.assign({}, root, { children });
3345
+ };
3346
+
3347
+ const resolveRadius = (box) => (value) => {
3348
+ if (!value)
3349
+ return undefined;
3350
+ const match = matchPercent(value);
3351
+ return match ? match.percent * Math.min(box.width, box.height) : value;
3352
+ };
3353
+ /**
3354
+ * Transforms percent border radius into fixed values
3355
+ *
3356
+ * @param node
3357
+ * @returns Node
3358
+ */
3359
+ const resolvePercentRadius = (node) => {
3360
+ const style = evolve({
3361
+ borderTopLeftRadius: resolveRadius(node.box),
3362
+ borderTopRightRadius: resolveRadius(node.box),
3363
+ borderBottomRightRadius: resolveRadius(node.box),
3364
+ borderBottomLeftRadius: resolveRadius(node.box),
3365
+ }, node.style || {});
3366
+ const newNode = Object.assign({}, node, { style });
3367
+ if (!node.children)
3368
+ return newNode;
3369
+ const children = node.children.map(resolvePercentRadius);
3370
+ return Object.assign({}, newNode, { children });
3371
+ };
3372
+
3373
+ /**
3374
+ * Transform percent height into fixed
3375
+ *
3376
+ * @param height
3377
+ * @returns Height
3378
+ */
3379
+ const transformHeight = (pageArea, height) => {
3380
+ const match = matchPercent(height);
3381
+ return match ? match.percent * pageArea : height;
3382
+ };
3383
+ /**
3384
+ * Get page area (height minus paddings)
3385
+ *
3386
+ * @param page
3387
+ * @returns Page area
3388
+ */
3389
+ const getPageArea = (page) => {
3390
+ const pageHeight = page.style.height;
3391
+ const pagePaddingTop = (page.style?.paddingTop || 0);
3392
+ const pagePaddingBottom = (page.style?.paddingBottom || 0);
3393
+ return pageHeight - pagePaddingTop - pagePaddingBottom;
3394
+ };
3395
+ /**
3396
+ * Transform node percent height to fixed
3397
+ *
3398
+ * @param page
3399
+ * @param node
3400
+ * @returns Transformed node
3401
+ */
3402
+ const resolveNodePercentHeight = (page, node) => {
3403
+ if (isNil(page.style?.height))
3404
+ return node;
3405
+ if (isNil(node.style?.height))
3406
+ return node;
3407
+ const pageArea = getPageArea(page);
3408
+ const height = transformHeight(pageArea, node.style.height);
3409
+ const style = Object.assign({}, node.style, { height });
3410
+ return Object.assign({}, node, { style });
3411
+ };
3412
+ /**
3413
+ * Transform page immediate children with percent height to fixed
3414
+ *
3415
+ * @param page
3416
+ * @returns Transformed page
3417
+ */
3418
+ const resolvePagePercentHeight = (page) => {
3419
+ if (!page.children)
3420
+ return page;
3421
+ const resolveChild = (child) => resolveNodePercentHeight(page, child);
3422
+ const children = page.children.map(resolveChild);
3423
+ return Object.assign({}, page, { children });
3424
+ };
3425
+ /**
3426
+ * Transform all page immediate children with percent height to fixed.
3427
+ * This is needed for computing correct dimensions on pre-pagination layout.
3428
+ *
3429
+ * @param root - Document root
3430
+ * @returns Transformed document root
3431
+ */
3432
+ const resolvePercentHeight = (root) => {
3433
+ if (!root.children)
3434
+ return root;
3435
+ const children = root.children.map(resolvePagePercentHeight);
3436
+ return Object.assign({}, root, { children });
3437
+ };
3438
+
3439
+ const isType = (type) => (node) => node.type === type;
3440
+ const isLink = isType(P.Link);
3441
+ const isText = isType(P.Text);
3442
+ const isTextInstance = isType(P.TextInstance);
3443
+ /**
3444
+ * Checks if node has render prop
3445
+ *
3446
+ * @param node
3447
+ * @returns Has render prop?
3448
+ */
3449
+ const hasRenderProp = (node) => 'render' in node.props;
3450
+ /**
3451
+ * Checks if node is text type (Text or TextInstance)
3452
+ *
3453
+ * @param node
3454
+ * @returns Are all children text instances?
3455
+ */
3456
+ const isTextType = (node) => isText(node) || isTextInstance(node);
3457
+ /**
3458
+ * Checks if is tet link that needs to be wrapped in Text
3459
+ *
3460
+ * @param node
3461
+ * @returns Are all children text instances?
3462
+ */
3463
+ const isTextLink = (node) => {
3464
+ const children = node.children || [];
3465
+ // Text string inside a Link
3466
+ if (children.every(isTextInstance))
3467
+ return true;
3468
+ // Text node inside a Link
3469
+ if (children.every(isText))
3470
+ return false;
3471
+ return children.every(isTextType);
3472
+ };
3473
+ /**
3474
+ * Wraps node children inside Text node
3475
+ *
3476
+ * @param node
3477
+ * @returns Node with intermediate Text child
3478
+ */
3479
+ const wrapText = (node) => {
3480
+ const textElement = {
3481
+ type: P.Text,
3482
+ props: {},
3483
+ style: {},
3484
+ box: {},
3485
+ children: node.children,
3486
+ };
3487
+ return Object.assign({}, node, { children: [textElement] });
3488
+ };
3489
+ const transformLink = (node) => {
3490
+ if (!isLink(node))
3491
+ return node;
3492
+ // If has render prop substitute the instance by a Text, that will
3493
+ // ultimately render the inline Link via the textkit PDF renderer.
3494
+ if (hasRenderProp(node))
3495
+ return Object.assign({}, node, { type: P.Text });
3496
+ // If is a text link (either contains Text or TextInstance), wrap it
3497
+ // inside a Text element so styles are applied correctly
3498
+ if (isTextLink(node))
3499
+ return wrapText(node);
3500
+ return node;
3501
+ };
3502
+ /**
3503
+ * Transforms Link layout to correctly render text and dynamic rendered links
3504
+ *
3505
+ * @param node
3506
+ * @returns Node with link substitution
3507
+ */
3508
+ const resolveLinkSubstitution = (node) => {
3509
+ if (!node.children)
3510
+ return node;
3511
+ const resolveChild = compose(transformLink, resolveLinkSubstitution);
3512
+ const children = node.children.map(resolveChild);
3513
+ return Object.assign({}, node, { children });
3514
+ };
3515
+
3516
+ const layout = asyncCompose(resolveZIndex, resolveOrigin, resolveAssets, resolvePagination, resolveTextLayout, resolvePercentRadius, resolveDimensions, resolveSvg, resolveAssets, resolveInheritance, resolvePercentHeight, resolvePagesPaddings, resolveStyles, resolveLinkSubstitution, resolveBookmarks, resolvePageSizes, resolveYoga);
3517
+
3518
+ export { layout as default };