av6-pdf-engine 2.0.0 → 3.0.0

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