av6-pdf-engine 2.0.0 → 3.0.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/README.md +686 -88
- package/dist/index.d.mts +319 -23
- package/dist/index.d.ts +319 -23
- package/dist/index.js +1364 -802
- package/dist/index.mjs +1326 -801
- package/package.json +6 -1
- package/.prettierignore +0 -4
- package/.prettierrc +0 -6
- package/tmp/border-showcase.pdf +0 -0
- package/tmp/quote-details.pdf +0 -0
package/dist/index.js
CHANGED
|
@@ -31,14 +31,51 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
BARCODE_TYPES: () => BARCODE_TYPES,
|
|
34
|
+
DEFAULT_PIE_OPTIONS: () => DEFAULT_PIE_OPTIONS,
|
|
34
35
|
PdfEngineError: () => PdfEngineError,
|
|
35
36
|
PdfEngineErrorCode: () => PdfEngineErrorCode,
|
|
36
37
|
QR_ERROR_LEVEL: () => QR_ERROR_LEVEL,
|
|
38
|
+
buildPieSlicePath: () => buildPieSlicePath,
|
|
39
|
+
calcPercentagePie: () => calcPercentagePie,
|
|
40
|
+
calcPieData: () => calcPieData,
|
|
41
|
+
computeColumnPixelWidths: () => computeColumnPixelWidths,
|
|
42
|
+
contentEnv: () => contentEnv,
|
|
43
|
+
createBlockRenderer: () => createBlockRenderer,
|
|
44
|
+
createBottomLimitForContent: () => createBottomLimitForContent,
|
|
45
|
+
createEnsureSpaceFor: () => createEnsureSpaceFor,
|
|
46
|
+
createInitialContext: () => createInitialContext,
|
|
47
|
+
createMeasureBlockHeight: () => createMeasureBlockHeight,
|
|
48
|
+
createProcessSignatureBlock: () => createProcessSignatureBlock,
|
|
49
|
+
createStartNewPageLayout: () => createStartNewPageLayout,
|
|
50
|
+
drawFooter: () => drawFooter,
|
|
51
|
+
drawHeader: () => drawHeader,
|
|
52
|
+
drawPageBackground: () => drawPageBackground,
|
|
53
|
+
drawSignatureBlock: () => drawSignatureBlock,
|
|
54
|
+
drawStyledText: () => drawStyledText,
|
|
55
|
+
drawWatermarkForPage: () => drawWatermarkForPage,
|
|
56
|
+
finishPage: () => finishPage,
|
|
57
|
+
generateBarcodeBuffer: () => generateBarcodeBuffer,
|
|
58
|
+
generateQrBuffer: () => generateQrBuffer,
|
|
59
|
+
getBottomLimitForContent: () => getBottomLimitForContent,
|
|
60
|
+
getFontNameForText: () => getFontNameForText,
|
|
61
|
+
hasPadding: () => hasPadding,
|
|
37
62
|
images: () => images,
|
|
38
63
|
isPdfEngineError: () => isPdfEngineError,
|
|
64
|
+
mapBarcodeTypeToBcid: () => mapBarcodeTypeToBcid,
|
|
65
|
+
materializeImagesInBlocks: () => materializeImagesInBlocks,
|
|
66
|
+
materializeQrAndBarcodesInBlocks: () => materializeQrAndBarcodesInBlocks,
|
|
67
|
+
mergeStyleDefs: () => mergeStyleDefs,
|
|
68
|
+
normalizeImageSrc: () => normalizeImageSrc,
|
|
39
69
|
renderCustomPdf: () => renderCustomPdf,
|
|
40
70
|
renderCustomPdfToBuffer: () => renderCustomPdfToBuffer,
|
|
41
|
-
|
|
71
|
+
resolveBlockPadding: () => resolveBlockPadding,
|
|
72
|
+
resolveFooterPadding: () => resolveFooterPadding,
|
|
73
|
+
resolveHeaderPadding: () => resolveHeaderPadding,
|
|
74
|
+
resolveSpacing: () => resolveSpacing,
|
|
75
|
+
resolveTableCell: () => resolveTableCell,
|
|
76
|
+
resolveTextBlock: () => resolveTextBlock,
|
|
77
|
+
toPdfEngineError: () => toPdfEngineError,
|
|
78
|
+
watermarkUsesLast: () => watermarkUsesLast
|
|
42
79
|
});
|
|
43
80
|
module.exports = __toCommonJS(index_exports);
|
|
44
81
|
|
|
@@ -267,12 +304,11 @@ var processImageBlock = (doc, ctx, block, y, env, ensureSpaceFor, defaultImage)
|
|
|
267
304
|
const availableWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
268
305
|
const mt = block.marginTop ?? 0;
|
|
269
306
|
const mb = block.marginBottom ?? 0;
|
|
270
|
-
const baseY = y ?? ctx.currentY;
|
|
271
|
-
const startY = baseY + mt;
|
|
272
307
|
const heightNeeded = mt + imgHeight + mb;
|
|
273
308
|
if (y === null) {
|
|
274
309
|
ensureSpaceFor(heightNeeded, env);
|
|
275
310
|
}
|
|
311
|
+
const startY = (y ?? ctx.currentY) + mt;
|
|
276
312
|
let x = baseLeft;
|
|
277
313
|
if (block.align === "center") {
|
|
278
314
|
x = baseLeft + (availableWidth - imgWidth) / 2;
|
|
@@ -348,7 +384,7 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
348
384
|
...base,
|
|
349
385
|
style: styleNames
|
|
350
386
|
});
|
|
351
|
-
const
|
|
387
|
+
const measureTextHeight2 = (tb, width) => {
|
|
352
388
|
const fontName = getFontNameForText(tb);
|
|
353
389
|
doc.font(fontName);
|
|
354
390
|
doc.fontSize(tb.fontSize ?? 10);
|
|
@@ -381,10 +417,10 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
381
417
|
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
382
418
|
const keyBase = buildKeyBlock(item);
|
|
383
419
|
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
384
|
-
const keyHeight =
|
|
420
|
+
const keyHeight = measureTextHeight2(keyResolved, colWidth);
|
|
385
421
|
const valueBase = buildValueBlock(item);
|
|
386
422
|
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
387
|
-
const valueHeight =
|
|
423
|
+
const valueHeight = measureTextHeight2(valueResolved, colWidth);
|
|
388
424
|
const rowHeight = keyHeight + keyValueGap + valueHeight;
|
|
389
425
|
colHeight += rowHeight + rowGap;
|
|
390
426
|
}
|
|
@@ -404,12 +440,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
404
440
|
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
405
441
|
const keyBase = buildKeyBlock(item);
|
|
406
442
|
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
407
|
-
const keyHeight =
|
|
443
|
+
const keyHeight = measureTextHeight2(keyResolved, keyWidthPx);
|
|
408
444
|
const valueBase = buildValueBlock(item);
|
|
409
445
|
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
410
446
|
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
411
447
|
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
412
|
-
const valueHeight =
|
|
448
|
+
const valueHeight = measureTextHeight2(valueResolved, valueWidth);
|
|
413
449
|
const cellHeight = Math.max(keyHeight, valueHeight);
|
|
414
450
|
if (cellHeight > rowHeight) rowHeight = cellHeight;
|
|
415
451
|
}
|
|
@@ -437,12 +473,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
437
473
|
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
438
474
|
const keyBlock = buildKeyBlock(item);
|
|
439
475
|
const keyResolved = resolveStyled(keyBlock, keyStyleNames);
|
|
440
|
-
const keyHeight =
|
|
476
|
+
const keyHeight = measureTextHeight2(keyResolved, colWidth);
|
|
441
477
|
drawStyledText(doc, keyResolved, colX, colY, colWidth);
|
|
442
478
|
colY += keyHeight + keyValueGap;
|
|
443
479
|
const valueBlock = buildValueBlock(item);
|
|
444
480
|
const valueResolved = resolveStyled(valueBlock, valueStyleNames);
|
|
445
|
-
const valueHeight =
|
|
481
|
+
const valueHeight = measureTextHeight2(valueResolved, colWidth);
|
|
446
482
|
drawStyledText(doc, valueResolved, colX, colY, colWidth);
|
|
447
483
|
colY += valueHeight + rowGap;
|
|
448
484
|
}
|
|
@@ -462,12 +498,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
462
498
|
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
463
499
|
const keyBase = buildKeyBlock(item);
|
|
464
500
|
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
465
|
-
const keyHeight =
|
|
501
|
+
const keyHeight = measureTextHeight2(keyResolved, keyWidthPx);
|
|
466
502
|
const valueBase = buildValueBlock(item);
|
|
467
503
|
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
468
504
|
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
469
505
|
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
470
|
-
const valueHeight =
|
|
506
|
+
const valueHeight = measureTextHeight2(valueResolved, valueWidth);
|
|
471
507
|
const cellHeight = Math.max(keyHeight, valueHeight);
|
|
472
508
|
if (cellHeight > rowHeight) rowHeight = cellHeight;
|
|
473
509
|
}
|
|
@@ -508,6 +544,149 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
|
|
|
508
544
|
return endY;
|
|
509
545
|
};
|
|
510
546
|
|
|
547
|
+
// src/renderer-engine/blocks/list.ts
|
|
548
|
+
var DEFAULT_BULLET = "\u2022";
|
|
549
|
+
var DEFAULT_MARKER_GAP = 6;
|
|
550
|
+
var DEFAULT_ITEM_GAP = 4;
|
|
551
|
+
var MARKER_PADDING = 2;
|
|
552
|
+
var normalizeListItem = (item) => typeof item === "string" ? { text: item } : item;
|
|
553
|
+
var buildTextBlock = (list, item) => ({
|
|
554
|
+
type: "text",
|
|
555
|
+
text: item.text ?? "",
|
|
556
|
+
fontSize: item.fontSize ?? list.fontSize,
|
|
557
|
+
lineGap: item.lineGap ?? list.lineGap,
|
|
558
|
+
color: item.color ?? list.color,
|
|
559
|
+
bold: item.bold,
|
|
560
|
+
italic: item.italic,
|
|
561
|
+
underline: item.underline,
|
|
562
|
+
strike: item.strike,
|
|
563
|
+
link: item.link,
|
|
564
|
+
font: item.font,
|
|
565
|
+
style: item.style ?? list.style
|
|
566
|
+
});
|
|
567
|
+
var getMarker = (list, index) => {
|
|
568
|
+
if ((list.listStyle ?? "bullet") === "number") {
|
|
569
|
+
const start = list.start ?? 1;
|
|
570
|
+
return `${start + index}.`;
|
|
571
|
+
}
|
|
572
|
+
return list.bullet ?? DEFAULT_BULLET;
|
|
573
|
+
};
|
|
574
|
+
var applyTextOptions = (doc, tb) => {
|
|
575
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
576
|
+
doc.font(getFontNameForText(tb));
|
|
577
|
+
if (tb.color) doc.fillColor(tb.color);
|
|
578
|
+
else doc.fillColor("black");
|
|
579
|
+
};
|
|
580
|
+
var measureTextHeight = (doc, tb, width) => {
|
|
581
|
+
applyTextOptions(doc, tb);
|
|
582
|
+
const lineGap = tb.lineGap ?? 4;
|
|
583
|
+
return doc.heightOfString(tb.text ?? "", { width, lineGap });
|
|
584
|
+
};
|
|
585
|
+
var measureMarkerWidth = (doc, marker, tb) => {
|
|
586
|
+
applyTextOptions(doc, tb);
|
|
587
|
+
return doc.widthOfString(marker) + MARKER_PADDING;
|
|
588
|
+
};
|
|
589
|
+
var computeMarkerWidth = (doc, list, items, tbSample) => {
|
|
590
|
+
if (list.markerWidth !== void 0) return list.markerWidth;
|
|
591
|
+
let maxW = 0;
|
|
592
|
+
for (let i = 0; i < items.length; i++) {
|
|
593
|
+
const marker = getMarker(list, i);
|
|
594
|
+
const w = measureMarkerWidth(doc, marker, tbSample);
|
|
595
|
+
if (w > maxW) maxW = w;
|
|
596
|
+
}
|
|
597
|
+
return maxW;
|
|
598
|
+
};
|
|
599
|
+
var measureItemHeight = (doc, list, item, index, markerWidth, textWidth, styles) => {
|
|
600
|
+
const tb = resolveTextBlock(styles, buildTextBlock(list, item));
|
|
601
|
+
const marker = getMarker(list, index);
|
|
602
|
+
const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
|
|
603
|
+
const markerH = measureTextHeight(doc, markerTb, markerWidth);
|
|
604
|
+
const textH = measureTextHeight(doc, tb, textWidth);
|
|
605
|
+
const lineGap = tb.lineGap ?? 4;
|
|
606
|
+
return Math.max(markerH, textH) + lineGap;
|
|
607
|
+
};
|
|
608
|
+
var measureListHeight = (doc, styles, block, env) => {
|
|
609
|
+
const items = (block.items ?? []).map(normalizeListItem);
|
|
610
|
+
const mt = block.marginTop ?? 0;
|
|
611
|
+
const mb = block.marginBottom ?? 0;
|
|
612
|
+
const itemGap = block.itemGap ?? DEFAULT_ITEM_GAP;
|
|
613
|
+
const markerGap = block.markerGap ?? DEFAULT_MARKER_GAP;
|
|
614
|
+
if (!items.length) return mt + mb;
|
|
615
|
+
const localLeft = block.marginLeft ?? 0;
|
|
616
|
+
const localRight = block.marginRight ?? 0;
|
|
617
|
+
const innerWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
618
|
+
const firstResolved = resolveTextBlock(styles, buildTextBlock(block, items[0]));
|
|
619
|
+
const markerWidth = computeMarkerWidth(doc, block, items, firstResolved);
|
|
620
|
+
const textWidth = Math.max(innerWidth - markerWidth - markerGap, 1);
|
|
621
|
+
let total = mt;
|
|
622
|
+
for (let i = 0; i < items.length; i++) {
|
|
623
|
+
total += measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
|
|
624
|
+
if (i < items.length - 1) total += itemGap;
|
|
625
|
+
}
|
|
626
|
+
return total + mb;
|
|
627
|
+
};
|
|
628
|
+
var drawListText = (doc, tb, x, y, width) => {
|
|
629
|
+
applyTextOptions(doc, tb);
|
|
630
|
+
const lineGap = tb.lineGap ?? 4;
|
|
631
|
+
doc.text(tb.text ?? "", x, y, {
|
|
632
|
+
width,
|
|
633
|
+
align: tb.align ?? "left",
|
|
634
|
+
underline: !!tb.underline,
|
|
635
|
+
strike: !!tb.strike,
|
|
636
|
+
link: tb.link,
|
|
637
|
+
lineGap
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
var processListBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
|
|
641
|
+
const items = (block.items ?? []).map(normalizeListItem);
|
|
642
|
+
const mt = block.marginTop ?? 0;
|
|
643
|
+
const mb = block.marginBottom ?? 0;
|
|
644
|
+
const itemGap = block.itemGap ?? DEFAULT_ITEM_GAP;
|
|
645
|
+
const markerGap = block.markerGap ?? DEFAULT_MARKER_GAP;
|
|
646
|
+
const localLeft = block.marginLeft ?? 0;
|
|
647
|
+
const localRight = block.marginRight ?? 0;
|
|
648
|
+
const xLeft = env.marginLeft + localLeft;
|
|
649
|
+
const innerWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
650
|
+
if (!items.length) {
|
|
651
|
+
const endY2 = (y ?? ctx.currentY) + mt + mb;
|
|
652
|
+
if (y === null) ctx.currentY = endY2;
|
|
653
|
+
return endY2;
|
|
654
|
+
}
|
|
655
|
+
const firstResolved = resolveTextBlock(styles, buildTextBlock(block, items[0]));
|
|
656
|
+
const markerWidth = computeMarkerWidth(doc, block, items, firstResolved);
|
|
657
|
+
const textX = xLeft + markerWidth + markerGap;
|
|
658
|
+
const textWidth = Math.max(innerWidth - markerWidth - markerGap, 1);
|
|
659
|
+
if (y !== null) {
|
|
660
|
+
let drawY = y + mt;
|
|
661
|
+
for (let i = 0; i < items.length; i++) {
|
|
662
|
+
if (i > 0) drawY += itemGap;
|
|
663
|
+
const tb = resolveTextBlock(styles, buildTextBlock(block, items[i]));
|
|
664
|
+
const marker = getMarker(block, i);
|
|
665
|
+
const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
|
|
666
|
+
const itemHeight = measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
|
|
667
|
+
drawListText(doc, markerTb, xLeft, drawY, markerWidth);
|
|
668
|
+
drawListText(doc, tb, textX, drawY, textWidth);
|
|
669
|
+
drawY += itemHeight;
|
|
670
|
+
}
|
|
671
|
+
return drawY + mb;
|
|
672
|
+
}
|
|
673
|
+
for (let i = 0; i < items.length; i++) {
|
|
674
|
+
const leading = i === 0 ? mt : itemGap;
|
|
675
|
+
const itemHeight = measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
|
|
676
|
+
ensureSpaceFor(leading + itemHeight, env);
|
|
677
|
+
const drawY = ctx.currentY + leading;
|
|
678
|
+
const tb = resolveTextBlock(styles, buildTextBlock(block, items[i]));
|
|
679
|
+
const marker = getMarker(block, i);
|
|
680
|
+
const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
|
|
681
|
+
drawListText(doc, markerTb, xLeft, drawY, markerWidth);
|
|
682
|
+
drawListText(doc, tb, textX, drawY, textWidth);
|
|
683
|
+
ctx.currentY = drawY + itemHeight;
|
|
684
|
+
}
|
|
685
|
+
const endY = ctx.currentY + mb;
|
|
686
|
+
ctx.currentY = endY;
|
|
687
|
+
return endY;
|
|
688
|
+
};
|
|
689
|
+
|
|
511
690
|
// src/renderer-engine/blocks/line.ts
|
|
512
691
|
var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
513
692
|
const localLeft = block.marginLeft ?? 0;
|
|
@@ -516,13 +695,12 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
|
516
695
|
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
517
696
|
const mt = block.marginTop ?? 0;
|
|
518
697
|
const mb = block.marginBottom ?? 0;
|
|
519
|
-
const baseY = y ?? ctx.currentY;
|
|
520
|
-
const startY = baseY + mt;
|
|
521
698
|
const lineWidth = block.lineWidth ?? 1;
|
|
522
699
|
const heightNeeded = mt + lineWidth + mb;
|
|
523
700
|
if (y === null) {
|
|
524
701
|
ensureSpaceFor(heightNeeded, env);
|
|
525
702
|
}
|
|
703
|
+
const startY = (y ?? ctx.currentY) + mt;
|
|
526
704
|
doc.save();
|
|
527
705
|
if (block.color) {
|
|
528
706
|
doc.strokeColor(block.color);
|
|
@@ -535,7 +713,99 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
|
535
713
|
return newY;
|
|
536
714
|
};
|
|
537
715
|
|
|
716
|
+
// src/renderer-engine/blocks/shape.ts
|
|
717
|
+
var processShapeBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
718
|
+
const mt = block.marginTop ?? 0;
|
|
719
|
+
const mb = block.marginBottom ?? 0;
|
|
720
|
+
const ml = block.marginLeft ?? 0;
|
|
721
|
+
const mr = block.marginRight ?? 0;
|
|
722
|
+
const lineWidth = block.lineWidth ?? 1;
|
|
723
|
+
const width = block.width ?? Math.max(env.innerWidth - ml - mr, 1);
|
|
724
|
+
const height = block.height ?? lineWidth;
|
|
725
|
+
const heightNeeded = mt + height + mb;
|
|
726
|
+
if (y === null) {
|
|
727
|
+
ensureSpaceFor(heightNeeded, env);
|
|
728
|
+
}
|
|
729
|
+
const startY = (y ?? ctx.currentY) + mt;
|
|
730
|
+
const availableWidth = Math.max(env.innerWidth - ml - mr, 1);
|
|
731
|
+
let x = env.marginLeft + ml;
|
|
732
|
+
if (block.align === "center") {
|
|
733
|
+
x = env.marginLeft + ml + (availableWidth - width) / 2;
|
|
734
|
+
} else if (block.align === "right") {
|
|
735
|
+
x = env.marginLeft + ml + availableWidth - width;
|
|
736
|
+
}
|
|
737
|
+
const drawY = startY;
|
|
738
|
+
doc.save();
|
|
739
|
+
if (typeof block.opacity === "number") {
|
|
740
|
+
doc.opacity(block.opacity);
|
|
741
|
+
}
|
|
742
|
+
doc.lineWidth(lineWidth);
|
|
743
|
+
if (block.strokeColor) {
|
|
744
|
+
doc.strokeColor(block.strokeColor);
|
|
745
|
+
}
|
|
746
|
+
if (block.fillColor) {
|
|
747
|
+
doc.fillColor(block.fillColor);
|
|
748
|
+
}
|
|
749
|
+
switch (block.shape) {
|
|
750
|
+
case "rect":
|
|
751
|
+
doc.rect(x, drawY, width, height);
|
|
752
|
+
break;
|
|
753
|
+
case "roundedRect":
|
|
754
|
+
doc.roundedRect(x, drawY, width, height, block.radius ?? 6);
|
|
755
|
+
break;
|
|
756
|
+
case "circle": {
|
|
757
|
+
const radius = block.radius ?? Math.min(width, height) / 2;
|
|
758
|
+
doc.circle(x + radius, drawY + radius, radius);
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
case "ellipse":
|
|
762
|
+
doc.ellipse(x + width / 2, drawY + height / 2, width / 2, height / 2);
|
|
763
|
+
break;
|
|
764
|
+
case "line":
|
|
765
|
+
doc.moveTo(x, drawY).lineTo(x + width, drawY);
|
|
766
|
+
break;
|
|
767
|
+
case "polygon":
|
|
768
|
+
if (block.points?.length) {
|
|
769
|
+
const [first, ...rest] = block.points;
|
|
770
|
+
doc.moveTo(x + first.x, drawY + first.y);
|
|
771
|
+
rest.forEach((point) => {
|
|
772
|
+
doc.lineTo(x + point.x, drawY + point.y);
|
|
773
|
+
});
|
|
774
|
+
doc.closePath();
|
|
775
|
+
}
|
|
776
|
+
break;
|
|
777
|
+
case "path":
|
|
778
|
+
if (block.path) {
|
|
779
|
+
const safePath = block.path.trim().replace(/[\n\r\t]+/g, " ").replace(/\s+/g, " ");
|
|
780
|
+
if (safePath.includes("undefined") || safePath.includes("NaN")) {
|
|
781
|
+
throw new Error(`Invalid shape path: ${safePath}`);
|
|
782
|
+
}
|
|
783
|
+
doc.translate(x, drawY);
|
|
784
|
+
doc.path(safePath);
|
|
785
|
+
}
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
if (block.fillColor && block.strokeColor) {
|
|
789
|
+
doc.fillAndStroke(block.fillColor, block.strokeColor);
|
|
790
|
+
} else if (block.fillColor) {
|
|
791
|
+
doc.fill(block.fillColor);
|
|
792
|
+
} else {
|
|
793
|
+
doc.stroke();
|
|
794
|
+
}
|
|
795
|
+
doc.restore();
|
|
796
|
+
const newY = startY + height + mb;
|
|
797
|
+
if (y === null) {
|
|
798
|
+
ctx.currentY = newY;
|
|
799
|
+
}
|
|
800
|
+
return newY;
|
|
801
|
+
};
|
|
802
|
+
|
|
538
803
|
// src/renderer-engine/blocks/table.ts
|
|
804
|
+
var shouldSkipHeaderOnlyTable = (table, rows) => {
|
|
805
|
+
const headerRows = table.headerRows ?? 0;
|
|
806
|
+
if (headerRows <= 0) return false;
|
|
807
|
+
return rows.length <= headerRows;
|
|
808
|
+
};
|
|
539
809
|
var normalizeSpan = (value) => {
|
|
540
810
|
if (!value || value < 1) return 1;
|
|
541
811
|
return Math.floor(value);
|
|
@@ -690,6 +960,9 @@ var measureTableHeight = (doc, table, env, styles, computeColumnPixelWidths2, me
|
|
|
690
960
|
const localRight = table.marginRight ?? 0;
|
|
691
961
|
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
692
962
|
const rows = normalizeTableRows(table);
|
|
963
|
+
if (shouldSkipHeaderOnlyTable(table, rows)) {
|
|
964
|
+
return 0;
|
|
965
|
+
}
|
|
693
966
|
const totalCols = table.widths.length;
|
|
694
967
|
const colWidths = computeColumnPixelWidths2(table.widths, width);
|
|
695
968
|
const rowHeights = [];
|
|
@@ -766,6 +1039,9 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
|
|
|
766
1039
|
};
|
|
767
1040
|
const headerRows = table.headerRows ?? 0;
|
|
768
1041
|
const rows = normalizeTableRows(table);
|
|
1042
|
+
if (shouldSkipHeaderOnlyTable(table, rows)) {
|
|
1043
|
+
return baseY;
|
|
1044
|
+
}
|
|
769
1045
|
const buildPlacementAndHeights = (rows2) => {
|
|
770
1046
|
const placedRows = [];
|
|
771
1047
|
const rowHeights = [];
|
|
@@ -1045,12 +1321,12 @@ var drawCellBorder = (doc, border, x, y, width, height) => {
|
|
|
1045
1321
|
// src/renderer-engine/blocks/text.ts
|
|
1046
1322
|
var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
|
|
1047
1323
|
const tb = resolveTextBlock(styles, block);
|
|
1324
|
+
const mt = tb.marginTop ?? 0;
|
|
1325
|
+
const mb = tb.marginBottom ?? 0;
|
|
1048
1326
|
const localLeft = tb.marginLeft ?? 0;
|
|
1049
1327
|
const localRight = tb.marginRight ?? 0;
|
|
1050
1328
|
const xLeft = env.marginLeft + localLeft;
|
|
1051
1329
|
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
1052
|
-
const baseY = y ?? ctx.currentY;
|
|
1053
|
-
const startY = baseY;
|
|
1054
1330
|
doc.fontSize(tb.fontSize ?? 10);
|
|
1055
1331
|
const isBold = !!tb.bold;
|
|
1056
1332
|
const isItalic = !!tb.italic;
|
|
@@ -1064,11 +1340,12 @@ var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
|
|
|
1064
1340
|
doc.font(fontName);
|
|
1065
1341
|
if (tb.color) doc.fillColor(tb.color);
|
|
1066
1342
|
else doc.fillColor("black");
|
|
1067
|
-
const
|
|
1343
|
+
const lineGap = tb.lineGap ?? 4;
|
|
1344
|
+
const textHeight = doc.heightOfString(tb.text, { width, lineGap });
|
|
1068
1345
|
if (y === null) {
|
|
1069
|
-
|
|
1070
|
-
ensureSpaceFor(textHeight + gapCheck, env);
|
|
1346
|
+
ensureSpaceFor(mt + textHeight + lineGap + mb, env);
|
|
1071
1347
|
}
|
|
1348
|
+
const startY = (y ?? ctx.currentY) + mt;
|
|
1072
1349
|
doc.text(tb.text, xLeft, startY, {
|
|
1073
1350
|
width,
|
|
1074
1351
|
align: tb.align ?? "left",
|
|
@@ -1076,414 +1353,82 @@ var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
|
|
|
1076
1353
|
strike: !!tb.strike,
|
|
1077
1354
|
link: tb.link
|
|
1078
1355
|
});
|
|
1079
|
-
const
|
|
1080
|
-
const newY = startY + textHeight + gap;
|
|
1356
|
+
const newY = doc.y + lineGap + mb;
|
|
1081
1357
|
if (y === null) ctx.currentY = newY;
|
|
1082
1358
|
return newY;
|
|
1083
1359
|
};
|
|
1084
1360
|
|
|
1085
|
-
// src/renderer-engine/utils/block-renderer.ts
|
|
1086
|
-
function createBlockRenderer(deps) {
|
|
1087
|
-
const { doc, ctx, styles, computeColumnPixelWidths: computeColumnPixelWidths2, finishPage: finishPage2, processSignatureBlock, defaultImage } = deps;
|
|
1088
|
-
const bottomLimitForContent = createBottomLimitForContent(doc, ctx);
|
|
1089
|
-
const ensureSpaceFor = createEnsureSpaceFor(ctx, bottomLimitForContent, finishPage2);
|
|
1090
|
-
const measureBlockHeight = createMeasureBlockHeight({
|
|
1091
|
-
doc,
|
|
1092
|
-
styles,
|
|
1093
|
-
computeColumnPixelWidths: computeColumnPixelWidths2
|
|
1094
|
-
});
|
|
1095
|
-
const renderBlock = (block, y, env) => {
|
|
1096
|
-
if (block.visible === false) {
|
|
1097
|
-
return y ?? ctx.currentY;
|
|
1098
|
-
}
|
|
1099
|
-
if ((block.backgroundColor || block.backgroundImage) && block.type !== "pageBreak" && block.type !== "signature") {
|
|
1100
|
-
const startY = y ?? ctx.currentY;
|
|
1101
|
-
const blockHeight = measureBlockHeight(block, env);
|
|
1102
|
-
const opacity = block.backgroundOpacity ?? 1;
|
|
1103
|
-
doc.save();
|
|
1104
|
-
if (block.backgroundColor) {
|
|
1105
|
-
doc.fillOpacity(opacity).rect(env.marginLeft, startY, env.innerWidth, blockHeight).fill(block.backgroundColor);
|
|
1106
|
-
doc.fillOpacity(1);
|
|
1107
|
-
}
|
|
1108
|
-
if (block.backgroundImage) {
|
|
1109
|
-
try {
|
|
1110
|
-
doc.image(block.backgroundImage, env.marginLeft, startY, {
|
|
1111
|
-
width: env.innerWidth,
|
|
1112
|
-
height: blockHeight
|
|
1113
|
-
});
|
|
1114
|
-
} catch (_) {
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
doc.restore();
|
|
1118
|
-
}
|
|
1119
|
-
switch (block.type) {
|
|
1120
|
-
case "text":
|
|
1121
|
-
return processTextBlock(doc, ctx, styles, block, y, env, ensureSpaceFor);
|
|
1122
|
-
case "image":
|
|
1123
|
-
return processImageBlock(doc, ctx, block, y, env, ensureSpaceFor, defaultImage);
|
|
1124
|
-
case "qr": {
|
|
1125
|
-
const qb = block;
|
|
1126
|
-
const imageLike = {
|
|
1127
|
-
type: "image",
|
|
1128
|
-
src: qb.src,
|
|
1129
|
-
width: qb.size,
|
|
1130
|
-
height: qb.size,
|
|
1131
|
-
align: qb.align,
|
|
1132
|
-
marginTop: qb.marginTop,
|
|
1133
|
-
marginBottom: qb.marginBottom,
|
|
1134
|
-
marginLeft: qb.marginLeft,
|
|
1135
|
-
marginRight: qb.marginRight
|
|
1136
|
-
};
|
|
1137
|
-
return processImageBlock(doc, ctx, imageLike, y, env, ensureSpaceFor, defaultImage);
|
|
1138
|
-
}
|
|
1139
|
-
case "barcode":
|
|
1140
|
-
return processBarcodeBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
1141
|
-
case "line":
|
|
1142
|
-
return processLineBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
1143
|
-
case "table":
|
|
1144
|
-
return processTableBlock(
|
|
1145
|
-
doc,
|
|
1146
|
-
ctx,
|
|
1147
|
-
styles,
|
|
1148
|
-
block,
|
|
1149
|
-
y,
|
|
1150
|
-
env,
|
|
1151
|
-
computeColumnPixelWidths2,
|
|
1152
|
-
bottomLimitForContent,
|
|
1153
|
-
finishPage2,
|
|
1154
|
-
(b, blockY, blockEnv) => renderBlock(b, blockY, blockEnv),
|
|
1155
|
-
measureBlockHeight
|
|
1156
|
-
);
|
|
1157
|
-
case "columns":
|
|
1158
|
-
return processColumnsBlock(
|
|
1159
|
-
ctx,
|
|
1160
|
-
block,
|
|
1161
|
-
y,
|
|
1162
|
-
env,
|
|
1163
|
-
computeColumnPixelWidths2,
|
|
1164
|
-
renderBlock,
|
|
1165
|
-
ensureSpaceFor,
|
|
1166
|
-
measureBlockHeight
|
|
1167
|
-
);
|
|
1168
|
-
case "keyValueGrid":
|
|
1169
|
-
return processKeyValueGridBlock(
|
|
1170
|
-
doc,
|
|
1171
|
-
ctx,
|
|
1172
|
-
styles,
|
|
1173
|
-
block,
|
|
1174
|
-
y,
|
|
1175
|
-
env,
|
|
1176
|
-
computeColumnPixelWidths2,
|
|
1177
|
-
ensureSpaceFor
|
|
1178
|
-
);
|
|
1179
|
-
case "signature":
|
|
1180
|
-
if (!env.allowPageBreak) {
|
|
1181
|
-
throw new PdfEngineError({
|
|
1182
|
-
code: "PDF_ERROR_SIGNATURE_NOT_IN_MAIN_FLOW" /* PDF_ERROR_SIGNATURE_NOT_IN_MAIN_FLOW */,
|
|
1183
|
-
message: "Signature block is only allowed in main content flow.",
|
|
1184
|
-
statusCode: 400,
|
|
1185
|
-
details: { blockType: "signature" }
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
if (y !== null) {
|
|
1189
|
-
throw new PdfEngineError({
|
|
1190
|
-
code: "PDF_ERROR_SIGNATURE_EXPLICIT_Y_FORBIDDEN" /* PDF_ERROR_SIGNATURE_EXPLICIT_Y_FORBIDDEN */,
|
|
1191
|
-
message: "Signature block must be part of main flow, not drawn at explicit Y.",
|
|
1192
|
-
statusCode: 400,
|
|
1193
|
-
details: { blockType: "signature", y }
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1196
|
-
processSignatureBlock(block);
|
|
1197
|
-
return ctx.currentY;
|
|
1198
|
-
case "pageBreak":
|
|
1199
|
-
if (!env.allowPageBreak || ctx.inFooter) {
|
|
1200
|
-
return y ?? ctx.currentY;
|
|
1201
|
-
}
|
|
1202
|
-
finishPage2(true);
|
|
1203
|
-
return ctx.currentY;
|
|
1204
|
-
default:
|
|
1205
|
-
return ctx.currentY;
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
const renderBlockArray = (blocks, startY, env) => {
|
|
1209
|
-
let localY = startY;
|
|
1210
|
-
for (const block of blocks) {
|
|
1211
|
-
if (block.visible === false) {
|
|
1212
|
-
continue;
|
|
1213
|
-
}
|
|
1214
|
-
localY = renderBlock(block, localY, env);
|
|
1215
|
-
}
|
|
1216
|
-
return localY;
|
|
1217
|
-
};
|
|
1218
|
-
return {
|
|
1219
|
-
renderBlock,
|
|
1220
|
-
renderBlockArray
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// src/renderer-engine/utils/context.ts
|
|
1225
|
-
function createInitialContext(doc) {
|
|
1226
|
-
return {
|
|
1227
|
-
pageNumber: 1,
|
|
1228
|
-
currentY: doc.page.margins.top,
|
|
1229
|
-
signatureBlock: null,
|
|
1230
|
-
signatureTopY: null,
|
|
1231
|
-
signatureHeight: 0,
|
|
1232
|
-
signaturePlaced: false,
|
|
1233
|
-
afterSignature: false,
|
|
1234
|
-
inFooter: false
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
1361
|
// src/renderer-engine/utils/ensure-space.ts
|
|
1239
|
-
function createEnsureSpaceFor(ctx, bottomLimitForContent, finishPage2) {
|
|
1362
|
+
function createEnsureSpaceFor(ctx, doc, bottomLimitForContent, finishPage2) {
|
|
1240
1363
|
return (heightNeeded, env) => {
|
|
1241
1364
|
if (!env.allowPageBreak || ctx.inFooter) return;
|
|
1242
1365
|
const bottomLimit = bottomLimitForContent();
|
|
1366
|
+
const fullContentHeight = bottomLimit - doc.page.margins.top;
|
|
1367
|
+
if (heightNeeded > fullContentHeight) {
|
|
1368
|
+
console.warn(
|
|
1369
|
+
`[pdf-engine] Block height (${Math.round(heightNeeded)}pt) exceeds page content area (${Math.round(fullContentHeight)}pt). Content will overflow.`
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1243
1372
|
if (ctx.currentY + heightNeeded > bottomLimit) {
|
|
1244
1373
|
finishPage2(true);
|
|
1245
1374
|
}
|
|
1246
1375
|
};
|
|
1247
1376
|
}
|
|
1248
1377
|
|
|
1249
|
-
// src/renderer-engine/utils/
|
|
1250
|
-
var
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1378
|
+
// src/renderer-engine/utils/spacing.ts
|
|
1379
|
+
var emptySpacing = () => ({
|
|
1380
|
+
top: 0,
|
|
1381
|
+
right: 0,
|
|
1382
|
+
bottom: 0,
|
|
1383
|
+
left: 0
|
|
1254
1384
|
});
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
if (def.watermark && watermarkUsesLast(mode)) {
|
|
1268
|
-
const isLast = !addNewPage;
|
|
1269
|
-
drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, isLast);
|
|
1270
|
-
}
|
|
1271
|
-
if (ctx.signatureBlock && ctx.signatureTopY !== null) {
|
|
1272
|
-
const env = contentEnv(doc);
|
|
1273
|
-
drawSignatureBlock(doc, ctx.signatureBlock, ctx.signatureTopY, env, renderBlockArray);
|
|
1385
|
+
var resolveSpacing = (value, overrides) => {
|
|
1386
|
+
const result = emptySpacing();
|
|
1387
|
+
if (typeof value === "number") {
|
|
1388
|
+
result.top = value;
|
|
1389
|
+
result.right = value;
|
|
1390
|
+
result.bottom = value;
|
|
1391
|
+
result.left = value;
|
|
1392
|
+
} else if (value) {
|
|
1393
|
+
result.top = value.top ?? 0;
|
|
1394
|
+
result.right = value.right ?? 0;
|
|
1395
|
+
result.bottom = value.bottom ?? 0;
|
|
1396
|
+
result.left = value.left ?? 0;
|
|
1274
1397
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
}
|
|
1310
|
-
if (Array.isArray(input)) {
|
|
1311
|
-
return { blocks: input };
|
|
1312
|
-
}
|
|
1313
|
-
return null;
|
|
1314
|
-
}
|
|
1315
|
-
function drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray) {
|
|
1316
|
-
const footerConfig = def.footer;
|
|
1317
|
-
if (!footerConfig) return;
|
|
1318
|
-
const layout = normalizeFooter(footerConfig, ctx, doc);
|
|
1319
|
-
if (!layout || !layout.blocks.length) return;
|
|
1320
|
-
if (layout.visible === false) return;
|
|
1321
|
-
const bandHeight = footerBandHeight;
|
|
1322
|
-
if (!bandHeight) return;
|
|
1323
|
-
const { marginTop = 4, backgroundColor, backgroundImage, blocks } = layout;
|
|
1324
|
-
const footerMarginLeft = layout.marginLeft ?? 0;
|
|
1325
|
-
const footerMarginRight = layout.marginRight ?? 0;
|
|
1326
|
-
const contentWidth = doc.page.width - footerMarginLeft - footerMarginRight;
|
|
1327
|
-
const footerEnv = {
|
|
1328
|
-
marginLeft: footerMarginLeft,
|
|
1329
|
-
innerWidth: contentWidth,
|
|
1330
|
-
allowPageBreak: false
|
|
1331
|
-
};
|
|
1332
|
-
const pageHeight = doc.page.height;
|
|
1333
|
-
const bandTop = pageHeight - bandHeight;
|
|
1334
|
-
if (backgroundColor) {
|
|
1335
|
-
doc.save();
|
|
1336
|
-
doc.rect(0, bandTop, doc.page.width, bandHeight).fill(backgroundColor);
|
|
1337
|
-
doc.restore();
|
|
1338
|
-
}
|
|
1339
|
-
if (backgroundImage) {
|
|
1340
|
-
try {
|
|
1341
|
-
doc.image(backgroundImage, 0, bandTop, {
|
|
1342
|
-
width: doc.page.width,
|
|
1343
|
-
height: bandHeight
|
|
1344
|
-
});
|
|
1345
|
-
} catch (e) {
|
|
1346
|
-
console.warn("Failed to load footer background image:", e);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
doc.save();
|
|
1350
|
-
doc.rect(footerMarginLeft, bandTop, contentWidth, bandHeight).clip();
|
|
1351
|
-
doc.translate(0, bandTop);
|
|
1352
|
-
ctx.inFooter = true;
|
|
1353
|
-
const localStartY = marginTop;
|
|
1354
|
-
renderBlockArray(blocks, localStartY, footerEnv);
|
|
1355
|
-
ctx.inFooter = false;
|
|
1356
|
-
doc.restore();
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
// src/renderer-engine/utils/header.ts
|
|
1360
|
-
function drawHeader(doc, def, headerBandHeight, header, renderBlockArray) {
|
|
1361
|
-
if (!header || !header.blocks.length) return;
|
|
1362
|
-
if (header.visible === false) return;
|
|
1363
|
-
if (!headerBandHeight) return;
|
|
1364
|
-
const bandTop = 0;
|
|
1365
|
-
const bandHeight = def.margins.top;
|
|
1366
|
-
const headerMarginLeft = header.marginLeft ?? 0;
|
|
1367
|
-
const headerMarginRight = header.marginRight ?? 0;
|
|
1368
|
-
const contentWidth = doc.page.width - headerMarginLeft - headerMarginRight;
|
|
1369
|
-
const headerEnv = {
|
|
1370
|
-
marginLeft: headerMarginLeft,
|
|
1371
|
-
innerWidth: contentWidth,
|
|
1372
|
-
allowPageBreak: false
|
|
1373
|
-
};
|
|
1374
|
-
if (header.backgroundColor) {
|
|
1375
|
-
doc.save();
|
|
1376
|
-
doc.rect(0, bandTop, doc.page.width, bandHeight).fill(header.backgroundColor);
|
|
1377
|
-
doc.restore();
|
|
1378
|
-
}
|
|
1379
|
-
if (header.backgroundImage) {
|
|
1380
|
-
try {
|
|
1381
|
-
doc.image(header.backgroundImage, 0, bandTop, {
|
|
1382
|
-
width: doc.page.width,
|
|
1383
|
-
height: bandHeight
|
|
1384
|
-
});
|
|
1385
|
-
} catch (e) {
|
|
1386
|
-
console.warn("Failed to load header background image:", e);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
doc.save();
|
|
1390
|
-
doc.rect(headerMarginLeft, bandTop, contentWidth, bandHeight).clip();
|
|
1391
|
-
const startY = bandTop + (header.marginTop ?? 0);
|
|
1392
|
-
renderBlockArray(header.blocks, startY, headerEnv);
|
|
1393
|
-
doc.restore();
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// src/renderer-engine/utils/image-loader.ts
|
|
1397
|
-
var import_axios = __toESM(require("axios"));
|
|
1398
|
-
async function normalizeImageSrc(src, fallback) {
|
|
1399
|
-
if (!src) return fallback ?? src;
|
|
1400
|
-
if (Buffer.isBuffer(src)) return src;
|
|
1401
|
-
if (src.startsWith("data:")) {
|
|
1402
|
-
const commaIdx = src.indexOf(",");
|
|
1403
|
-
if (commaIdx !== -1) {
|
|
1404
|
-
return Buffer.from(src.slice(commaIdx + 1), "base64");
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
1408
|
-
try {
|
|
1409
|
-
const res = await import_axios.default.get(src, { responseType: "arraybuffer" });
|
|
1410
|
-
return Buffer.from(res.data);
|
|
1411
|
-
} catch (e) {
|
|
1412
|
-
if (fallback !== void 0) {
|
|
1413
|
-
console.warn(`Failed to fetch remote image "${src}", using default image.`);
|
|
1414
|
-
return fallback;
|
|
1415
|
-
}
|
|
1416
|
-
throw toPdfEngineError(e, {
|
|
1417
|
-
code: "PDF_ERROR_IMAGE_FETCH_FAILED" /* PDF_ERROR_IMAGE_FETCH_FAILED */,
|
|
1418
|
-
message: "Failed to fetch remote image for PDF.",
|
|
1419
|
-
statusCode: 422,
|
|
1420
|
-
details: { url: src },
|
|
1421
|
-
retryable: false
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
return src;
|
|
1426
|
-
}
|
|
1427
|
-
async function materializeImagesInBlocks(blocks, fallback) {
|
|
1428
|
-
const out = [];
|
|
1429
|
-
for (const block of blocks) {
|
|
1430
|
-
const blockAny = block;
|
|
1431
|
-
if (blockAny.backgroundImage) {
|
|
1432
|
-
blockAny.backgroundImage = await normalizeImageSrc(blockAny.backgroundImage, fallback);
|
|
1433
|
-
}
|
|
1434
|
-
if (block.type === "image") {
|
|
1435
|
-
const img = { ...block };
|
|
1436
|
-
img.src = await normalizeImageSrc(img.src, fallback);
|
|
1437
|
-
out.push(img);
|
|
1438
|
-
} else if (block.type === "columns") {
|
|
1439
|
-
out.push({
|
|
1440
|
-
...block,
|
|
1441
|
-
columns: await Promise.all(block.columns.map((col) => materializeImagesInBlocks(col, fallback)))
|
|
1442
|
-
});
|
|
1443
|
-
} else if (block.type === "signature" && block.blocks) {
|
|
1444
|
-
out.push({
|
|
1445
|
-
...block,
|
|
1446
|
-
blocks: await materializeImagesInBlocks(block.blocks, fallback)
|
|
1447
|
-
});
|
|
1448
|
-
} else if (block.type === "table") {
|
|
1449
|
-
const tb = block;
|
|
1450
|
-
const newBody = await Promise.all(
|
|
1451
|
-
tb.body.map(
|
|
1452
|
-
async (entry) => {
|
|
1453
|
-
const materializeCell = async (cell) => {
|
|
1454
|
-
if (!cell.blocks?.length) return cell;
|
|
1455
|
-
return { ...cell, blocks: await materializeImagesInBlocks(cell.blocks, fallback) };
|
|
1456
|
-
};
|
|
1457
|
-
if (Array.isArray(entry)) {
|
|
1458
|
-
return await Promise.all(entry.map(materializeCell));
|
|
1459
|
-
}
|
|
1460
|
-
if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
|
|
1461
|
-
return { ...entry, content: await Promise.all(entry.content.map(materializeCell)) };
|
|
1462
|
-
}
|
|
1463
|
-
return entry;
|
|
1464
|
-
}
|
|
1465
|
-
)
|
|
1466
|
-
);
|
|
1467
|
-
out.push({ ...tb, body: newBody });
|
|
1468
|
-
} else {
|
|
1469
|
-
out.push(block);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
return out;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// src/renderer-engine/utils/layout.ts
|
|
1476
|
-
function computeColumnPixelWidths(widths, totalWidth) {
|
|
1477
|
-
let fixedTotal = 0;
|
|
1478
|
-
let starCount = 0;
|
|
1479
|
-
widths.forEach((w) => {
|
|
1480
|
-
if (w === "*") starCount++;
|
|
1481
|
-
else fixedTotal += w;
|
|
1482
|
-
});
|
|
1483
|
-
const remaining = Math.max(totalWidth - fixedTotal, 0);
|
|
1484
|
-
const starWidth = starCount > 0 ? remaining / starCount : 0;
|
|
1485
|
-
return widths.map((w) => w === "*" ? starWidth : w);
|
|
1486
|
-
}
|
|
1398
|
+
return {
|
|
1399
|
+
top: overrides?.top ?? result.top,
|
|
1400
|
+
right: overrides?.right ?? result.right,
|
|
1401
|
+
bottom: overrides?.bottom ?? result.bottom,
|
|
1402
|
+
left: overrides?.left ?? result.left
|
|
1403
|
+
};
|
|
1404
|
+
};
|
|
1405
|
+
var resolveBlockPadding = (block) => {
|
|
1406
|
+
return resolveSpacing(block.padding, {
|
|
1407
|
+
top: block.paddingTop,
|
|
1408
|
+
right: block.paddingRight,
|
|
1409
|
+
bottom: block.paddingBottom,
|
|
1410
|
+
left: block.paddingLeft
|
|
1411
|
+
});
|
|
1412
|
+
};
|
|
1413
|
+
var resolveHeaderPadding = (header) => {
|
|
1414
|
+
return resolveSpacing(header.padding, {
|
|
1415
|
+
top: header.paddingTop,
|
|
1416
|
+
right: header.paddingRight,
|
|
1417
|
+
bottom: header.paddingBottom,
|
|
1418
|
+
left: header.paddingLeft
|
|
1419
|
+
});
|
|
1420
|
+
};
|
|
1421
|
+
var resolveFooterPadding = (footer) => {
|
|
1422
|
+
return resolveSpacing(footer.padding, {
|
|
1423
|
+
top: footer.paddingTop,
|
|
1424
|
+
right: footer.paddingRight,
|
|
1425
|
+
bottom: footer.paddingBottom,
|
|
1426
|
+
left: footer.paddingLeft
|
|
1427
|
+
});
|
|
1428
|
+
};
|
|
1429
|
+
var hasPadding = (padding) => {
|
|
1430
|
+
return padding.top > 0 || padding.right > 0 || padding.bottom > 0 || padding.left > 0;
|
|
1431
|
+
};
|
|
1487
1432
|
|
|
1488
1433
|
// src/renderer-engine/utils/styles.ts
|
|
1489
1434
|
function mergeStyleDefs(styles, names) {
|
|
@@ -1579,76 +1524,57 @@ var drawStyledText = (doc, tb, x, y, width) => {
|
|
|
1579
1524
|
// src/renderer-engine/utils/measure-block-height.ts
|
|
1580
1525
|
function createMeasureBlockHeight(deps) {
|
|
1581
1526
|
const { doc, styles, computeColumnPixelWidths: computeColumnPixelWidths2 } = deps;
|
|
1582
|
-
const measureText = (
|
|
1583
|
-
const tb = resolveTextBlock(styles,
|
|
1584
|
-
const
|
|
1527
|
+
const measureText = (block, env) => {
|
|
1528
|
+
const tb = resolveTextBlock(styles, block);
|
|
1529
|
+
const mt = tb.marginTop ?? 0;
|
|
1530
|
+
const mb = tb.marginBottom ?? 0;
|
|
1531
|
+
const localLeft = tb.marginLeft ?? 0;
|
|
1532
|
+
const localRight = tb.marginRight ?? 0;
|
|
1533
|
+
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
1585
1534
|
doc.font(getFontNameForText(tb));
|
|
1586
1535
|
doc.fontSize(tb.fontSize ?? 10);
|
|
1587
|
-
const
|
|
1588
|
-
const
|
|
1589
|
-
return
|
|
1536
|
+
const lineGap = tb.lineGap ?? 4;
|
|
1537
|
+
const height = doc.heightOfString(tb.text ?? "", { width, lineGap });
|
|
1538
|
+
return mt + height + lineGap + mb;
|
|
1590
1539
|
};
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1593
|
-
const h = b.height ?? 50;
|
|
1594
|
-
return measureImageLike(b.marginTop, b.marginBottom, h);
|
|
1540
|
+
const measureImage = (block) => {
|
|
1541
|
+
return (block.marginTop ?? 0) + (block.height ?? 50) + (block.marginBottom ?? 0);
|
|
1595
1542
|
};
|
|
1596
|
-
const measureQr = (
|
|
1597
|
-
|
|
1598
|
-
return measureImageLike(b.marginTop, b.marginBottom, size);
|
|
1543
|
+
const measureQr = (block) => {
|
|
1544
|
+
return (block.marginTop ?? 0) + (block.size ?? 80) + (block.marginBottom ?? 0);
|
|
1599
1545
|
};
|
|
1600
|
-
const measureBarcode = (
|
|
1601
|
-
|
|
1602
|
-
return measureImageLike(b.marginTop, b.marginBottom, h);
|
|
1546
|
+
const measureBarcode = (block) => {
|
|
1547
|
+
return (block.marginTop ?? 0) + (block.height ?? 40) + (block.marginBottom ?? 0);
|
|
1603
1548
|
};
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
const mt = b.marginTop ?? 0;
|
|
1607
|
-
const mb = b.marginBottom ?? 0;
|
|
1608
|
-
return mt + lw + mb;
|
|
1549
|
+
const measureShape = (block) => {
|
|
1550
|
+
return (block.marginTop ?? 0) + (block.height ?? block.lineWidth ?? 1) + (block.marginBottom ?? 0);
|
|
1609
1551
|
};
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
const mb = b.marginBottom ?? 0;
|
|
1613
|
-
const ml = b.marginLeft ?? 0;
|
|
1614
|
-
const mr = b.marginRight ?? 0;
|
|
1615
|
-
const innerWidth = Math.max(env.innerWidth - ml - mr, 1);
|
|
1616
|
-
const fakeEnv = {
|
|
1617
|
-
marginLeft: 0,
|
|
1618
|
-
innerWidth,
|
|
1619
|
-
allowPageBreak: false
|
|
1620
|
-
};
|
|
1621
|
-
const h = measureTableHeight(doc, b, fakeEnv, styles, computeColumnPixelWidths2, measure);
|
|
1622
|
-
return mt + h + mb;
|
|
1552
|
+
const measureLine = (block) => {
|
|
1553
|
+
return (block.marginTop ?? 0) + (block.lineWidth ?? 1) + (block.marginBottom ?? 0);
|
|
1623
1554
|
};
|
|
1624
|
-
const measureColumns = (
|
|
1625
|
-
const mt =
|
|
1626
|
-
const mb =
|
|
1627
|
-
const blockLeft =
|
|
1628
|
-
const blockRight =
|
|
1555
|
+
const measureColumns = (block, env) => {
|
|
1556
|
+
const mt = block.marginTop ?? 0;
|
|
1557
|
+
const mb = block.marginBottom ?? 0;
|
|
1558
|
+
const blockLeft = block.marginLeft ?? 0;
|
|
1559
|
+
const blockRight = block.marginRight ?? 0;
|
|
1629
1560
|
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
1630
|
-
const cols =
|
|
1561
|
+
const cols = block.columns ?? [];
|
|
1631
1562
|
const n = cols.length;
|
|
1632
1563
|
if (!n) return mt + mb;
|
|
1633
1564
|
let colWidths;
|
|
1634
|
-
const mode =
|
|
1635
|
-
let gap;
|
|
1565
|
+
const mode = block.mode ?? "fixedGap";
|
|
1636
1566
|
if (mode === "spaceBetween" && n > 1) {
|
|
1637
|
-
if (
|
|
1638
|
-
colWidths = computeColumnPixelWidths2(
|
|
1567
|
+
if (block.widths && block.widths.length === n) {
|
|
1568
|
+
colWidths = computeColumnPixelWidths2(block.widths, totalWidth);
|
|
1639
1569
|
} else {
|
|
1640
1570
|
colWidths = Array(n).fill(totalWidth / n);
|
|
1641
1571
|
}
|
|
1642
|
-
const totalColsWidth = colWidths.reduce((a, x) => a + x, 0);
|
|
1643
|
-
const remaining = Math.max(totalWidth - totalColsWidth, 0);
|
|
1644
|
-
gap = remaining / (n - 1);
|
|
1645
|
-
void gap;
|
|
1646
1572
|
} else {
|
|
1647
|
-
gap =
|
|
1573
|
+
const gap = block.gap ?? 20;
|
|
1648
1574
|
const totalGapsWidth = gap * (n - 1);
|
|
1649
1575
|
const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
|
|
1650
|
-
if (
|
|
1651
|
-
colWidths = computeColumnPixelWidths2(
|
|
1576
|
+
if (block.widths && block.widths.length === n) {
|
|
1577
|
+
colWidths = computeColumnPixelWidths2(block.widths, widthForCols);
|
|
1652
1578
|
} else {
|
|
1653
1579
|
colWidths = Array(n).fill(widthForCols / n);
|
|
1654
1580
|
}
|
|
@@ -1661,109 +1587,709 @@ function createMeasureBlockHeight(deps) {
|
|
|
1661
1587
|
innerWidth: colWidths[i],
|
|
1662
1588
|
allowPageBreak: false
|
|
1663
1589
|
};
|
|
1664
|
-
let
|
|
1590
|
+
let colHeight = 0;
|
|
1665
1591
|
for (const child of colBlocks) {
|
|
1666
1592
|
if (!child || child.visible === false) continue;
|
|
1667
|
-
|
|
1593
|
+
colHeight += measure(child, colEnv);
|
|
1594
|
+
}
|
|
1595
|
+
heights.push(colHeight);
|
|
1596
|
+
}
|
|
1597
|
+
return mt + Math.max(...heights, 0) + mb;
|
|
1598
|
+
};
|
|
1599
|
+
const measureTable = (block, env) => {
|
|
1600
|
+
const mt = block.marginTop ?? 0;
|
|
1601
|
+
const mb = block.marginBottom ?? 0;
|
|
1602
|
+
const ml = block.marginLeft ?? 0;
|
|
1603
|
+
const mr = block.marginRight ?? 0;
|
|
1604
|
+
const innerWidth = Math.max(env.innerWidth - ml - mr, 1);
|
|
1605
|
+
const fakeEnv = {
|
|
1606
|
+
marginLeft: 0,
|
|
1607
|
+
innerWidth,
|
|
1608
|
+
allowPageBreak: false
|
|
1609
|
+
};
|
|
1610
|
+
const height = measureTableHeight(doc, block, fakeEnv, styles, computeColumnPixelWidths2, measure);
|
|
1611
|
+
return mt + height + mb;
|
|
1612
|
+
};
|
|
1613
|
+
const measureKeyValueGrid = (block, env) => {
|
|
1614
|
+
const mt = block.marginTop ?? 0;
|
|
1615
|
+
const mb = block.marginBottom ?? 0;
|
|
1616
|
+
const ml = block.marginLeft ?? 0;
|
|
1617
|
+
const mr = block.marginRight ?? 0;
|
|
1618
|
+
const totalWidth = Math.max(env.innerWidth - ml - mr, 1);
|
|
1619
|
+
const cols = block.columns ?? [];
|
|
1620
|
+
const colCount = cols.length;
|
|
1621
|
+
if (!colCount) return mt + mb;
|
|
1622
|
+
const rowGap = block.rowGap ?? 4;
|
|
1623
|
+
const keyValueGap = block.verticalKeyValueGap ?? 2;
|
|
1624
|
+
const separatorText = block.separator ?? "";
|
|
1625
|
+
const separatorWidth = separatorText ? doc.widthOfString(separatorText) : 0;
|
|
1626
|
+
let colWidths;
|
|
1627
|
+
if (block.columnWidths && block.columnWidths.length === colCount) {
|
|
1628
|
+
colWidths = computeColumnPixelWidths2(
|
|
1629
|
+
block.columnWidths.map((w) => w ?? "*"),
|
|
1630
|
+
totalWidth
|
|
1631
|
+
);
|
|
1632
|
+
} else {
|
|
1633
|
+
colWidths = Array(colCount).fill(totalWidth / colCount);
|
|
1634
|
+
}
|
|
1635
|
+
const measureTextHeight2 = (text, width) => {
|
|
1636
|
+
doc.fontSize(10);
|
|
1637
|
+
return doc.heightOfString(text ?? "", { width }) + 4;
|
|
1638
|
+
};
|
|
1639
|
+
let totalHeight = mt;
|
|
1640
|
+
if (block.orientation === "vertical") {
|
|
1641
|
+
let maxColHeight = 0;
|
|
1642
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1643
|
+
const col = cols[colIndex] ?? [];
|
|
1644
|
+
const colWidth = colWidths[colIndex];
|
|
1645
|
+
let colHeight = 0;
|
|
1646
|
+
for (const item of col) {
|
|
1647
|
+
const keyHeight = measureTextHeight2(item.key ?? "", colWidth);
|
|
1648
|
+
const valueHeight = measureTextHeight2(item.value ?? "", colWidth);
|
|
1649
|
+
colHeight += keyHeight + keyValueGap + valueHeight + rowGap;
|
|
1650
|
+
}
|
|
1651
|
+
maxColHeight = Math.max(maxColHeight, colHeight);
|
|
1652
|
+
}
|
|
1653
|
+
totalHeight += maxColHeight;
|
|
1654
|
+
} else {
|
|
1655
|
+
const maxRows = Math.max(...cols.map((c) => c ? c.length : 0), 0);
|
|
1656
|
+
for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
|
|
1657
|
+
let rowHeight = 0;
|
|
1658
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1659
|
+
const item = cols[colIndex]?.[rowIndex];
|
|
1660
|
+
if (!item) continue;
|
|
1661
|
+
const colWidth = colWidths[colIndex];
|
|
1662
|
+
const keyWidth = block.keyWidth === "*" ? Math.max(colWidth * 0.35, 20) : block.keyWidth ?? 80;
|
|
1663
|
+
const keyHeight = measureTextHeight2(item.key ?? "", keyWidth);
|
|
1664
|
+
const valueXInsideCol = keyWidth + (separatorText ? separatorWidth + 4 : 4);
|
|
1665
|
+
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
1666
|
+
const valueHeight = measureTextHeight2(item.value ?? "", valueWidth);
|
|
1667
|
+
rowHeight = Math.max(rowHeight, keyHeight, valueHeight);
|
|
1668
|
+
}
|
|
1669
|
+
if (rowHeight > 0) {
|
|
1670
|
+
totalHeight += rowHeight + rowGap;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return totalHeight + mb;
|
|
1675
|
+
};
|
|
1676
|
+
const stripBoxModelProps = (block) => {
|
|
1677
|
+
return {
|
|
1678
|
+
...block,
|
|
1679
|
+
marginTop: 0,
|
|
1680
|
+
marginBottom: 0,
|
|
1681
|
+
marginLeft: 0,
|
|
1682
|
+
marginRight: 0,
|
|
1683
|
+
backgroundColor: void 0,
|
|
1684
|
+
backgroundImage: void 0,
|
|
1685
|
+
backgroundOpacity: void 0,
|
|
1686
|
+
backgroundBlocks: void 0,
|
|
1687
|
+
padding: void 0,
|
|
1688
|
+
paddingTop: void 0,
|
|
1689
|
+
paddingRight: void 0,
|
|
1690
|
+
paddingBottom: void 0,
|
|
1691
|
+
paddingLeft: void 0
|
|
1692
|
+
};
|
|
1693
|
+
};
|
|
1694
|
+
const measureRaw = (block, env) => {
|
|
1695
|
+
switch (block.type) {
|
|
1696
|
+
case "text":
|
|
1697
|
+
return measureText(block, env);
|
|
1698
|
+
case "image":
|
|
1699
|
+
return measureImage(block);
|
|
1700
|
+
case "qr":
|
|
1701
|
+
return measureQr(block);
|
|
1702
|
+
case "barcode":
|
|
1703
|
+
return measureBarcode(block);
|
|
1704
|
+
case "line":
|
|
1705
|
+
return measureLine(block);
|
|
1706
|
+
case "shape":
|
|
1707
|
+
return measureShape(block);
|
|
1708
|
+
case "columns":
|
|
1709
|
+
return measureColumns(block, env);
|
|
1710
|
+
case "table":
|
|
1711
|
+
return measureTable(block, env);
|
|
1712
|
+
case "keyValueGrid":
|
|
1713
|
+
return measureKeyValueGrid(block, env);
|
|
1714
|
+
case "list":
|
|
1715
|
+
return measureListHeight(doc, styles, block, env);
|
|
1716
|
+
case "signature":
|
|
1717
|
+
return block.height ?? 0;
|
|
1718
|
+
case "pageBreak":
|
|
1719
|
+
return 0;
|
|
1720
|
+
default:
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
const measure = (block, env) => {
|
|
1725
|
+
if (!block || block.visible === false) return 0;
|
|
1726
|
+
if (env.isBackgroundLayer) {
|
|
1727
|
+
return measureRaw(block, env);
|
|
1728
|
+
}
|
|
1729
|
+
const padding = resolveBlockPadding(block);
|
|
1730
|
+
const hasBoxBackground = !!block.backgroundColor || !!block.backgroundImage || !!block.backgroundBlocks?.length;
|
|
1731
|
+
const shouldUseBoxModel = hasPadding(padding) || hasBoxBackground;
|
|
1732
|
+
if (!shouldUseBoxModel) {
|
|
1733
|
+
return measureRaw(block, env);
|
|
1734
|
+
}
|
|
1735
|
+
const marginTop = block.marginTop ?? 0;
|
|
1736
|
+
const marginBottom = block.marginBottom ?? 0;
|
|
1737
|
+
const marginLeft = block.marginLeft ?? 0;
|
|
1738
|
+
const marginRight = block.marginRight ?? 0;
|
|
1739
|
+
const outerWidth = Math.max(env.innerWidth - marginLeft - marginRight, 1);
|
|
1740
|
+
const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
|
|
1741
|
+
const innerEnv = {
|
|
1742
|
+
...env,
|
|
1743
|
+
marginLeft: 0,
|
|
1744
|
+
innerWidth
|
|
1745
|
+
};
|
|
1746
|
+
const innerBlock = stripBoxModelProps(block);
|
|
1747
|
+
const innerHeight = measureRaw(innerBlock, innerEnv);
|
|
1748
|
+
return marginTop + padding.top + innerHeight + padding.bottom + marginBottom;
|
|
1749
|
+
};
|
|
1750
|
+
return (block, env) => measure(block, env);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/renderer-engine/utils/page-limit.ts
|
|
1754
|
+
function createBottomLimitForContent(doc, ctx) {
|
|
1755
|
+
return () => {
|
|
1756
|
+
const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
|
|
1757
|
+
return ctx.signatureTopY ?? pageBottomForContent;
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/renderer-engine/utils/block-renderer.ts
|
|
1762
|
+
function createBlockRenderer(deps) {
|
|
1763
|
+
const { doc, ctx, styles, computeColumnPixelWidths: computeColumnPixelWidths2, finishPage: finishPage2, processSignatureBlock, defaultImage } = deps;
|
|
1764
|
+
const bottomLimitForContent = createBottomLimitForContent(doc, ctx);
|
|
1765
|
+
const ensureSpaceFor = createEnsureSpaceFor(ctx, doc, bottomLimitForContent, finishPage2);
|
|
1766
|
+
const measureBlockHeight = createMeasureBlockHeight({
|
|
1767
|
+
doc,
|
|
1768
|
+
styles,
|
|
1769
|
+
computeColumnPixelWidths: computeColumnPixelWidths2
|
|
1770
|
+
});
|
|
1771
|
+
const stripBoxModelProps = (block) => {
|
|
1772
|
+
return {
|
|
1773
|
+
...block,
|
|
1774
|
+
marginTop: 0,
|
|
1775
|
+
marginBottom: 0,
|
|
1776
|
+
marginLeft: 0,
|
|
1777
|
+
marginRight: 0,
|
|
1778
|
+
backgroundColor: void 0,
|
|
1779
|
+
backgroundImage: void 0,
|
|
1780
|
+
backgroundOpacity: void 0,
|
|
1781
|
+
backgroundBlocks: void 0,
|
|
1782
|
+
padding: void 0,
|
|
1783
|
+
paddingTop: void 0,
|
|
1784
|
+
paddingRight: void 0,
|
|
1785
|
+
paddingBottom: void 0,
|
|
1786
|
+
paddingLeft: void 0
|
|
1787
|
+
};
|
|
1788
|
+
};
|
|
1789
|
+
const renderBlockCore = (block, y, env) => {
|
|
1790
|
+
if (block.visible === false) {
|
|
1791
|
+
return y ?? ctx.currentY;
|
|
1792
|
+
}
|
|
1793
|
+
switch (block.type) {
|
|
1794
|
+
case "text":
|
|
1795
|
+
return processTextBlock(doc, ctx, styles, block, y, env, ensureSpaceFor);
|
|
1796
|
+
case "image":
|
|
1797
|
+
return processImageBlock(doc, ctx, block, y, env, ensureSpaceFor, defaultImage);
|
|
1798
|
+
case "qr": {
|
|
1799
|
+
const qb = block;
|
|
1800
|
+
const imageLike = {
|
|
1801
|
+
type: "image",
|
|
1802
|
+
src: qb.src,
|
|
1803
|
+
width: qb.size,
|
|
1804
|
+
height: qb.size,
|
|
1805
|
+
align: qb.align,
|
|
1806
|
+
marginTop: qb.marginTop,
|
|
1807
|
+
marginBottom: qb.marginBottom,
|
|
1808
|
+
marginLeft: qb.marginLeft,
|
|
1809
|
+
marginRight: qb.marginRight
|
|
1810
|
+
};
|
|
1811
|
+
return processImageBlock(doc, ctx, imageLike, y, env, ensureSpaceFor, defaultImage);
|
|
1812
|
+
}
|
|
1813
|
+
case "barcode":
|
|
1814
|
+
return processBarcodeBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
1815
|
+
case "line":
|
|
1816
|
+
return processLineBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
1817
|
+
case "shape":
|
|
1818
|
+
return processShapeBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
1819
|
+
case "columns":
|
|
1820
|
+
return processColumnsBlock(
|
|
1821
|
+
ctx,
|
|
1822
|
+
block,
|
|
1823
|
+
y,
|
|
1824
|
+
env,
|
|
1825
|
+
computeColumnPixelWidths2,
|
|
1826
|
+
renderBlock,
|
|
1827
|
+
ensureSpaceFor,
|
|
1828
|
+
measureBlockHeight
|
|
1829
|
+
);
|
|
1830
|
+
case "table":
|
|
1831
|
+
return processTableBlock(
|
|
1832
|
+
doc,
|
|
1833
|
+
ctx,
|
|
1834
|
+
styles,
|
|
1835
|
+
block,
|
|
1836
|
+
y,
|
|
1837
|
+
env,
|
|
1838
|
+
computeColumnPixelWidths2,
|
|
1839
|
+
bottomLimitForContent,
|
|
1840
|
+
finishPage2,
|
|
1841
|
+
renderBlock,
|
|
1842
|
+
measureBlockHeight
|
|
1843
|
+
);
|
|
1844
|
+
case "keyValueGrid":
|
|
1845
|
+
return processKeyValueGridBlock(
|
|
1846
|
+
doc,
|
|
1847
|
+
ctx,
|
|
1848
|
+
styles,
|
|
1849
|
+
block,
|
|
1850
|
+
y,
|
|
1851
|
+
env,
|
|
1852
|
+
computeColumnPixelWidths2,
|
|
1853
|
+
ensureSpaceFor
|
|
1854
|
+
);
|
|
1855
|
+
case "list":
|
|
1856
|
+
return processListBlock(
|
|
1857
|
+
doc,
|
|
1858
|
+
ctx,
|
|
1859
|
+
styles,
|
|
1860
|
+
block,
|
|
1861
|
+
y,
|
|
1862
|
+
env,
|
|
1863
|
+
ensureSpaceFor
|
|
1864
|
+
);
|
|
1865
|
+
case "pageBreak":
|
|
1866
|
+
finishPage2(true);
|
|
1867
|
+
return ctx.currentY;
|
|
1868
|
+
case "signature":
|
|
1869
|
+
processSignatureBlock(block);
|
|
1870
|
+
return y ?? ctx.currentY;
|
|
1871
|
+
default:
|
|
1872
|
+
throw new PdfEngineError({
|
|
1873
|
+
code: "PDF_ERROR_BLOCK_UNSUPPORTED" /* PDF_ERROR_BLOCK_UNSUPPORTED */,
|
|
1874
|
+
message: `Unsupported block type: ${block.type}`,
|
|
1875
|
+
statusCode: 422,
|
|
1876
|
+
details: { block }
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
const renderBlock = (block, y, env) => {
|
|
1881
|
+
if (block.visible === false) {
|
|
1882
|
+
return y ?? ctx.currentY;
|
|
1883
|
+
}
|
|
1884
|
+
if (env.isBackgroundLayer) {
|
|
1885
|
+
return renderBlockCore(block, y, env);
|
|
1886
|
+
}
|
|
1887
|
+
const padding = resolveBlockPadding(block);
|
|
1888
|
+
const hasBoxBackground = !!block.backgroundColor || !!block.backgroundImage || !!block.backgroundBlocks?.length;
|
|
1889
|
+
const shouldUseBoxModel = hasPadding(padding) || hasBoxBackground;
|
|
1890
|
+
if (!shouldUseBoxModel) {
|
|
1891
|
+
return renderBlockCore(block, y, env);
|
|
1892
|
+
}
|
|
1893
|
+
const marginTop = block.marginTop ?? 0;
|
|
1894
|
+
const marginBottom = block.marginBottom ?? 0;
|
|
1895
|
+
const marginLeft = block.marginLeft ?? 0;
|
|
1896
|
+
const marginRight = block.marginRight ?? 0;
|
|
1897
|
+
const outerX = env.marginLeft + marginLeft;
|
|
1898
|
+
const outerWidth = Math.max(env.innerWidth - marginLeft - marginRight, 1);
|
|
1899
|
+
const innerX = outerX + padding.left;
|
|
1900
|
+
const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
|
|
1901
|
+
const innerEnv = {
|
|
1902
|
+
...env,
|
|
1903
|
+
marginLeft: innerX,
|
|
1904
|
+
innerWidth
|
|
1905
|
+
};
|
|
1906
|
+
const innerBlock = stripBoxModelProps(block);
|
|
1907
|
+
const innerHeight = measureBlockHeight(innerBlock, innerEnv);
|
|
1908
|
+
const boxHeight = padding.top + innerHeight + padding.bottom;
|
|
1909
|
+
const totalHeight = marginTop + boxHeight + marginBottom;
|
|
1910
|
+
if (y === null && env.allowPageBreak !== false && block.type !== "table") {
|
|
1911
|
+
ensureSpaceFor(totalHeight, env);
|
|
1912
|
+
}
|
|
1913
|
+
const finalOuterY = (y ?? ctx.currentY) + marginTop;
|
|
1914
|
+
if (block.backgroundColor) {
|
|
1915
|
+
doc.save();
|
|
1916
|
+
doc.fillOpacity(block.backgroundOpacity ?? 1).rect(outerX, finalOuterY, outerWidth, boxHeight).fill(block.backgroundColor);
|
|
1917
|
+
doc.restore();
|
|
1918
|
+
}
|
|
1919
|
+
if (block.backgroundImage) {
|
|
1920
|
+
try {
|
|
1921
|
+
doc.save();
|
|
1922
|
+
doc.opacity(block.backgroundOpacity ?? 1);
|
|
1923
|
+
doc.image(block.backgroundImage, outerX, finalOuterY, {
|
|
1924
|
+
width: outerWidth,
|
|
1925
|
+
height: boxHeight
|
|
1926
|
+
});
|
|
1927
|
+
doc.restore();
|
|
1928
|
+
} catch (_) {
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
if (block.backgroundBlocks?.length) {
|
|
1932
|
+
doc.save();
|
|
1933
|
+
doc.opacity(block.backgroundOpacity ?? 1);
|
|
1934
|
+
renderBlockArray(block.backgroundBlocks, finalOuterY, {
|
|
1935
|
+
...env,
|
|
1936
|
+
marginLeft: outerX,
|
|
1937
|
+
innerWidth: outerWidth,
|
|
1938
|
+
allowPageBreak: false,
|
|
1939
|
+
isBackgroundLayer: true
|
|
1940
|
+
});
|
|
1941
|
+
doc.restore();
|
|
1942
|
+
}
|
|
1943
|
+
const innerEndY = renderBlockCore(innerBlock, finalOuterY + padding.top, innerEnv);
|
|
1944
|
+
const newY = innerEndY + padding.bottom + marginBottom;
|
|
1945
|
+
if (y === null) {
|
|
1946
|
+
ctx.currentY = newY;
|
|
1947
|
+
}
|
|
1948
|
+
return newY;
|
|
1949
|
+
};
|
|
1950
|
+
const renderBlockArray = (blocks, startY, env) => {
|
|
1951
|
+
let localY = startY;
|
|
1952
|
+
for (const block of blocks) {
|
|
1953
|
+
localY = renderBlock(block, localY, env);
|
|
1954
|
+
}
|
|
1955
|
+
return localY;
|
|
1956
|
+
};
|
|
1957
|
+
return { renderBlock, renderBlockArray, measureBlockHeight };
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// src/renderer-engine/utils/context.ts
|
|
1961
|
+
function createInitialContext(doc) {
|
|
1962
|
+
return {
|
|
1963
|
+
pageNumber: 1,
|
|
1964
|
+
currentY: doc.page.margins.top,
|
|
1965
|
+
signatureBlock: null,
|
|
1966
|
+
signatureTopY: null,
|
|
1967
|
+
signatureHeight: 0,
|
|
1968
|
+
signaturePlaced: false,
|
|
1969
|
+
afterSignature: false,
|
|
1970
|
+
inFooter: false,
|
|
1971
|
+
inManualPageAdd: false
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/renderer-engine/utils/env.ts
|
|
1976
|
+
var contentEnv = (doc) => ({
|
|
1977
|
+
marginLeft: doc.page.margins.left,
|
|
1978
|
+
innerWidth: doc.page.width - doc.page.margins.left - doc.page.margins.right,
|
|
1979
|
+
allowPageBreak: true
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1982
|
+
// src/renderer-engine/utils/finish-page.ts
|
|
1983
|
+
function finishPage({
|
|
1984
|
+
addNewPage,
|
|
1985
|
+
doc,
|
|
1986
|
+
def,
|
|
1987
|
+
ctx,
|
|
1988
|
+
footerBandHeight,
|
|
1989
|
+
renderBlockArray,
|
|
1990
|
+
startNewPageLayout
|
|
1991
|
+
}) {
|
|
1992
|
+
const mode = def.watermark?.mode;
|
|
1993
|
+
if (def.watermark && watermarkUsesLast(mode)) {
|
|
1994
|
+
const isLast = !addNewPage;
|
|
1995
|
+
drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, isLast);
|
|
1996
|
+
}
|
|
1997
|
+
if (ctx.signatureBlock && ctx.signatureTopY !== null) {
|
|
1998
|
+
const env = contentEnv(doc);
|
|
1999
|
+
drawSignatureBlock(doc, ctx.signatureBlock, ctx.signatureTopY, env, renderBlockArray);
|
|
2000
|
+
}
|
|
2001
|
+
drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray);
|
|
2002
|
+
if (addNewPage) {
|
|
2003
|
+
ctx.inManualPageAdd = true;
|
|
2004
|
+
doc.addPage();
|
|
2005
|
+
ctx.inManualPageAdd = false;
|
|
2006
|
+
ctx.pageNumber += 1;
|
|
2007
|
+
startNewPageLayout();
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// src/renderer-engine/utils/footer.ts
|
|
2012
|
+
function normalizeFooter(input, ctx, doc) {
|
|
2013
|
+
if (!input) return null;
|
|
2014
|
+
if (typeof input === "function") {
|
|
2015
|
+
const result = input(ctx.pageNumber, {
|
|
2016
|
+
width: doc.page.width,
|
|
2017
|
+
height: doc.page.height
|
|
2018
|
+
});
|
|
2019
|
+
if (!result) return null;
|
|
2020
|
+
return normalizeFooter(result, ctx, doc);
|
|
2021
|
+
}
|
|
2022
|
+
if (input.blocks && Array.isArray(input.blocks)) {
|
|
2023
|
+
const footer = input;
|
|
2024
|
+
return {
|
|
2025
|
+
visible: footer.visible,
|
|
2026
|
+
blocks: footer.blocks,
|
|
2027
|
+
backgroundBlocks: footer.backgroundBlocks,
|
|
2028
|
+
marginTop: footer.marginTop,
|
|
2029
|
+
marginBottom: footer.marginBottom,
|
|
2030
|
+
marginLeft: footer.marginLeft,
|
|
2031
|
+
marginRight: footer.marginRight,
|
|
2032
|
+
padding: footer.padding,
|
|
2033
|
+
paddingTop: footer.paddingTop,
|
|
2034
|
+
paddingRight: footer.paddingRight,
|
|
2035
|
+
paddingBottom: footer.paddingBottom,
|
|
2036
|
+
paddingLeft: footer.paddingLeft,
|
|
2037
|
+
backgroundColor: footer.backgroundColor,
|
|
2038
|
+
backgroundImage: footer.backgroundImage,
|
|
2039
|
+
backgroundOpacity: footer.backgroundOpacity
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
if (input.type) {
|
|
2043
|
+
return { blocks: [input] };
|
|
2044
|
+
}
|
|
2045
|
+
if (Array.isArray(input)) {
|
|
2046
|
+
return { blocks: input };
|
|
2047
|
+
}
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
function drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray) {
|
|
2051
|
+
const footerConfig = def.footer;
|
|
2052
|
+
if (!footerConfig) return;
|
|
2053
|
+
const layout = normalizeFooter(footerConfig, ctx, doc);
|
|
2054
|
+
if (!layout) return;
|
|
2055
|
+
if (layout.visible === false) return;
|
|
2056
|
+
const blocks = layout.blocks ?? [];
|
|
2057
|
+
const backgroundBlocks = layout.backgroundBlocks ?? [];
|
|
2058
|
+
if (!blocks.length && !backgroundBlocks.length && !layout.backgroundColor && !layout.backgroundImage) {
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
const bandHeight = footerBandHeight;
|
|
2062
|
+
if (!bandHeight) return;
|
|
2063
|
+
const footerMarginLeft = layout.marginLeft ?? 0;
|
|
2064
|
+
const footerMarginRight = layout.marginRight ?? 0;
|
|
2065
|
+
const outerX = footerMarginLeft;
|
|
2066
|
+
const outerWidth = doc.page.width - footerMarginLeft - footerMarginRight;
|
|
2067
|
+
const padding = resolveFooterPadding(layout);
|
|
2068
|
+
const innerX = outerX + padding.left;
|
|
2069
|
+
const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
|
|
2070
|
+
const outerEnv = {
|
|
2071
|
+
marginLeft: outerX,
|
|
2072
|
+
innerWidth: outerWidth,
|
|
2073
|
+
allowPageBreak: false
|
|
2074
|
+
};
|
|
2075
|
+
const innerEnv = {
|
|
2076
|
+
marginLeft: innerX,
|
|
2077
|
+
innerWidth,
|
|
2078
|
+
allowPageBreak: false
|
|
2079
|
+
};
|
|
2080
|
+
const pageHeight = doc.page.height;
|
|
2081
|
+
const bandTop = pageHeight - bandHeight;
|
|
2082
|
+
const backgroundOpacity = layout.backgroundOpacity ?? 1;
|
|
2083
|
+
if (layout.backgroundColor) {
|
|
2084
|
+
doc.save();
|
|
2085
|
+
doc.fillOpacity(backgroundOpacity).rect(0, bandTop, doc.page.width, bandHeight).fill(layout.backgroundColor);
|
|
2086
|
+
doc.restore();
|
|
2087
|
+
}
|
|
2088
|
+
if (layout.backgroundImage) {
|
|
2089
|
+
try {
|
|
2090
|
+
doc.save();
|
|
2091
|
+
doc.opacity(backgroundOpacity);
|
|
2092
|
+
doc.image(layout.backgroundImage, 0, bandTop, {
|
|
2093
|
+
width: doc.page.width,
|
|
2094
|
+
height: bandHeight
|
|
2095
|
+
});
|
|
2096
|
+
doc.restore();
|
|
2097
|
+
} catch (e) {
|
|
2098
|
+
console.warn("Failed to load footer background image:", e);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
doc.save();
|
|
2102
|
+
doc.rect(outerX, bandTop, outerWidth, bandHeight).clip();
|
|
2103
|
+
doc.translate(0, bandTop);
|
|
2104
|
+
ctx.inFooter = true;
|
|
2105
|
+
const localStartY = layout.marginTop ?? 4;
|
|
2106
|
+
if (backgroundBlocks.length) {
|
|
2107
|
+
doc.save();
|
|
2108
|
+
doc.opacity(backgroundOpacity);
|
|
2109
|
+
renderBlockArray(backgroundBlocks, localStartY, {
|
|
2110
|
+
...outerEnv,
|
|
2111
|
+
allowPageBreak: false,
|
|
2112
|
+
isBackgroundLayer: true
|
|
2113
|
+
});
|
|
2114
|
+
doc.restore();
|
|
2115
|
+
}
|
|
2116
|
+
if (blocks.length) {
|
|
2117
|
+
renderBlockArray(blocks, localStartY + padding.top, innerEnv);
|
|
2118
|
+
}
|
|
2119
|
+
ctx.inFooter = false;
|
|
2120
|
+
doc.restore();
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// src/renderer-engine/utils/header.ts
|
|
2124
|
+
function drawHeader(doc, def, headerBandHeight, header, renderBlockArray) {
|
|
2125
|
+
if (!header) return;
|
|
2126
|
+
if (header.visible === false) return;
|
|
2127
|
+
if (!headerBandHeight) return;
|
|
2128
|
+
const blocks = header.blocks ?? [];
|
|
2129
|
+
const backgroundBlocks = header.backgroundBlocks ?? [];
|
|
2130
|
+
if (!blocks.length && !backgroundBlocks.length && !header.backgroundColor && !header.backgroundImage) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const bandTop = 0;
|
|
2134
|
+
const bandHeight = def.margins.top;
|
|
2135
|
+
const headerMarginLeft = header.marginLeft ?? 0;
|
|
2136
|
+
const headerMarginRight = header.marginRight ?? 0;
|
|
2137
|
+
const outerX = headerMarginLeft;
|
|
2138
|
+
const outerWidth = doc.page.width - headerMarginLeft - headerMarginRight;
|
|
2139
|
+
const padding = resolveHeaderPadding(header);
|
|
2140
|
+
const innerX = outerX + padding.left;
|
|
2141
|
+
const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
|
|
2142
|
+
const outerEnv = {
|
|
2143
|
+
marginLeft: outerX,
|
|
2144
|
+
innerWidth: outerWidth,
|
|
2145
|
+
allowPageBreak: false
|
|
2146
|
+
};
|
|
2147
|
+
const innerEnv = {
|
|
2148
|
+
marginLeft: innerX,
|
|
2149
|
+
innerWidth,
|
|
2150
|
+
allowPageBreak: false
|
|
2151
|
+
};
|
|
2152
|
+
const backgroundOpacity = header.backgroundOpacity ?? 1;
|
|
2153
|
+
if (header.backgroundColor) {
|
|
2154
|
+
doc.save();
|
|
2155
|
+
doc.fillOpacity(backgroundOpacity).rect(0, bandTop, doc.page.width, bandHeight).fill(header.backgroundColor);
|
|
2156
|
+
doc.restore();
|
|
2157
|
+
}
|
|
2158
|
+
if (header.backgroundImage) {
|
|
2159
|
+
try {
|
|
2160
|
+
doc.save();
|
|
2161
|
+
doc.opacity(backgroundOpacity);
|
|
2162
|
+
doc.image(header.backgroundImage, 0, bandTop, {
|
|
2163
|
+
width: doc.page.width,
|
|
2164
|
+
height: bandHeight
|
|
2165
|
+
});
|
|
2166
|
+
doc.restore();
|
|
2167
|
+
} catch (e) {
|
|
2168
|
+
console.warn("Failed to load header background image:", e);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
doc.save();
|
|
2172
|
+
doc.rect(outerX, bandTop, outerWidth, bandHeight).clip();
|
|
2173
|
+
const startY = bandTop + (header.marginTop ?? 0);
|
|
2174
|
+
if (backgroundBlocks.length) {
|
|
2175
|
+
doc.save();
|
|
2176
|
+
doc.opacity(backgroundOpacity);
|
|
2177
|
+
renderBlockArray(backgroundBlocks, startY, {
|
|
2178
|
+
...outerEnv,
|
|
2179
|
+
allowPageBreak: false,
|
|
2180
|
+
isBackgroundLayer: true
|
|
2181
|
+
});
|
|
2182
|
+
doc.restore();
|
|
2183
|
+
}
|
|
2184
|
+
if (blocks.length) {
|
|
2185
|
+
renderBlockArray(blocks, startY + padding.top, innerEnv);
|
|
2186
|
+
}
|
|
2187
|
+
doc.restore();
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// src/renderer-engine/utils/image-loader.ts
|
|
2191
|
+
var import_axios = __toESM(require("axios"));
|
|
2192
|
+
async function normalizeImageSrc(src, fallback) {
|
|
2193
|
+
if (!src) return fallback ?? src;
|
|
2194
|
+
if (Buffer.isBuffer(src)) return src;
|
|
2195
|
+
if (src.startsWith("data:")) {
|
|
2196
|
+
const commaIdx = src.indexOf(",");
|
|
2197
|
+
if (commaIdx !== -1) {
|
|
2198
|
+
return Buffer.from(src.slice(commaIdx + 1), "base64");
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
2202
|
+
try {
|
|
2203
|
+
const res = await import_axios.default.get(src, {
|
|
2204
|
+
responseType: "arraybuffer"
|
|
2205
|
+
});
|
|
2206
|
+
return Buffer.from(res.data);
|
|
2207
|
+
} catch (e) {
|
|
2208
|
+
if (fallback !== void 0) {
|
|
2209
|
+
console.warn(`Failed to fetch remote image "${src}", using default image.`);
|
|
2210
|
+
return fallback;
|
|
1668
2211
|
}
|
|
1669
|
-
|
|
2212
|
+
throw toPdfEngineError(e, {
|
|
2213
|
+
code: "PDF_ERROR_IMAGE_FETCH_FAILED" /* PDF_ERROR_IMAGE_FETCH_FAILED */,
|
|
2214
|
+
message: "Failed to fetch remote image for PDF.",
|
|
2215
|
+
statusCode: 422,
|
|
2216
|
+
details: { url: src },
|
|
2217
|
+
retryable: false
|
|
2218
|
+
});
|
|
1670
2219
|
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
const colCount = cols.length;
|
|
1681
|
-
if (!colCount) return mt + mb;
|
|
1682
|
-
const blockLeft = b.marginLeft ?? 0;
|
|
1683
|
-
const blockRight = b.marginRight ?? 0;
|
|
1684
|
-
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
1685
|
-
let colWidths;
|
|
1686
|
-
if (b.columnWidths && b.columnWidths.length === colCount) {
|
|
1687
|
-
const safe = b.columnWidths.map((w) => w === void 0 ? "*" : w);
|
|
1688
|
-
colWidths = computeColumnPixelWidths2(safe, totalWidth);
|
|
1689
|
-
} else {
|
|
1690
|
-
colWidths = Array(colCount).fill(totalWidth / colCount);
|
|
2220
|
+
}
|
|
2221
|
+
return src;
|
|
2222
|
+
}
|
|
2223
|
+
async function materializeImagesInBlocks(blocks, fallback) {
|
|
2224
|
+
const out = [];
|
|
2225
|
+
for (const block of blocks) {
|
|
2226
|
+
const blockAny = { ...block };
|
|
2227
|
+
if (blockAny.backgroundImage) {
|
|
2228
|
+
blockAny.backgroundImage = await normalizeImageSrc(blockAny.backgroundImage, fallback);
|
|
1691
2229
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const baseKeyWidthRatio = 0.35;
|
|
1695
|
-
const measureKVText = (tb, width) => {
|
|
1696
|
-
const r = resolveTextBlock(styles, tb);
|
|
1697
|
-
const fontName = getFontNameForText(r);
|
|
1698
|
-
doc.font(fontName);
|
|
1699
|
-
doc.fontSize(r.fontSize ?? 10);
|
|
1700
|
-
return doc.heightOfString(r.text ?? "", { width });
|
|
1701
|
-
};
|
|
1702
|
-
let totalHeight = mt;
|
|
1703
|
-
if (orientation === "vertical") {
|
|
1704
|
-
const keyValueGap = b.verticalKeyValueGap ?? 2;
|
|
1705
|
-
let maxColHeight = 0;
|
|
1706
|
-
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1707
|
-
const col = cols[colIndex] ?? [];
|
|
1708
|
-
const colWidth = colWidths[colIndex];
|
|
1709
|
-
let colHeight = 0;
|
|
1710
|
-
for (const item of col) {
|
|
1711
|
-
const keyH = measureKVText({ type: "text", text: item.key ?? "" }, colWidth);
|
|
1712
|
-
const valH = measureKVText({ type: "text", text: item.value ?? "" }, colWidth);
|
|
1713
|
-
colHeight += keyH + keyValueGap + valH + rowGap;
|
|
1714
|
-
}
|
|
1715
|
-
if (colHeight > maxColHeight) maxColHeight = colHeight;
|
|
1716
|
-
}
|
|
1717
|
-
totalHeight += maxColHeight;
|
|
1718
|
-
} else {
|
|
1719
|
-
const maxRows = Math.max(...cols.map((c) => c ? c.length : 0), 0);
|
|
1720
|
-
for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
|
|
1721
|
-
let rowHeight = 0;
|
|
1722
|
-
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1723
|
-
const item = cols[colIndex]?.[rowIndex];
|
|
1724
|
-
if (!item) continue;
|
|
1725
|
-
const colWidth = colWidths[colIndex];
|
|
1726
|
-
const keyWidthPx = b.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : b.keyWidth ?? 80;
|
|
1727
|
-
const keyH = measureKVText({ type: "text", text: item.key ?? "" }, keyWidthPx);
|
|
1728
|
-
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
1729
|
-
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
1730
|
-
const valH = measureKVText({ type: "text", text: item.value ?? "" }, valueWidth);
|
|
1731
|
-
rowHeight = Math.max(rowHeight, Math.max(keyH, valH));
|
|
1732
|
-
}
|
|
1733
|
-
if (rowHeight > 0) totalHeight += rowHeight + rowGap;
|
|
1734
|
-
}
|
|
2230
|
+
if (blockAny.backgroundBlocks?.length) {
|
|
2231
|
+
blockAny.backgroundBlocks = await materializeImagesInBlocks(blockAny.backgroundBlocks, fallback);
|
|
1735
2232
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
if (
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
2233
|
+
if (block.type === "image") {
|
|
2234
|
+
const img = { ...blockAny };
|
|
2235
|
+
img.src = await normalizeImageSrc(img.src, fallback);
|
|
2236
|
+
out.push(img);
|
|
2237
|
+
} else if (block.type === "columns") {
|
|
2238
|
+
out.push({
|
|
2239
|
+
...blockAny,
|
|
2240
|
+
columns: await Promise.all(
|
|
2241
|
+
block.columns.map((col) => materializeImagesInBlocks(col, fallback))
|
|
2242
|
+
)
|
|
2243
|
+
});
|
|
2244
|
+
} else if (block.type === "signature" && block.blocks) {
|
|
2245
|
+
out.push({
|
|
2246
|
+
...blockAny,
|
|
2247
|
+
blocks: await materializeImagesInBlocks(block.blocks, fallback)
|
|
2248
|
+
});
|
|
2249
|
+
} else if (block.type === "table") {
|
|
2250
|
+
const tb = blockAny;
|
|
2251
|
+
const newBody = await Promise.all(
|
|
2252
|
+
tb.body.map(
|
|
2253
|
+
async (entry) => {
|
|
2254
|
+
const materializeCell = async (cell) => {
|
|
2255
|
+
if (!cell.blocks?.length) return cell;
|
|
2256
|
+
return {
|
|
2257
|
+
...cell,
|
|
2258
|
+
blocks: await materializeImagesInBlocks(cell.blocks, fallback)
|
|
2259
|
+
};
|
|
2260
|
+
};
|
|
2261
|
+
if (Array.isArray(entry)) {
|
|
2262
|
+
return await Promise.all(entry.map(materializeCell));
|
|
2263
|
+
}
|
|
2264
|
+
if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
|
|
2265
|
+
return {
|
|
2266
|
+
...entry,
|
|
2267
|
+
content: await Promise.all(entry.content.map(materializeCell))
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
return entry;
|
|
2271
|
+
}
|
|
2272
|
+
)
|
|
2273
|
+
);
|
|
2274
|
+
out.push({ ...tb, body: newBody });
|
|
2275
|
+
} else {
|
|
2276
|
+
out.push(blockAny);
|
|
1764
2277
|
}
|
|
1765
|
-
}
|
|
1766
|
-
return
|
|
2278
|
+
}
|
|
2279
|
+
return out;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/renderer-engine/utils/layout.ts
|
|
2283
|
+
function computeColumnPixelWidths(widths, totalWidth) {
|
|
2284
|
+
let fixedTotal = 0;
|
|
2285
|
+
let starCount = 0;
|
|
2286
|
+
widths.forEach((w) => {
|
|
2287
|
+
if (w === "*") starCount++;
|
|
2288
|
+
else fixedTotal += w;
|
|
2289
|
+
});
|
|
2290
|
+
const remaining = Math.max(totalWidth - fixedTotal, 0);
|
|
2291
|
+
const starWidth = starCount > 0 ? remaining / starCount : 0;
|
|
2292
|
+
return widths.map((w) => w === "*" ? starWidth : w);
|
|
1767
2293
|
}
|
|
1768
2294
|
|
|
1769
2295
|
// src/renderer-engine/utils/page-background.ts
|
|
@@ -1788,12 +2314,10 @@ function drawPageBackground(doc, pageBackground) {
|
|
|
1788
2314
|
doc.opacity(1);
|
|
1789
2315
|
}
|
|
1790
2316
|
|
|
1791
|
-
// src/renderer-engine/utils/page-
|
|
1792
|
-
function
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
return ctx.signatureTopY ?? pageBottomForContent;
|
|
1796
|
-
};
|
|
2317
|
+
// src/renderer-engine/utils/page-flow.ts
|
|
2318
|
+
function getBottomLimitForContent(doc, ctx) {
|
|
2319
|
+
const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
|
|
2320
|
+
return ctx.signatureTopY ?? pageBottomForContent;
|
|
1797
2321
|
}
|
|
1798
2322
|
|
|
1799
2323
|
// src/renderer-engine/utils/qr-bar-code.ts
|
|
@@ -1816,7 +2340,10 @@ function generateQrBuffer(value, size, version, errorCorrectionLevel) {
|
|
|
1816
2340
|
code: "PDF_ERROR_QR_GENERATION_FAILED" /* PDF_ERROR_QR_GENERATION_FAILED */,
|
|
1817
2341
|
message: "Failed to generate QR code.",
|
|
1818
2342
|
statusCode: 500,
|
|
1819
|
-
details: {
|
|
2343
|
+
details: {
|
|
2344
|
+
valuePreview: String(value).slice(0, 60),
|
|
2345
|
+
size
|
|
2346
|
+
},
|
|
1820
2347
|
retryable: false
|
|
1821
2348
|
})
|
|
1822
2349
|
);
|
|
@@ -1843,109 +2370,101 @@ function mapBarcodeTypeToBcid(bcType) {
|
|
|
1843
2370
|
function generateBarcodeBuffer(value, options = {}) {
|
|
1844
2371
|
const { bcType, scale = 3, barHeight = 10, includetext = false, textalign = "center" } = options;
|
|
1845
2372
|
const bcid = mapBarcodeTypeToBcid(bcType);
|
|
2373
|
+
if (bcType === "EAN13" && !/^\d{12,13}$/.test(value)) {
|
|
2374
|
+
throw toPdfEngineError(new Error("Invalid EAN13 value"), {
|
|
2375
|
+
code: "PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH" /* PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH */,
|
|
2376
|
+
message: "EAN13 barcode value must be 12 or 13 digits.",
|
|
2377
|
+
statusCode: 422,
|
|
2378
|
+
details: { value },
|
|
2379
|
+
retryable: false
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
1846
2382
|
return new Promise((resolve, reject) => {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
};
|
|
1873
|
-
bwipOptions.textxalign = textalign;
|
|
1874
|
-
import_bwip_js.default.toBuffer(bwipOptions, (err, png) => {
|
|
1875
|
-
if (err) {
|
|
1876
|
-
return reject(
|
|
1877
|
-
toPdfEngineError(err, {
|
|
1878
|
-
code: "PDF_ERROR_BARCODE_GENERATION_FAILED" /* PDF_ERROR_BARCODE_GENERATION_FAILED */,
|
|
1879
|
-
message: "Failed to generate barcode.",
|
|
1880
|
-
statusCode: 500,
|
|
1881
|
-
details: { bcType, valuePreview: String(value).slice(0, 60) },
|
|
1882
|
-
retryable: false
|
|
1883
|
-
})
|
|
1884
|
-
);
|
|
2383
|
+
import_bwip_js.default.toBuffer(
|
|
2384
|
+
{
|
|
2385
|
+
bcid,
|
|
2386
|
+
text: value,
|
|
2387
|
+
scale,
|
|
2388
|
+
height: barHeight,
|
|
2389
|
+
includetext,
|
|
2390
|
+
textxalign: textalign
|
|
2391
|
+
},
|
|
2392
|
+
(err, png) => {
|
|
2393
|
+
if (err) {
|
|
2394
|
+
return reject(
|
|
2395
|
+
toPdfEngineError(err, {
|
|
2396
|
+
code: "PDF_ERROR_BARCODE_GENERATION_FAILED" /* PDF_ERROR_BARCODE_GENERATION_FAILED */,
|
|
2397
|
+
message: "Failed to generate barcode.",
|
|
2398
|
+
statusCode: 500,
|
|
2399
|
+
details: {
|
|
2400
|
+
valuePreview: String(value).slice(0, 60),
|
|
2401
|
+
bcType
|
|
2402
|
+
},
|
|
2403
|
+
retryable: false
|
|
2404
|
+
})
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
resolve(png);
|
|
1885
2408
|
}
|
|
1886
|
-
|
|
1887
|
-
});
|
|
2409
|
+
);
|
|
1888
2410
|
});
|
|
1889
2411
|
}
|
|
1890
2412
|
async function materializeQrAndBarcodesInBlocks(blocks) {
|
|
1891
2413
|
const out = [];
|
|
1892
2414
|
for (const block of blocks) {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
out.push({
|
|
1900
|
-
...colBlock,
|
|
1901
|
-
columns: newCols
|
|
1902
|
-
});
|
|
1903
|
-
} else if (block.type === "signature") {
|
|
1904
|
-
const sig = block;
|
|
1905
|
-
if (sig.blocks && sig.blocks.length) {
|
|
1906
|
-
const newInner = await materializeQrAndBarcodesInBlocks(sig.blocks);
|
|
1907
|
-
out.push({
|
|
1908
|
-
...sig,
|
|
1909
|
-
blocks: newInner
|
|
1910
|
-
});
|
|
1911
|
-
} else {
|
|
1912
|
-
out.push(block);
|
|
1913
|
-
}
|
|
1914
|
-
} else if (block.type === "qr") {
|
|
1915
|
-
const qb = { ...block };
|
|
2415
|
+
const blockAny = { ...block };
|
|
2416
|
+
if (blockAny.backgroundBlocks?.length) {
|
|
2417
|
+
blockAny.backgroundBlocks = await materializeQrAndBarcodesInBlocks(blockAny.backgroundBlocks);
|
|
2418
|
+
}
|
|
2419
|
+
if (block.type === "qr") {
|
|
2420
|
+
const qb = blockAny;
|
|
1916
2421
|
if (!qb.src && qb.value) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
qb.src = buf;
|
|
2422
|
+
qb.size = qb.size ?? 80;
|
|
2423
|
+
qb.src = await generateQrBuffer(qb.value, qb.size, qb.qrVersion, qb.errorCorrectionLevel);
|
|
1920
2424
|
}
|
|
1921
2425
|
out.push(qb);
|
|
1922
2426
|
} else if (block.type === "barcode") {
|
|
1923
|
-
const bb =
|
|
2427
|
+
const bb = blockAny;
|
|
1924
2428
|
if (!bb.src && bb.value) {
|
|
1925
|
-
|
|
2429
|
+
bb.src = await generateBarcodeBuffer(bb.value, {
|
|
1926
2430
|
bcType: bb.bcType,
|
|
1927
2431
|
scale: bb.scale,
|
|
1928
2432
|
barHeight: bb.barHeight,
|
|
1929
2433
|
includetext: bb.includetext,
|
|
1930
2434
|
textalign: bb.textalign
|
|
1931
2435
|
});
|
|
1932
|
-
bb.src = buf;
|
|
1933
2436
|
}
|
|
1934
2437
|
out.push(bb);
|
|
2438
|
+
} else if (block.type === "columns") {
|
|
2439
|
+
out.push({
|
|
2440
|
+
...blockAny,
|
|
2441
|
+
columns: await Promise.all(block.columns.map((col) => materializeQrAndBarcodesInBlocks(col)))
|
|
2442
|
+
});
|
|
2443
|
+
} else if (block.type === "signature" && block.blocks) {
|
|
2444
|
+
out.push({
|
|
2445
|
+
...blockAny,
|
|
2446
|
+
blocks: await materializeQrAndBarcodesInBlocks(block.blocks)
|
|
2447
|
+
});
|
|
1935
2448
|
} else if (block.type === "table") {
|
|
1936
|
-
const tb =
|
|
2449
|
+
const tb = blockAny;
|
|
1937
2450
|
const newBody = await Promise.all(
|
|
1938
2451
|
tb.body.map(
|
|
1939
2452
|
async (entry) => {
|
|
1940
2453
|
const materializeCell = async (cell) => {
|
|
1941
2454
|
if (!cell.blocks?.length) return cell;
|
|
1942
|
-
return {
|
|
2455
|
+
return {
|
|
2456
|
+
...cell,
|
|
2457
|
+
blocks: await materializeQrAndBarcodesInBlocks(cell.blocks)
|
|
2458
|
+
};
|
|
1943
2459
|
};
|
|
1944
2460
|
if (Array.isArray(entry)) {
|
|
1945
2461
|
return await Promise.all(entry.map(materializeCell));
|
|
1946
2462
|
}
|
|
1947
2463
|
if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
|
|
1948
|
-
return {
|
|
2464
|
+
return {
|
|
2465
|
+
...entry,
|
|
2466
|
+
content: await Promise.all(entry.content.map(materializeCell))
|
|
2467
|
+
};
|
|
1949
2468
|
}
|
|
1950
2469
|
return entry;
|
|
1951
2470
|
}
|
|
@@ -1953,7 +2472,7 @@ async function materializeQrAndBarcodesInBlocks(blocks) {
|
|
|
1953
2472
|
);
|
|
1954
2473
|
out.push({ ...tb, body: newBody });
|
|
1955
2474
|
} else {
|
|
1956
|
-
out.push(
|
|
2475
|
+
out.push(blockAny);
|
|
1957
2476
|
}
|
|
1958
2477
|
}
|
|
1959
2478
|
return out;
|
|
@@ -2207,102 +2726,143 @@ function drawWatermarkForPage(doc, watermark, pageNumber, isLast) {
|
|
|
2207
2726
|
}
|
|
2208
2727
|
|
|
2209
2728
|
// src/renderer-engine/index.ts
|
|
2210
|
-
var BUILT_IN_DEFAULT_IMAGE = Buffer.from(
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
)
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
if (
|
|
2729
|
+
var BUILT_IN_DEFAULT_IMAGE = Buffer.from(images.default.slice(images.default.indexOf(",") + 1), "base64");
|
|
2730
|
+
async function materializeFooterAssets(footer, fallbackImage) {
|
|
2731
|
+
if (!footer) return footer;
|
|
2732
|
+
if (typeof footer === "function") {
|
|
2733
|
+
return footer;
|
|
2734
|
+
}
|
|
2735
|
+
if (Array.isArray(footer)) {
|
|
2736
|
+
let blocks = await materializeQrAndBarcodesInBlocks(footer);
|
|
2737
|
+
blocks = await materializeImagesInBlocks(blocks, fallbackImage);
|
|
2738
|
+
return blocks;
|
|
2739
|
+
}
|
|
2740
|
+
if (footer.type) {
|
|
2741
|
+
let blocks = await materializeQrAndBarcodesInBlocks([footer]);
|
|
2742
|
+
blocks = await materializeImagesInBlocks(blocks, fallbackImage);
|
|
2743
|
+
return blocks[0];
|
|
2744
|
+
}
|
|
2745
|
+
const footerDef = footer;
|
|
2746
|
+
if (footerDef.backgroundImage) {
|
|
2747
|
+
footerDef.backgroundImage = await normalizeImageSrc(footerDef.backgroundImage, fallbackImage);
|
|
2748
|
+
}
|
|
2749
|
+
if (footerDef.blocks?.length) {
|
|
2750
|
+
footerDef.blocks = await materializeQrAndBarcodesInBlocks(footerDef.blocks);
|
|
2751
|
+
footerDef.blocks = await materializeImagesInBlocks(footerDef.blocks, fallbackImage);
|
|
2752
|
+
}
|
|
2753
|
+
if (footerDef.backgroundBlocks?.length) {
|
|
2754
|
+
footerDef.backgroundBlocks = await materializeQrAndBarcodesInBlocks(footerDef.backgroundBlocks);
|
|
2755
|
+
footerDef.backgroundBlocks = await materializeImagesInBlocks(footerDef.backgroundBlocks, fallbackImage);
|
|
2756
|
+
}
|
|
2757
|
+
return footerDef;
|
|
2758
|
+
}
|
|
2759
|
+
async function materializeDocAssets(def, fallbackImage) {
|
|
2760
|
+
if (def.header?.blocks?.length) {
|
|
2217
2761
|
def.header.blocks = await materializeQrAndBarcodesInBlocks(def.header.blocks);
|
|
2218
2762
|
def.header.blocks = await materializeImagesInBlocks(def.header.blocks, fallbackImage);
|
|
2219
2763
|
}
|
|
2764
|
+
if (def.header?.backgroundImage) {
|
|
2765
|
+
def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
|
|
2766
|
+
}
|
|
2767
|
+
if (def.header?.backgroundBlocks?.length) {
|
|
2768
|
+
def.header.backgroundBlocks = await materializeQrAndBarcodesInBlocks(def.header.backgroundBlocks);
|
|
2769
|
+
def.header.backgroundBlocks = await materializeImagesInBlocks(def.header.backgroundBlocks, fallbackImage);
|
|
2770
|
+
}
|
|
2220
2771
|
def.content = await materializeQrAndBarcodesInBlocks(def.content);
|
|
2221
2772
|
def.content = await materializeImagesInBlocks(def.content, fallbackImage);
|
|
2222
2773
|
if (def.pageBackground?.src) {
|
|
2223
2774
|
def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src, fallbackImage);
|
|
2224
2775
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2776
|
+
def.footer = await materializeFooterAssets(def.footer, fallbackImage);
|
|
2777
|
+
}
|
|
2778
|
+
function runRender(doc, def, fallbackImage) {
|
|
2779
|
+
const headerBandHeight = def.margins.top ?? 0;
|
|
2780
|
+
const footerBandHeight = def.margins.bottom ?? 0;
|
|
2781
|
+
if (def.fonts && def.fonts.length) {
|
|
2782
|
+
for (const font of def.fonts) {
|
|
2783
|
+
try {
|
|
2784
|
+
doc.registerFont(font.name, font.src);
|
|
2785
|
+
} catch (e) {
|
|
2786
|
+
console.warn("Failed to register font:", font.name, e);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2227
2789
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2790
|
+
const ctx = createInitialContext(doc);
|
|
2791
|
+
const styles = def.styles ?? {};
|
|
2792
|
+
const finishPage2 = (addNewPage) => finishPage({
|
|
2793
|
+
addNewPage,
|
|
2794
|
+
doc,
|
|
2795
|
+
def,
|
|
2796
|
+
ctx,
|
|
2797
|
+
footerBandHeight,
|
|
2798
|
+
renderBlockArray,
|
|
2799
|
+
startNewPageLayout
|
|
2800
|
+
});
|
|
2801
|
+
const processSignatureBlock = createProcessSignatureBlock({
|
|
2802
|
+
doc,
|
|
2803
|
+
ctx,
|
|
2804
|
+
styles,
|
|
2805
|
+
finishPage: finishPage2,
|
|
2806
|
+
contentEnvFn: contentEnv
|
|
2807
|
+
});
|
|
2808
|
+
const { renderBlock, renderBlockArray } = createBlockRenderer({
|
|
2809
|
+
doc,
|
|
2810
|
+
ctx,
|
|
2811
|
+
styles,
|
|
2812
|
+
computeColumnPixelWidths,
|
|
2813
|
+
finishPage: finishPage2,
|
|
2814
|
+
processSignatureBlock,
|
|
2815
|
+
defaultImage: fallbackImage
|
|
2816
|
+
});
|
|
2817
|
+
const startNewPageLayout = createStartNewPageLayout({
|
|
2818
|
+
doc,
|
|
2819
|
+
def,
|
|
2820
|
+
ctx,
|
|
2821
|
+
headerBandHeight,
|
|
2822
|
+
renderBlockArray
|
|
2823
|
+
});
|
|
2824
|
+
doc.on("pageAdded", () => {
|
|
2825
|
+
if (ctx.inManualPageAdd) return;
|
|
2826
|
+
ctx.pageNumber += 1;
|
|
2827
|
+
drawPageBackground(doc, def.pageBackground);
|
|
2828
|
+
drawHeader(doc, def, headerBandHeight, def.header ?? void 0, renderBlockArray);
|
|
2829
|
+
const mode = def.watermark?.mode;
|
|
2830
|
+
if (def.watermark && !watermarkUsesLast(mode)) {
|
|
2831
|
+
drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, false);
|
|
2832
|
+
}
|
|
2833
|
+
ctx.currentY = doc.page.margins.top;
|
|
2834
|
+
});
|
|
2835
|
+
startNewPageLayout();
|
|
2836
|
+
for (const block of def.content) {
|
|
2837
|
+
if (block.type === "signature") {
|
|
2838
|
+
processSignatureBlock(block);
|
|
2839
|
+
continue;
|
|
2840
|
+
}
|
|
2841
|
+
if (ctx.afterSignature) {
|
|
2842
|
+
finishPage2(true);
|
|
2843
|
+
ctx.afterSignature = false;
|
|
2232
2844
|
}
|
|
2845
|
+
renderBlock(block, null, contentEnv(doc));
|
|
2233
2846
|
}
|
|
2847
|
+
finishPage2(false);
|
|
2848
|
+
doc.end();
|
|
2849
|
+
}
|
|
2850
|
+
async function renderCustomPdf(def, outputPath) {
|
|
2851
|
+
const fallbackImage = def.defaultImage ?? BUILT_IN_DEFAULT_IMAGE;
|
|
2852
|
+
await materializeDocAssets(def, fallbackImage);
|
|
2234
2853
|
return new Promise((resolve, reject) => {
|
|
2235
|
-
const headerBandHeight = def.margins.top ?? 0;
|
|
2236
|
-
const footerBandHeight = def.margins.bottom ?? 0;
|
|
2237
2854
|
const doc = new import_pdfkit.default({
|
|
2238
2855
|
size: def.pageSize || "A4",
|
|
2239
2856
|
layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
|
|
2240
2857
|
margins: {
|
|
2241
|
-
top:
|
|
2242
|
-
bottom:
|
|
2858
|
+
top: def.margins.top ?? 0,
|
|
2859
|
+
bottom: def.margins.bottom ?? 0,
|
|
2243
2860
|
left: def.margins.left,
|
|
2244
2861
|
right: def.margins.right
|
|
2245
2862
|
}
|
|
2246
2863
|
});
|
|
2247
|
-
if (def.fonts && def.fonts.length) {
|
|
2248
|
-
for (const f of def.fonts) {
|
|
2249
|
-
try {
|
|
2250
|
-
doc.registerFont(f.name, f.src);
|
|
2251
|
-
} catch (e) {
|
|
2252
|
-
console.warn("Failed to register font:", f.name, e);
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
2864
|
const stream = import_fs.default.createWriteStream(outputPath);
|
|
2257
2865
|
doc.pipe(stream);
|
|
2258
|
-
const ctx = createInitialContext(doc);
|
|
2259
|
-
const styles = def.styles ?? {};
|
|
2260
|
-
const finishPage2 = (addNewPage) => finishPage({
|
|
2261
|
-
addNewPage,
|
|
2262
|
-
doc,
|
|
2263
|
-
def,
|
|
2264
|
-
ctx,
|
|
2265
|
-
footerBandHeight,
|
|
2266
|
-
renderBlockArray,
|
|
2267
|
-
startNewPageLayout
|
|
2268
|
-
});
|
|
2269
|
-
const processSignatureBlock = createProcessSignatureBlock({
|
|
2270
|
-
doc,
|
|
2271
|
-
ctx,
|
|
2272
|
-
styles,
|
|
2273
|
-
finishPage: finishPage2,
|
|
2274
|
-
contentEnvFn: contentEnv
|
|
2275
|
-
});
|
|
2276
|
-
const { renderBlock, renderBlockArray } = createBlockRenderer({
|
|
2277
|
-
doc,
|
|
2278
|
-
ctx,
|
|
2279
|
-
styles,
|
|
2280
|
-
computeColumnPixelWidths,
|
|
2281
|
-
finishPage: finishPage2,
|
|
2282
|
-
processSignatureBlock,
|
|
2283
|
-
defaultImage: fallbackImage
|
|
2284
|
-
});
|
|
2285
|
-
const startNewPageLayout = createStartNewPageLayout({
|
|
2286
|
-
doc,
|
|
2287
|
-
def,
|
|
2288
|
-
ctx,
|
|
2289
|
-
headerBandHeight,
|
|
2290
|
-
renderBlockArray
|
|
2291
|
-
});
|
|
2292
|
-
startNewPageLayout();
|
|
2293
|
-
for (const block of def.content) {
|
|
2294
|
-
if (block.type === "signature") {
|
|
2295
|
-
processSignatureBlock(block);
|
|
2296
|
-
continue;
|
|
2297
|
-
}
|
|
2298
|
-
if (ctx.afterSignature) {
|
|
2299
|
-
finishPage2(true);
|
|
2300
|
-
ctx.afterSignature = false;
|
|
2301
|
-
}
|
|
2302
|
-
renderBlock(block, null, contentEnv(doc));
|
|
2303
|
-
}
|
|
2304
|
-
finishPage2(false);
|
|
2305
|
-
doc.end();
|
|
2306
2866
|
stream.on("finish", () => resolve());
|
|
2307
2867
|
stream.on(
|
|
2308
2868
|
"error",
|
|
@@ -2316,126 +2876,128 @@ async function renderCustomPdf(def, outputPath) {
|
|
|
2316
2876
|
})
|
|
2317
2877
|
)
|
|
2318
2878
|
);
|
|
2879
|
+
runRender(doc, def, fallbackImage);
|
|
2319
2880
|
});
|
|
2320
2881
|
}
|
|
2321
2882
|
async function renderCustomPdfToBuffer(def) {
|
|
2322
2883
|
const fallbackImage = def.defaultImage ?? BUILT_IN_DEFAULT_IMAGE;
|
|
2323
|
-
|
|
2324
|
-
def.header.blocks = await materializeQrAndBarcodesInBlocks(def.header.blocks);
|
|
2325
|
-
def.header.blocks = await materializeImagesInBlocks(def.header.blocks, fallbackImage);
|
|
2326
|
-
}
|
|
2327
|
-
def.content = await materializeQrAndBarcodesInBlocks(def.content);
|
|
2328
|
-
def.content = await materializeImagesInBlocks(def.content, fallbackImage);
|
|
2329
|
-
if (def.pageBackground?.src) {
|
|
2330
|
-
def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src, fallbackImage);
|
|
2331
|
-
}
|
|
2332
|
-
if (def.header?.backgroundImage) {
|
|
2333
|
-
def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
|
|
2334
|
-
}
|
|
2335
|
-
if (def.footer && typeof def.footer !== "function") {
|
|
2336
|
-
const footer = def.footer;
|
|
2337
|
-
if (footer.backgroundImage) {
|
|
2338
|
-
footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage, fallbackImage);
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2884
|
+
await materializeDocAssets(def, fallbackImage);
|
|
2341
2885
|
return new Promise((resolve, reject) => {
|
|
2342
|
-
const headerBandHeight = def.margins.top ?? 0;
|
|
2343
|
-
const footerBandHeight = def.margins.bottom ?? 0;
|
|
2344
2886
|
const doc = new import_pdfkit.default({
|
|
2345
2887
|
size: def.pageSize || "A4",
|
|
2346
2888
|
layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
|
|
2347
2889
|
margins: {
|
|
2348
|
-
top:
|
|
2349
|
-
bottom:
|
|
2890
|
+
top: def.margins.top ?? 0,
|
|
2891
|
+
bottom: def.margins.bottom ?? 0,
|
|
2350
2892
|
left: def.margins.left,
|
|
2351
2893
|
right: def.margins.right
|
|
2352
2894
|
}
|
|
2353
2895
|
});
|
|
2354
2896
|
const chunks = [];
|
|
2355
|
-
doc.on("data", (chunk) =>
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
});
|
|
2361
|
-
doc.on("error", (err) => {
|
|
2362
|
-
reject(
|
|
2897
|
+
doc.on("data", (chunk) => chunks.push(chunk));
|
|
2898
|
+
doc.on("end", () => resolve(Buffer.concat(chunks)));
|
|
2899
|
+
doc.on(
|
|
2900
|
+
"error",
|
|
2901
|
+
(err) => reject(
|
|
2363
2902
|
toPdfEngineError(err, {
|
|
2364
2903
|
code: "PDF_ERROR_PDFKIT_ERROR" /* PDF_ERROR_PDFKIT_ERROR */,
|
|
2365
2904
|
message: "PDFKit emitted an error while rendering.",
|
|
2366
2905
|
statusCode: 500,
|
|
2367
2906
|
retryable: true
|
|
2368
2907
|
})
|
|
2369
|
-
)
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
for (const f of def.fonts) {
|
|
2373
|
-
try {
|
|
2374
|
-
doc.registerFont(f.name, f.src);
|
|
2375
|
-
} catch (e) {
|
|
2376
|
-
console.warn("Failed to register font:", f.name, e);
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
const ctx = createInitialContext(doc);
|
|
2381
|
-
const styles = def.styles ?? {};
|
|
2382
|
-
const finishPage2 = (addNewPage) => finishPage({
|
|
2383
|
-
addNewPage,
|
|
2384
|
-
doc,
|
|
2385
|
-
def,
|
|
2386
|
-
ctx,
|
|
2387
|
-
footerBandHeight,
|
|
2388
|
-
renderBlockArray,
|
|
2389
|
-
startNewPageLayout
|
|
2390
|
-
});
|
|
2391
|
-
const processSignatureBlock = createProcessSignatureBlock({
|
|
2392
|
-
doc,
|
|
2393
|
-
ctx,
|
|
2394
|
-
styles,
|
|
2395
|
-
finishPage: finishPage2,
|
|
2396
|
-
contentEnvFn: contentEnv
|
|
2397
|
-
});
|
|
2398
|
-
const { renderBlock, renderBlockArray } = createBlockRenderer({
|
|
2399
|
-
doc,
|
|
2400
|
-
ctx,
|
|
2401
|
-
styles,
|
|
2402
|
-
computeColumnPixelWidths,
|
|
2403
|
-
finishPage: finishPage2,
|
|
2404
|
-
processSignatureBlock,
|
|
2405
|
-
defaultImage: fallbackImage
|
|
2406
|
-
});
|
|
2407
|
-
const startNewPageLayout = createStartNewPageLayout({
|
|
2408
|
-
doc,
|
|
2409
|
-
def,
|
|
2410
|
-
ctx,
|
|
2411
|
-
headerBandHeight,
|
|
2412
|
-
renderBlockArray
|
|
2413
|
-
});
|
|
2414
|
-
startNewPageLayout();
|
|
2415
|
-
for (const block of def.content) {
|
|
2416
|
-
if (block.type === "signature") {
|
|
2417
|
-
processSignatureBlock(block);
|
|
2418
|
-
continue;
|
|
2419
|
-
}
|
|
2420
|
-
if (ctx.afterSignature) {
|
|
2421
|
-
finishPage2(true);
|
|
2422
|
-
ctx.afterSignature = false;
|
|
2423
|
-
}
|
|
2424
|
-
renderBlock(block, null, contentEnv(doc));
|
|
2425
|
-
}
|
|
2426
|
-
finishPage2(false);
|
|
2427
|
-
doc.end();
|
|
2908
|
+
)
|
|
2909
|
+
);
|
|
2910
|
+
runRender(doc, def, fallbackImage);
|
|
2428
2911
|
});
|
|
2429
2912
|
}
|
|
2913
|
+
|
|
2914
|
+
// src/utils/pie-chart.ts
|
|
2915
|
+
function buildPieSlicePath(percent, options = {}) {
|
|
2916
|
+
const { cx = 55, cy = 55, radius: r = 50 } = options;
|
|
2917
|
+
const p = Math.max(0, Math.min(100, Number(percent) || 0));
|
|
2918
|
+
if (p <= 0) return "";
|
|
2919
|
+
if (p >= 100) {
|
|
2920
|
+
return `M ${cx} ${cy} L ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${(cx - 0.01).toFixed(2)} ${cy - r} Z`;
|
|
2921
|
+
}
|
|
2922
|
+
const startAngle = -Math.PI / 2;
|
|
2923
|
+
const endAngle = startAngle + p / 100 * 2 * Math.PI;
|
|
2924
|
+
const x1 = cx;
|
|
2925
|
+
const y1 = cy - r;
|
|
2926
|
+
const x2 = cx + r * Math.cos(endAngle);
|
|
2927
|
+
const y2 = cy + r * Math.sin(endAngle);
|
|
2928
|
+
const largeArc = p > 50 ? 1 : 0;
|
|
2929
|
+
return `M ${cx} ${cy} L ${x1.toFixed(1)} ${y1.toFixed(1)} A ${r} ${r} 0 ${largeArc} 1 ${x2.toFixed(1)} ${y2.toFixed(1)} Z`;
|
|
2930
|
+
}
|
|
2931
|
+
function calcPieData(input, options = {}) {
|
|
2932
|
+
const { obtained, total } = input;
|
|
2933
|
+
if (!Number.isFinite(total) || !Number.isFinite(obtained) || total <= 0) {
|
|
2934
|
+
throw new Error(`calcPieData: "total" must be a positive finite number, got ${total}`);
|
|
2935
|
+
}
|
|
2936
|
+
const percentage = Math.round(obtained / total * 100);
|
|
2937
|
+
const remaining = Math.max(0, total - obtained);
|
|
2938
|
+
return {
|
|
2939
|
+
obtained,
|
|
2940
|
+
total,
|
|
2941
|
+
remaining,
|
|
2942
|
+
percentage,
|
|
2943
|
+
piePath: buildPieSlicePath(percentage, options)
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
function calcPercentagePie(percentage, options = {}) {
|
|
2947
|
+
const p = Math.max(0, Math.min(100, Math.round(Number(percentage) || 0)));
|
|
2948
|
+
return {
|
|
2949
|
+
percentage: p,
|
|
2950
|
+
remaining: 100 - p,
|
|
2951
|
+
piePath: buildPieSlicePath(p, options)
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
var DEFAULT_PIE_OPTIONS = { cx: 50, cy: 50, radius: 42 };
|
|
2430
2955
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2431
2956
|
0 && (module.exports = {
|
|
2432
2957
|
BARCODE_TYPES,
|
|
2958
|
+
DEFAULT_PIE_OPTIONS,
|
|
2433
2959
|
PdfEngineError,
|
|
2434
2960
|
PdfEngineErrorCode,
|
|
2435
2961
|
QR_ERROR_LEVEL,
|
|
2962
|
+
buildPieSlicePath,
|
|
2963
|
+
calcPercentagePie,
|
|
2964
|
+
calcPieData,
|
|
2965
|
+
computeColumnPixelWidths,
|
|
2966
|
+
contentEnv,
|
|
2967
|
+
createBlockRenderer,
|
|
2968
|
+
createBottomLimitForContent,
|
|
2969
|
+
createEnsureSpaceFor,
|
|
2970
|
+
createInitialContext,
|
|
2971
|
+
createMeasureBlockHeight,
|
|
2972
|
+
createProcessSignatureBlock,
|
|
2973
|
+
createStartNewPageLayout,
|
|
2974
|
+
drawFooter,
|
|
2975
|
+
drawHeader,
|
|
2976
|
+
drawPageBackground,
|
|
2977
|
+
drawSignatureBlock,
|
|
2978
|
+
drawStyledText,
|
|
2979
|
+
drawWatermarkForPage,
|
|
2980
|
+
finishPage,
|
|
2981
|
+
generateBarcodeBuffer,
|
|
2982
|
+
generateQrBuffer,
|
|
2983
|
+
getBottomLimitForContent,
|
|
2984
|
+
getFontNameForText,
|
|
2985
|
+
hasPadding,
|
|
2436
2986
|
images,
|
|
2437
2987
|
isPdfEngineError,
|
|
2988
|
+
mapBarcodeTypeToBcid,
|
|
2989
|
+
materializeImagesInBlocks,
|
|
2990
|
+
materializeQrAndBarcodesInBlocks,
|
|
2991
|
+
mergeStyleDefs,
|
|
2992
|
+
normalizeImageSrc,
|
|
2438
2993
|
renderCustomPdf,
|
|
2439
2994
|
renderCustomPdfToBuffer,
|
|
2440
|
-
|
|
2995
|
+
resolveBlockPadding,
|
|
2996
|
+
resolveFooterPadding,
|
|
2997
|
+
resolveHeaderPadding,
|
|
2998
|
+
resolveSpacing,
|
|
2999
|
+
resolveTableCell,
|
|
3000
|
+
resolveTextBlock,
|
|
3001
|
+
toPdfEngineError,
|
|
3002
|
+
watermarkUsesLast
|
|
2441
3003
|
});
|