@fauzi-dhuhuri/react-pdf-layout 4.7.0-zi-1

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