@pdfme/common 6.1.0 → 6.1.1-dev.11
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/dist/dynamicTemplate.d.ts +2 -2
- package/dist/helper.d.ts +30 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +430 -194
- package/dist/index.js.map +1 -1
- package/dist/pageSize.d.ts +53 -0
- package/dist/schema.d.ts +61 -21
- package/dist/splitRange.d.ts +5 -0
- package/dist/types.d.ts +25 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Buffer } from "buffer";
|
|
3
|
+
import { PDFName } from "@pdfme/pdf-lib";
|
|
3
4
|
import * as acorn from "acorn";
|
|
4
5
|
//#region src/version.ts
|
|
5
|
-
var PDFME_VERSION = "6.1.
|
|
6
|
+
var PDFME_VERSION = "6.1.1";
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/pageSize.ts
|
|
9
|
+
var PAGE_SIZE_PRESETS = {
|
|
10
|
+
A3: {
|
|
11
|
+
width: 297,
|
|
12
|
+
height: 420
|
|
13
|
+
},
|
|
14
|
+
A4: {
|
|
15
|
+
width: 210,
|
|
16
|
+
height: 297
|
|
17
|
+
},
|
|
18
|
+
A5: {
|
|
19
|
+
width: 148,
|
|
20
|
+
height: 210
|
|
21
|
+
},
|
|
22
|
+
A6: {
|
|
23
|
+
width: 105,
|
|
24
|
+
height: 148
|
|
25
|
+
},
|
|
26
|
+
B4: {
|
|
27
|
+
width: 250,
|
|
28
|
+
height: 353
|
|
29
|
+
},
|
|
30
|
+
B5: {
|
|
31
|
+
width: 176,
|
|
32
|
+
height: 250
|
|
33
|
+
},
|
|
34
|
+
B6: {
|
|
35
|
+
width: 125,
|
|
36
|
+
height: 176
|
|
37
|
+
},
|
|
38
|
+
Letter: {
|
|
39
|
+
width: 215.9,
|
|
40
|
+
height: 279.4
|
|
41
|
+
},
|
|
42
|
+
Legal: {
|
|
43
|
+
width: 215.9,
|
|
44
|
+
height: 355.6
|
|
45
|
+
},
|
|
46
|
+
Tabloid: {
|
|
47
|
+
width: 279.4,
|
|
48
|
+
height: 431.8
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var resolvePageSize = (size = "A4", orientation = "portrait") => {
|
|
52
|
+
const resolved = typeof size === "string" ? PAGE_SIZE_PRESETS[size] : {
|
|
53
|
+
width: size.width,
|
|
54
|
+
height: size.height
|
|
55
|
+
};
|
|
56
|
+
if (!resolved) throw new Error(`@pdfme/common: unknown page size preset "${String(size)}"`);
|
|
57
|
+
if (orientation === "landscape") return {
|
|
58
|
+
width: resolved.height,
|
|
59
|
+
height: resolved.width
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
width: resolved.width,
|
|
63
|
+
height: resolved.height
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
var detectPaperSize = (width, height, tolerance = 2) => {
|
|
67
|
+
const entries = Object.entries(PAGE_SIZE_PRESETS);
|
|
68
|
+
for (const [name, size] of entries) if (Math.abs(width - size.width) <= tolerance && Math.abs(height - size.height) <= tolerance || Math.abs(width - size.height) <= tolerance && Math.abs(height - size.width) <= tolerance) return `${name} ${width < height ? "portrait" : "landscape"}`;
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
6
71
|
//#endregion
|
|
7
72
|
//#region src/constants.ts
|
|
8
73
|
var PT_TO_PX_RATIO = 1.333;
|
|
@@ -13,8 +78,7 @@ var ZOOM = 3.7795275591;
|
|
|
13
78
|
* Blank A4 PDF size.
|
|
14
79
|
*/
|
|
15
80
|
var BLANK_A4_PDF = {
|
|
16
|
-
|
|
17
|
-
height: 297,
|
|
81
|
+
...PAGE_SIZE_PRESETS.A4,
|
|
18
82
|
padding: [
|
|
19
83
|
10,
|
|
20
84
|
10,
|
|
@@ -104,6 +168,9 @@ z.object({
|
|
|
104
168
|
"schemas.text.max": z.string(),
|
|
105
169
|
"schemas.text.fit": z.string(),
|
|
106
170
|
"schemas.text.dynamicFontSize": z.string(),
|
|
171
|
+
"schemas.text.overflow": z.string(),
|
|
172
|
+
"schemas.text.overflowVisible": z.string(),
|
|
173
|
+
"schemas.text.overflowExpand": z.string(),
|
|
107
174
|
"schemas.text.format": z.string(),
|
|
108
175
|
"schemas.text.plain": z.string(),
|
|
109
176
|
"schemas.text.inlineMarkdown": z.string(),
|
|
@@ -133,7 +200,18 @@ z.object({
|
|
|
133
200
|
"schemas.date.locale": z.string(),
|
|
134
201
|
"schemas.select.options": z.string(),
|
|
135
202
|
"schemas.select.optionPlaceholder": z.string(),
|
|
136
|
-
"schemas.radioGroup.groupName": z.string()
|
|
203
|
+
"schemas.radioGroup.groupName": z.string(),
|
|
204
|
+
"schemas.list.listStyle": z.string(),
|
|
205
|
+
"schemas.list.bullet": z.string(),
|
|
206
|
+
"schemas.list.ordered": z.string(),
|
|
207
|
+
"schemas.list.markerWidth": z.string(),
|
|
208
|
+
"schemas.list.markerGap": z.string(),
|
|
209
|
+
"schemas.list.indentSize": z.string(),
|
|
210
|
+
"schemas.list.itemSpacing": z.string(),
|
|
211
|
+
"schemas.list.addItem": z.string(),
|
|
212
|
+
"schemas.list.removeItem": z.string(),
|
|
213
|
+
"schemas.list.indentItem": z.string(),
|
|
214
|
+
"schemas.list.outdentItem": z.string()
|
|
137
215
|
});
|
|
138
216
|
z.enum([
|
|
139
217
|
"viewer",
|
|
@@ -145,6 +223,11 @@ z.object({
|
|
|
145
223
|
height: z.number(),
|
|
146
224
|
width: z.number()
|
|
147
225
|
});
|
|
226
|
+
var DynamicLayoutSplitRange = z.object({
|
|
227
|
+
unit: z.string().min(1),
|
|
228
|
+
start: z.number(),
|
|
229
|
+
end: z.number().optional()
|
|
230
|
+
});
|
|
148
231
|
var Schema = z.object({
|
|
149
232
|
name: z.string(),
|
|
150
233
|
type: z.string(),
|
|
@@ -159,10 +242,7 @@ var Schema = z.object({
|
|
|
159
242
|
opacity: z.number().optional(),
|
|
160
243
|
readOnly: z.boolean().optional(),
|
|
161
244
|
required: z.boolean().optional(),
|
|
162
|
-
|
|
163
|
-
start: z.number(),
|
|
164
|
-
end: z.number().optional()
|
|
165
|
-
}).optional(),
|
|
245
|
+
__splitRange: DynamicLayoutSplitRange.optional(),
|
|
166
246
|
__isSplit: z.boolean().optional()
|
|
167
247
|
}).passthrough();
|
|
168
248
|
var SchemaForUIAdditionalInfo = z.object({ id: z.string() });
|
|
@@ -350,6 +430,130 @@ var isUrlSafeToFetch = (urlString) => {
|
|
|
350
430
|
}
|
|
351
431
|
return true;
|
|
352
432
|
};
|
|
433
|
+
var SAFE_LINK_URI_PROTOCOLS = new Set([
|
|
434
|
+
"http:",
|
|
435
|
+
"https:",
|
|
436
|
+
"mailto:"
|
|
437
|
+
]);
|
|
438
|
+
var INTERNAL_LINK_CACHE_KEY = "pdfme-internal-link-cache";
|
|
439
|
+
var normalizeSafeLinkUri = (uri) => {
|
|
440
|
+
const trimmed = uri.trim();
|
|
441
|
+
if (!trimmed) return void 0;
|
|
442
|
+
let parsed;
|
|
443
|
+
try {
|
|
444
|
+
parsed = new URL(trimmed);
|
|
445
|
+
} catch {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
return SAFE_LINK_URI_PROTOCOLS.has(parsed.protocol.toLowerCase()) ? trimmed : void 0;
|
|
449
|
+
};
|
|
450
|
+
var getInternalLinkTarget = (href) => {
|
|
451
|
+
const trimmed = href.trim();
|
|
452
|
+
if (!trimmed.startsWith("#") || trimmed.length === 1) return void 0;
|
|
453
|
+
let target = trimmed.slice(1);
|
|
454
|
+
try {
|
|
455
|
+
target = decodeURIComponent(target);
|
|
456
|
+
} catch {}
|
|
457
|
+
target = target.trim();
|
|
458
|
+
if (!target || Array.from(target).some((char) => {
|
|
459
|
+
const code = char.charCodeAt(0);
|
|
460
|
+
return code < 32 || code === 127;
|
|
461
|
+
})) return;
|
|
462
|
+
return target;
|
|
463
|
+
};
|
|
464
|
+
var normalizeInternalLinkHref = (href) => {
|
|
465
|
+
const target = getInternalLinkTarget(href);
|
|
466
|
+
return target ? `#${target}` : void 0;
|
|
467
|
+
};
|
|
468
|
+
var normalizeLinkHref = (href) => normalizeSafeLinkUri(href) ?? normalizeInternalLinkHref(href);
|
|
469
|
+
var getInternalLinkCache = (_cache) => {
|
|
470
|
+
let cache = _cache.get(INTERNAL_LINK_CACHE_KEY);
|
|
471
|
+
if (!cache) {
|
|
472
|
+
cache = {
|
|
473
|
+
anchors: /* @__PURE__ */ new Map(),
|
|
474
|
+
annotations: []
|
|
475
|
+
};
|
|
476
|
+
_cache.set(INTERNAL_LINK_CACHE_KEY, cache);
|
|
477
|
+
}
|
|
478
|
+
return cache;
|
|
479
|
+
};
|
|
480
|
+
var resetInternalLinkAnnotations = (_cache) => {
|
|
481
|
+
_cache.set(INTERNAL_LINK_CACHE_KEY, {
|
|
482
|
+
anchors: /* @__PURE__ */ new Map(),
|
|
483
|
+
annotations: []
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
var registerInternalLinkAnchor = (arg) => {
|
|
487
|
+
const { _cache, name, page, x, y } = arg;
|
|
488
|
+
if (!name) return;
|
|
489
|
+
const cache = getInternalLinkCache(_cache);
|
|
490
|
+
const anchors = cache.anchors.get(name) ?? [];
|
|
491
|
+
anchors.push({
|
|
492
|
+
name,
|
|
493
|
+
page,
|
|
494
|
+
x,
|
|
495
|
+
y
|
|
496
|
+
});
|
|
497
|
+
cache.anchors.set(name, anchors);
|
|
498
|
+
};
|
|
499
|
+
var registerInternalLinkAnnotation = (arg) => {
|
|
500
|
+
const { _cache, page, targetName, rect, borderWidth } = arg;
|
|
501
|
+
if (!targetName || rect.width <= 0 || rect.height <= 0) return;
|
|
502
|
+
getInternalLinkCache(_cache).annotations.push({
|
|
503
|
+
page,
|
|
504
|
+
targetName,
|
|
505
|
+
rect,
|
|
506
|
+
borderWidth
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
var addGoToLinkAnnotation = (arg) => {
|
|
510
|
+
const { pdfDoc, page, target, rect, borderWidth = 0 } = arg;
|
|
511
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
512
|
+
const annotationRef = pdfDoc.context.register(pdfDoc.context.obj({
|
|
513
|
+
Type: PDFName.of("Annot"),
|
|
514
|
+
Subtype: PDFName.of("Link"),
|
|
515
|
+
Rect: [
|
|
516
|
+
rect.x,
|
|
517
|
+
rect.y,
|
|
518
|
+
rect.x + rect.width,
|
|
519
|
+
rect.y + rect.height
|
|
520
|
+
],
|
|
521
|
+
Border: [
|
|
522
|
+
0,
|
|
523
|
+
0,
|
|
524
|
+
borderWidth
|
|
525
|
+
],
|
|
526
|
+
A: {
|
|
527
|
+
Type: PDFName.of("Action"),
|
|
528
|
+
S: PDFName.of("GoTo"),
|
|
529
|
+
D: [
|
|
530
|
+
target.page.ref,
|
|
531
|
+
PDFName.of("XYZ"),
|
|
532
|
+
target.x,
|
|
533
|
+
target.y,
|
|
534
|
+
null
|
|
535
|
+
]
|
|
536
|
+
}
|
|
537
|
+
}));
|
|
538
|
+
page.node.addAnnot(annotationRef);
|
|
539
|
+
};
|
|
540
|
+
var applyInternalLinkAnnotations = (arg) => {
|
|
541
|
+
const { _cache, pdfDoc } = arg;
|
|
542
|
+
const cache = getInternalLinkCache(_cache);
|
|
543
|
+
cache.annotations.forEach((annotation) => {
|
|
544
|
+
const anchors = cache.anchors.get(annotation.targetName) ?? [];
|
|
545
|
+
if (anchors.length === 0) throw new Error(`[@pdfme/generator] Internal link target "#${annotation.targetName}" was not found.`);
|
|
546
|
+
if (anchors.length > 1) throw new Error(`[@pdfme/generator] Internal link target "#${annotation.targetName}" is ambiguous because multiple schemas use that name.`);
|
|
547
|
+
addGoToLinkAnnotation({
|
|
548
|
+
pdfDoc,
|
|
549
|
+
page: annotation.page,
|
|
550
|
+
target: anchors[0],
|
|
551
|
+
rect: annotation.rect,
|
|
552
|
+
borderWidth: annotation.borderWidth
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
resetInternalLinkAnnotations(_cache);
|
|
556
|
+
};
|
|
353
557
|
var getB64BasePdf = async (customPdf) => {
|
|
354
558
|
if (typeof customPdf === "string" && !customPdf.startsWith("data:application/pdf;") && typeof window !== "undefined") {
|
|
355
559
|
if (!isUrlSafeToFetch(customPdf)) throw Error("[@pdfme/common] Invalid or unsafe URL for basePdf. Only http: and https: URLs pointing to public hosts are allowed.");
|
|
@@ -431,191 +635,6 @@ var checkGenerateProps = (data) => {
|
|
|
431
635
|
checkProps(data, GenerateProps);
|
|
432
636
|
};
|
|
433
637
|
//#endregion
|
|
434
|
-
//#region src/dynamicTemplate.ts
|
|
435
|
-
/** Floating point tolerance for comparisons */
|
|
436
|
-
var EPSILON = .01;
|
|
437
|
-
/** Calculate the content height of a page (drawable area excluding padding) */
|
|
438
|
-
var getContentHeight = (basePdf) => basePdf.height - basePdf.padding[0] - basePdf.padding[2];
|
|
439
|
-
/** Get the input value for a schema */
|
|
440
|
-
var getSchemaValue = (schema, input) => (schema.readOnly ? schema.content : input?.[schema.name]) || "";
|
|
441
|
-
/**
|
|
442
|
-
* Normalize schemas within a single page into layout items.
|
|
443
|
-
* Returns items sorted by Y coordinate with their order preserved.
|
|
444
|
-
*/
|
|
445
|
-
function normalizePageSchemas(pageSchemas, paddingTop) {
|
|
446
|
-
const items = [];
|
|
447
|
-
const orderMap = /* @__PURE__ */ new Map();
|
|
448
|
-
pageSchemas.forEach((schema, index) => {
|
|
449
|
-
const localY = Math.max(0, schema.position.y - paddingTop);
|
|
450
|
-
items.push({
|
|
451
|
-
schema: cloneDeep(schema),
|
|
452
|
-
baseY: localY,
|
|
453
|
-
height: schema.height,
|
|
454
|
-
dynamicHeights: [schema.height]
|
|
455
|
-
});
|
|
456
|
-
orderMap.set(schema.name, index);
|
|
457
|
-
});
|
|
458
|
-
items.sort((a, b) => {
|
|
459
|
-
if (Math.abs(a.baseY - b.baseY) > EPSILON) return a.baseY - b.baseY;
|
|
460
|
-
return (orderMap.get(a.schema.name) ?? 0) - (orderMap.get(b.schema.name) ?? 0);
|
|
461
|
-
});
|
|
462
|
-
return {
|
|
463
|
-
items,
|
|
464
|
-
orderMap
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Place rows on pages, splitting across pages as needed.
|
|
469
|
-
* @returns The final global Y coordinate after placement
|
|
470
|
-
*/
|
|
471
|
-
function placeRowsOnPages(schema, dynamicHeights, startGlobalY, contentHeight, paddingTop, pages) {
|
|
472
|
-
let currentRowIndex = 0;
|
|
473
|
-
let currentPageIndex = Math.floor(startGlobalY / contentHeight);
|
|
474
|
-
let currentYInPage = startGlobalY % contentHeight;
|
|
475
|
-
if (currentYInPage < 0) currentYInPage = 0;
|
|
476
|
-
let actualGlobalEndY = 0;
|
|
477
|
-
const isSplittable = dynamicHeights.length > 1;
|
|
478
|
-
while (currentRowIndex < dynamicHeights.length) {
|
|
479
|
-
while (pages.length <= currentPageIndex) pages.push([]);
|
|
480
|
-
const spaceLeft = contentHeight - currentYInPage;
|
|
481
|
-
if (dynamicHeights[currentRowIndex] > spaceLeft + EPSILON) {
|
|
482
|
-
if (!(Math.abs(spaceLeft - contentHeight) <= EPSILON)) {
|
|
483
|
-
currentPageIndex++;
|
|
484
|
-
currentYInPage = 0;
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
let chunkHeight = 0;
|
|
489
|
-
const startRowIndex = currentRowIndex;
|
|
490
|
-
while (currentRowIndex < dynamicHeights.length) {
|
|
491
|
-
const h = dynamicHeights[currentRowIndex];
|
|
492
|
-
if (currentYInPage + chunkHeight + h <= contentHeight + EPSILON) {
|
|
493
|
-
chunkHeight += h;
|
|
494
|
-
currentRowIndex++;
|
|
495
|
-
} else break;
|
|
496
|
-
}
|
|
497
|
-
const isAtPageTop = currentYInPage <= EPSILON;
|
|
498
|
-
if (isSplittable && startRowIndex === 0 && currentRowIndex === 1 && dynamicHeights.length > 1 && !isAtPageTop) {
|
|
499
|
-
currentRowIndex = 0;
|
|
500
|
-
currentPageIndex++;
|
|
501
|
-
currentYInPage = 0;
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
if (currentRowIndex === startRowIndex) {
|
|
505
|
-
chunkHeight += dynamicHeights[currentRowIndex];
|
|
506
|
-
currentRowIndex++;
|
|
507
|
-
}
|
|
508
|
-
const newSchema = {
|
|
509
|
-
...schema,
|
|
510
|
-
height: chunkHeight,
|
|
511
|
-
position: {
|
|
512
|
-
...schema.position,
|
|
513
|
-
y: currentYInPage + paddingTop
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
if (isSplittable) {
|
|
517
|
-
newSchema.__bodyRange = {
|
|
518
|
-
start: startRowIndex === 0 ? 0 : startRowIndex - 1,
|
|
519
|
-
end: currentRowIndex - 1
|
|
520
|
-
};
|
|
521
|
-
newSchema.__isSplit = startRowIndex > 0;
|
|
522
|
-
}
|
|
523
|
-
pages[currentPageIndex].push(newSchema);
|
|
524
|
-
currentYInPage += chunkHeight;
|
|
525
|
-
if (currentYInPage >= contentHeight - EPSILON) {
|
|
526
|
-
currentPageIndex++;
|
|
527
|
-
currentYInPage = 0;
|
|
528
|
-
}
|
|
529
|
-
actualGlobalEndY = currentPageIndex * contentHeight + currentYInPage;
|
|
530
|
-
}
|
|
531
|
-
return actualGlobalEndY;
|
|
532
|
-
}
|
|
533
|
-
/** Sort elements within each page by their original order */
|
|
534
|
-
function sortPagesByOrder(pages, orderMap) {
|
|
535
|
-
pages.forEach((page) => {
|
|
536
|
-
page.sort((a, b) => (orderMap.get(a.name) ?? 0) - (orderMap.get(b.name) ?? 0));
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
/** Remove trailing empty pages */
|
|
540
|
-
function removeTrailingEmptyPages(pages) {
|
|
541
|
-
while (pages.length > 1 && pages[pages.length - 1].length === 0) pages.pop();
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Process a single template page that has dynamic content.
|
|
545
|
-
* Uses the same layout algorithm as the original implementation,
|
|
546
|
-
* but scoped to a single page's schemas.
|
|
547
|
-
*/
|
|
548
|
-
function processDynamicPage(items, orderMap, contentHeight, paddingTop) {
|
|
549
|
-
const pages = [];
|
|
550
|
-
let totalYOffset = 0;
|
|
551
|
-
for (const item of items) {
|
|
552
|
-
const currentGlobalStartY = item.baseY + totalYOffset;
|
|
553
|
-
totalYOffset = placeRowsOnPages(item.schema, item.dynamicHeights, currentGlobalStartY, contentHeight, paddingTop, pages) - (item.baseY + item.height);
|
|
554
|
-
}
|
|
555
|
-
sortPagesByOrder(pages, orderMap);
|
|
556
|
-
removeTrailingEmptyPages(pages);
|
|
557
|
-
return pages;
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Process a template containing tables with dynamic heights
|
|
561
|
-
* and generate a new template with proper page breaks.
|
|
562
|
-
*
|
|
563
|
-
* Processing is done page-by-page:
|
|
564
|
-
* - Pages with height changes are processed with full layout calculations
|
|
565
|
-
* - Pages without height changes are copied as-is (no offset propagation between pages)
|
|
566
|
-
*
|
|
567
|
-
* This reduces computation cost by:
|
|
568
|
-
* 1. Limiting layout calculations to pages that need them
|
|
569
|
-
* 2. Avoiding cross-page offset propagation for static pages
|
|
570
|
-
*/
|
|
571
|
-
var getDynamicTemplate = async (arg) => {
|
|
572
|
-
const { template, input, options, _cache, getDynamicHeights } = arg;
|
|
573
|
-
const basePdf = template.basePdf;
|
|
574
|
-
if (!isBlankPdf(basePdf)) return template;
|
|
575
|
-
const contentHeight = getContentHeight(basePdf);
|
|
576
|
-
const paddingTop = basePdf.padding[0];
|
|
577
|
-
const resultPages = [];
|
|
578
|
-
const PARALLEL_LIMIT = 10;
|
|
579
|
-
for (let pageIndex = 0; pageIndex < template.schemas.length; pageIndex++) {
|
|
580
|
-
const pageSchemas = template.schemas[pageIndex];
|
|
581
|
-
const { items, orderMap } = normalizePageSchemas(pageSchemas, paddingTop);
|
|
582
|
-
for (let i = 0; i < items.length; i += PARALLEL_LIMIT) {
|
|
583
|
-
const chunk = items.slice(i, i + PARALLEL_LIMIT);
|
|
584
|
-
const chunkResults = await Promise.all(chunk.map((item) => {
|
|
585
|
-
return getDynamicHeights(getSchemaValue(item.schema, input), {
|
|
586
|
-
schema: item.schema,
|
|
587
|
-
basePdf,
|
|
588
|
-
options,
|
|
589
|
-
_cache
|
|
590
|
-
}).then((heights) => heights.length === 0 ? [0] : heights);
|
|
591
|
-
}));
|
|
592
|
-
for (let j = 0; j < chunkResults.length; j++) items[i + j].dynamicHeights = chunkResults[j];
|
|
593
|
-
}
|
|
594
|
-
const processedPages = processDynamicPage(items, orderMap, contentHeight, paddingTop);
|
|
595
|
-
resultPages.push(...processedPages);
|
|
596
|
-
}
|
|
597
|
-
removeTrailingEmptyPages(resultPages);
|
|
598
|
-
if (resultPages.length === template.schemas.length) {
|
|
599
|
-
let unchanged = true;
|
|
600
|
-
for (let i = 0; i < resultPages.length && unchanged; i++) {
|
|
601
|
-
if (resultPages[i].length !== template.schemas[i].length) {
|
|
602
|
-
unchanged = false;
|
|
603
|
-
break;
|
|
604
|
-
}
|
|
605
|
-
for (let j = 0; j < resultPages[i].length && unchanged; j++) {
|
|
606
|
-
const orig = template.schemas[i][j];
|
|
607
|
-
const result = resultPages[i][j];
|
|
608
|
-
if (Math.abs(orig.height - result.height) > EPSILON || Math.abs(orig.position.y - result.position.y) > EPSILON) unchanged = false;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
if (unchanged) return template;
|
|
612
|
-
}
|
|
613
|
-
return {
|
|
614
|
-
basePdf,
|
|
615
|
-
schemas: resultPages
|
|
616
|
-
};
|
|
617
|
-
};
|
|
618
|
-
//#endregion
|
|
619
638
|
//#region src/expression.ts
|
|
620
639
|
var expressionCache = /* @__PURE__ */ new Map();
|
|
621
640
|
/**
|
|
@@ -935,6 +954,223 @@ var replacePlaceholders = (arg) => {
|
|
|
935
954
|
});
|
|
936
955
|
};
|
|
937
956
|
//#endregion
|
|
957
|
+
//#region src/dynamicTemplate.ts
|
|
958
|
+
/** Floating point tolerance for comparisons */
|
|
959
|
+
var EPSILON = .01;
|
|
960
|
+
/** Calculate the content height of a page (drawable area excluding padding) */
|
|
961
|
+
var getContentHeight = (basePdf) => basePdf.height - basePdf.padding[0] - basePdf.padding[2];
|
|
962
|
+
/** Get the input value for a schema */
|
|
963
|
+
var getSchemaValue = (schema, input, schemas) => {
|
|
964
|
+
if (!schema.readOnly) return input?.[schema.name] || "";
|
|
965
|
+
if (schema.type !== "text" && schema.type !== "multiVariableText") return schema.content || "";
|
|
966
|
+
return replacePlaceholders({
|
|
967
|
+
content: schema.content || "",
|
|
968
|
+
variables: input,
|
|
969
|
+
schemas
|
|
970
|
+
});
|
|
971
|
+
};
|
|
972
|
+
/**
|
|
973
|
+
* Normalize schemas within a single page into layout items.
|
|
974
|
+
* Returns items sorted by Y coordinate with their order preserved.
|
|
975
|
+
*/
|
|
976
|
+
function normalizePageSchemas(pageSchemas, paddingTop) {
|
|
977
|
+
const items = [];
|
|
978
|
+
const orderMap = /* @__PURE__ */ new Map();
|
|
979
|
+
pageSchemas.forEach((schema, index) => {
|
|
980
|
+
const localY = Math.max(0, schema.position.y - paddingTop);
|
|
981
|
+
items.push({
|
|
982
|
+
schema: cloneDeep(schema),
|
|
983
|
+
baseY: localY,
|
|
984
|
+
height: schema.height,
|
|
985
|
+
dynamicLayout: { heights: [schema.height] }
|
|
986
|
+
});
|
|
987
|
+
orderMap.set(schema.name, index);
|
|
988
|
+
});
|
|
989
|
+
items.sort((a, b) => {
|
|
990
|
+
if (Math.abs(a.baseY - b.baseY) > EPSILON) return a.baseY - b.baseY;
|
|
991
|
+
return (orderMap.get(a.schema.name) ?? 0) - (orderMap.get(b.schema.name) ?? 0);
|
|
992
|
+
});
|
|
993
|
+
return {
|
|
994
|
+
items,
|
|
995
|
+
orderMap
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Place height units on pages, splitting across pages as needed.
|
|
1000
|
+
* @returns The final global Y coordinate after placement
|
|
1001
|
+
*/
|
|
1002
|
+
function placeUnitsOnPages(schema, dynamicLayout, startGlobalY, contentHeight, paddingTop, pages) {
|
|
1003
|
+
const dynamicHeights = dynamicLayout.heights;
|
|
1004
|
+
let currentUnitIndex = 0;
|
|
1005
|
+
let currentPageIndex = Math.floor(startGlobalY / contentHeight);
|
|
1006
|
+
let currentYInPage = startGlobalY % contentHeight;
|
|
1007
|
+
if (currentYInPage < 0) currentYInPage = 0;
|
|
1008
|
+
let actualGlobalEndY = 0;
|
|
1009
|
+
const isSplittable = dynamicHeights.length > 1;
|
|
1010
|
+
while (currentUnitIndex < dynamicHeights.length) {
|
|
1011
|
+
while (pages.length <= currentPageIndex) pages.push([]);
|
|
1012
|
+
const spaceLeft = contentHeight - currentYInPage;
|
|
1013
|
+
if (dynamicHeights[currentUnitIndex] > spaceLeft + EPSILON) {
|
|
1014
|
+
if (!(Math.abs(spaceLeft - contentHeight) <= EPSILON)) {
|
|
1015
|
+
currentPageIndex++;
|
|
1016
|
+
currentYInPage = 0;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
let chunkHeight = 0;
|
|
1021
|
+
const startUnitIndex = currentUnitIndex;
|
|
1022
|
+
while (currentUnitIndex < dynamicHeights.length) {
|
|
1023
|
+
const h = dynamicHeights[currentUnitIndex];
|
|
1024
|
+
if (currentYInPage + chunkHeight + h <= contentHeight + EPSILON) {
|
|
1025
|
+
chunkHeight += h;
|
|
1026
|
+
currentUnitIndex++;
|
|
1027
|
+
} else break;
|
|
1028
|
+
}
|
|
1029
|
+
const isAtPageTop = currentYInPage <= EPSILON;
|
|
1030
|
+
if (dynamicLayout.avoidFirstUnitOnly && isSplittable && startUnitIndex === 0 && currentUnitIndex === 1 && dynamicHeights.length > 1 && !isAtPageTop) {
|
|
1031
|
+
currentUnitIndex = 0;
|
|
1032
|
+
currentPageIndex++;
|
|
1033
|
+
currentYInPage = 0;
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (currentUnitIndex === startUnitIndex) {
|
|
1037
|
+
chunkHeight += dynamicHeights[currentUnitIndex];
|
|
1038
|
+
currentUnitIndex++;
|
|
1039
|
+
}
|
|
1040
|
+
const patch = dynamicLayout.patchSplitSchema?.({
|
|
1041
|
+
schema,
|
|
1042
|
+
start: startUnitIndex,
|
|
1043
|
+
end: currentUnitIndex,
|
|
1044
|
+
isSplit: startUnitIndex > 0,
|
|
1045
|
+
chunkHeight
|
|
1046
|
+
}) ?? {};
|
|
1047
|
+
const newSchema = {
|
|
1048
|
+
...schema,
|
|
1049
|
+
...patch,
|
|
1050
|
+
height: chunkHeight,
|
|
1051
|
+
position: {
|
|
1052
|
+
...schema.position,
|
|
1053
|
+
y: currentYInPage + paddingTop
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
pages[currentPageIndex].push(newSchema);
|
|
1057
|
+
currentYInPage += chunkHeight;
|
|
1058
|
+
if (currentYInPage >= contentHeight - EPSILON) {
|
|
1059
|
+
currentPageIndex++;
|
|
1060
|
+
currentYInPage = 0;
|
|
1061
|
+
}
|
|
1062
|
+
actualGlobalEndY = currentPageIndex * contentHeight + currentYInPage;
|
|
1063
|
+
}
|
|
1064
|
+
return actualGlobalEndY;
|
|
1065
|
+
}
|
|
1066
|
+
/** Sort elements within each page by their original order */
|
|
1067
|
+
function sortPagesByOrder(pages, orderMap) {
|
|
1068
|
+
pages.forEach((page) => {
|
|
1069
|
+
page.sort((a, b) => (orderMap.get(a.name) ?? 0) - (orderMap.get(b.name) ?? 0));
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
/** Remove trailing empty pages */
|
|
1073
|
+
function removeTrailingEmptyPages(pages) {
|
|
1074
|
+
while (pages.length > 1 && pages[pages.length - 1].length === 0) pages.pop();
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Process a single template page that has dynamic content.
|
|
1078
|
+
* Uses the same layout algorithm as the original implementation,
|
|
1079
|
+
* but scoped to a single page's schemas.
|
|
1080
|
+
*/
|
|
1081
|
+
function processDynamicPage(items, orderMap, contentHeight, paddingTop) {
|
|
1082
|
+
const pages = [];
|
|
1083
|
+
let totalYOffset = 0;
|
|
1084
|
+
for (const item of items) {
|
|
1085
|
+
const currentGlobalStartY = item.baseY + totalYOffset;
|
|
1086
|
+
totalYOffset = placeUnitsOnPages(item.schema, item.dynamicLayout, currentGlobalStartY, contentHeight, paddingTop, pages) - (item.baseY + item.height);
|
|
1087
|
+
}
|
|
1088
|
+
sortPagesByOrder(pages, orderMap);
|
|
1089
|
+
removeTrailingEmptyPages(pages);
|
|
1090
|
+
return pages;
|
|
1091
|
+
}
|
|
1092
|
+
var normalizeDynamicLayoutResult = (result) => {
|
|
1093
|
+
const dynamicLayout = Array.isArray(result) ? { heights: result } : result;
|
|
1094
|
+
return {
|
|
1095
|
+
...dynamicLayout,
|
|
1096
|
+
heights: dynamicLayout.heights.length === 0 ? [0] : dynamicLayout.heights
|
|
1097
|
+
};
|
|
1098
|
+
};
|
|
1099
|
+
/**
|
|
1100
|
+
* Process a template containing tables with dynamic heights
|
|
1101
|
+
* and generate a new template with proper page breaks.
|
|
1102
|
+
*
|
|
1103
|
+
* Processing is done page-by-page:
|
|
1104
|
+
* - Pages with height changes are processed with full layout calculations
|
|
1105
|
+
* - Pages without height changes are copied as-is (no offset propagation between pages)
|
|
1106
|
+
*
|
|
1107
|
+
* This reduces computation cost by:
|
|
1108
|
+
* 1. Limiting layout calculations to pages that need them
|
|
1109
|
+
* 2. Avoiding cross-page offset propagation for static pages
|
|
1110
|
+
*/
|
|
1111
|
+
var getDynamicTemplate = async (arg) => {
|
|
1112
|
+
const { template, input, options, _cache, getDynamicHeights } = arg;
|
|
1113
|
+
const basePdf = template.basePdf;
|
|
1114
|
+
if (!isBlankPdf(basePdf)) return template;
|
|
1115
|
+
const contentHeight = getContentHeight(basePdf);
|
|
1116
|
+
const paddingTop = basePdf.padding[0];
|
|
1117
|
+
const resultPages = [];
|
|
1118
|
+
const PARALLEL_LIMIT = 10;
|
|
1119
|
+
for (let pageIndex = 0; pageIndex < template.schemas.length; pageIndex++) {
|
|
1120
|
+
const pageSchemas = template.schemas[pageIndex];
|
|
1121
|
+
const { items, orderMap } = normalizePageSchemas(pageSchemas, paddingTop);
|
|
1122
|
+
for (let i = 0; i < items.length; i += PARALLEL_LIMIT) {
|
|
1123
|
+
const chunk = items.slice(i, i + PARALLEL_LIMIT);
|
|
1124
|
+
const chunkResults = await Promise.all(chunk.map((item) => {
|
|
1125
|
+
return getDynamicHeights(getSchemaValue(item.schema, input, template.schemas), {
|
|
1126
|
+
schema: item.schema,
|
|
1127
|
+
basePdf,
|
|
1128
|
+
options,
|
|
1129
|
+
_cache
|
|
1130
|
+
}).then(normalizeDynamicLayoutResult);
|
|
1131
|
+
}));
|
|
1132
|
+
for (let j = 0; j < chunkResults.length; j++) items[i + j].dynamicLayout = chunkResults[j];
|
|
1133
|
+
}
|
|
1134
|
+
const processedPages = processDynamicPage(items, orderMap, contentHeight, paddingTop);
|
|
1135
|
+
resultPages.push(...processedPages);
|
|
1136
|
+
}
|
|
1137
|
+
removeTrailingEmptyPages(resultPages);
|
|
1138
|
+
if (resultPages.length === template.schemas.length) {
|
|
1139
|
+
let unchanged = true;
|
|
1140
|
+
for (let i = 0; i < resultPages.length && unchanged; i++) {
|
|
1141
|
+
if (resultPages[i].length !== template.schemas[i].length) {
|
|
1142
|
+
unchanged = false;
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
for (let j = 0; j < resultPages[i].length && unchanged; j++) {
|
|
1146
|
+
const orig = template.schemas[i][j];
|
|
1147
|
+
const result = resultPages[i][j];
|
|
1148
|
+
if (Math.abs(orig.height - result.height) > EPSILON || Math.abs(orig.position.y - result.position.y) > EPSILON) unchanged = false;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (unchanged) return template;
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
basePdf,
|
|
1155
|
+
schemas: resultPages
|
|
1156
|
+
};
|
|
1157
|
+
};
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/splitRange.ts
|
|
1160
|
+
var createDynamicLayoutSplitRange = (unit, start, end) => ({
|
|
1161
|
+
unit,
|
|
1162
|
+
start,
|
|
1163
|
+
...end === void 0 ? {} : { end }
|
|
1164
|
+
});
|
|
1165
|
+
var getDynamicLayoutSplitRange = (schema, unit) => {
|
|
1166
|
+
const range = schema.__splitRange;
|
|
1167
|
+
if (range?.unit !== unit) return void 0;
|
|
1168
|
+
return {
|
|
1169
|
+
start: range.start,
|
|
1170
|
+
...range.end === void 0 ? {} : { end: range.end }
|
|
1171
|
+
};
|
|
1172
|
+
};
|
|
1173
|
+
//#endregion
|
|
938
1174
|
//#region src/pluginRegistry.ts
|
|
939
1175
|
/**
|
|
940
1176
|
* Wraps plugins collection with utility methods
|
|
@@ -961,6 +1197,6 @@ var pluginRegistry = (plugins) => {
|
|
|
961
1197
|
};
|
|
962
1198
|
};
|
|
963
1199
|
//#endregion
|
|
964
|
-
export { BLANK_A4_PDF, BLANK_PDF, CUSTOM_A4_PDF, DEFAULT_FONT_NAME, MM_TO_PT_RATIO, PDFME_VERSION, PT_TO_MM_RATIO, PT_TO_PX_RATIO, ZOOM, b64toUint8Array, checkDesignerProps, checkFont, checkGenerateProps, checkInputs, checkPreviewProps, checkTemplate, checkUIOptions, checkUIProps, cloneDeep, getB64BasePdf, getDefaultFont, getDynamicTemplate, getFallbackFontName, getInputFromTemplate, isBlankPdf, isHexValid, isUrlSafeToFetch, mm2pt, pluginRegistry, pt2mm, pt2px, px2mm, replacePlaceholders };
|
|
1200
|
+
export { BLANK_A4_PDF, BLANK_PDF, CUSTOM_A4_PDF, DEFAULT_FONT_NAME, MM_TO_PT_RATIO, PAGE_SIZE_PRESETS, PDFME_VERSION, PT_TO_MM_RATIO, PT_TO_PX_RATIO, ZOOM, applyInternalLinkAnnotations, b64toUint8Array, checkDesignerProps, checkFont, checkGenerateProps, checkInputs, checkPreviewProps, checkTemplate, checkUIOptions, checkUIProps, cloneDeep, createDynamicLayoutSplitRange, detectPaperSize, getB64BasePdf, getDefaultFont, getDynamicLayoutSplitRange, getDynamicTemplate, getFallbackFontName, getInputFromTemplate, getInternalLinkTarget, isBlankPdf, isHexValid, isUrlSafeToFetch, mm2pt, normalizeInternalLinkHref, normalizeLinkHref, normalizeSafeLinkUri, pluginRegistry, pt2mm, pt2px, px2mm, registerInternalLinkAnchor, registerInternalLinkAnnotation, replacePlaceholders, resetInternalLinkAnnotations, resolvePageSize };
|
|
965
1201
|
|
|
966
1202
|
//# sourceMappingURL=index.js.map
|