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/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
- toPdfEngineError: () => toPdfEngineError
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 measureTextHeight = (tb, width) => {
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 = measureTextHeight(keyResolved, colWidth);
420
+ const keyHeight = measureTextHeight2(keyResolved, colWidth);
385
421
  const valueBase = buildValueBlock(item);
386
422
  const valueResolved = resolveStyled(valueBase, valueStyleNames);
387
- const valueHeight = measureTextHeight(valueResolved, colWidth);
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 = measureTextHeight(keyResolved, keyWidthPx);
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 = measureTextHeight(valueResolved, valueWidth);
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 = measureTextHeight(keyResolved, colWidth);
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 = measureTextHeight(valueResolved, colWidth);
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 = measureTextHeight(keyResolved, keyWidthPx);
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 = measureTextHeight(valueResolved, valueWidth);
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 textHeight = doc.heightOfString(tb.text, { width });
1343
+ const lineGap = tb.lineGap ?? 4;
1344
+ const textHeight = doc.heightOfString(tb.text, { width, lineGap });
1068
1345
  if (y === null) {
1069
- const gapCheck = tb.lineGap ?? 4;
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 gap = tb.lineGap ?? 4;
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/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
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
- // 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);
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
- 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
- }
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 = (b, env) => {
1583
- const tb = resolveTextBlock(styles, b);
1584
- const width = Math.max(env.innerWidth, 1);
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 h = doc.heightOfString(tb.text ?? "", { width });
1588
- const gap = tb.lineGap ?? 4;
1589
- return h + gap;
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 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);
1540
+ const measureImage = (block) => {
1541
+ return (block.marginTop ?? 0) + (block.height ?? 50) + (block.marginBottom ?? 0);
1595
1542
  };
1596
- const measureQr = (b) => {
1597
- const size = b.size ?? 80;
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 = (b) => {
1601
- const h = b.height ?? 40;
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 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;
1549
+ const measureShape = (block) => {
1550
+ return (block.marginTop ?? 0) + (block.height ?? block.lineWidth ?? 1) + (block.marginBottom ?? 0);
1609
1551
  };
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;
1552
+ const measureLine = (block) => {
1553
+ return (block.marginTop ?? 0) + (block.lineWidth ?? 1) + (block.marginBottom ?? 0);
1623
1554
  };
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;
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 = b.columns ?? [];
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 = b.mode ?? "fixedGap";
1635
- let gap;
1565
+ const mode = block.mode ?? "fixedGap";
1636
1566
  if (mode === "spaceBetween" && n > 1) {
1637
- if (b.widths && b.widths.length === n) {
1638
- colWidths = computeColumnPixelWidths2(b.widths, totalWidth);
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 = b.gap ?? 20;
1573
+ const gap = block.gap ?? 20;
1648
1574
  const totalGapsWidth = gap * (n - 1);
1649
1575
  const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
1650
- if (b.widths && b.widths.length === n) {
1651
- colWidths = computeColumnPixelWidths2(b.widths, widthForCols);
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 colH = 0;
1590
+ let colHeight = 0;
1665
1591
  for (const child of colBlocks) {
1666
1592
  if (!child || child.visible === false) continue;
1667
- colH += measure(child, colEnv);
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
- heights.push(colH);
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
- 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);
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
- 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;
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
- 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;
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 (block, env) => measure(block, env);
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-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
- };
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: { valuePreview: String(value).slice(0, 60), size },
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
- 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
- );
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
- resolve(png);
1887
- });
2409
+ );
1888
2410
  });
1889
2411
  }
1890
2412
  async function materializeQrAndBarcodesInBlocks(blocks) {
1891
2413
  const out = [];
1892
2414
  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 };
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
- const size = qb.size ?? 80;
1918
- const buf = await generateQrBuffer(qb.value, size, qb.qrVersion, qb.errorCorrectionLevel);
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 = { ...block };
2427
+ const bb = blockAny;
1924
2428
  if (!bb.src && bb.value) {
1925
- const buf = await generateBarcodeBuffer(bb.value, {
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 = block;
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 { ...cell, blocks: await materializeQrAndBarcodesInBlocks(cell.blocks) };
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 { ...entry, content: await Promise.all(entry.content.map(materializeCell)) };
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(block);
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
- 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) {
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
- if (def.header?.backgroundImage) {
2226
- def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
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
- if (def.footer && typeof def.footer !== "function") {
2229
- const footer = def.footer;
2230
- if (footer.backgroundImage) {
2231
- footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage, fallbackImage);
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: headerBandHeight,
2242
- bottom: footerBandHeight,
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
- 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
- }
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: headerBandHeight,
2349
- bottom: footerBandHeight,
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
- chunks.push(chunk);
2357
- });
2358
- doc.on("end", () => {
2359
- resolve(Buffer.concat(chunks));
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
- 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();
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
- toPdfEngineError
2995
+ resolveBlockPadding,
2996
+ resolveFooterPadding,
2997
+ resolveHeaderPadding,
2998
+ resolveSpacing,
2999
+ resolveTableCell,
3000
+ resolveTextBlock,
3001
+ toPdfEngineError,
3002
+ watermarkUsesLast
2441
3003
  });