av6-pdf-engine 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -223,12 +223,11 @@ var processImageBlock = (doc, ctx, block, y, env, ensureSpaceFor, defaultImage)
223
223
  const availableWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
224
224
  const mt = block.marginTop ?? 0;
225
225
  const mb = block.marginBottom ?? 0;
226
- const baseY = y ?? ctx.currentY;
227
- const startY = baseY + mt;
228
226
  const heightNeeded = mt + imgHeight + mb;
229
227
  if (y === null) {
230
228
  ensureSpaceFor(heightNeeded, env);
231
229
  }
230
+ const startY = (y ?? ctx.currentY) + mt;
232
231
  let x = baseLeft;
233
232
  if (block.align === "center") {
234
233
  x = baseLeft + (availableWidth - imgWidth) / 2;
@@ -304,7 +303,7 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
304
303
  ...base,
305
304
  style: styleNames
306
305
  });
307
- const measureTextHeight = (tb, width) => {
306
+ const measureTextHeight2 = (tb, width) => {
308
307
  const fontName = getFontNameForText(tb);
309
308
  doc.font(fontName);
310
309
  doc.fontSize(tb.fontSize ?? 10);
@@ -337,10 +336,10 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
337
336
  const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
338
337
  const keyBase = buildKeyBlock(item);
339
338
  const keyResolved = resolveStyled(keyBase, keyStyleNames);
340
- const keyHeight = measureTextHeight(keyResolved, colWidth);
339
+ const keyHeight = measureTextHeight2(keyResolved, colWidth);
341
340
  const valueBase = buildValueBlock(item);
342
341
  const valueResolved = resolveStyled(valueBase, valueStyleNames);
343
- const valueHeight = measureTextHeight(valueResolved, colWidth);
342
+ const valueHeight = measureTextHeight2(valueResolved, colWidth);
344
343
  const rowHeight = keyHeight + keyValueGap + valueHeight;
345
344
  colHeight += rowHeight + rowGap;
346
345
  }
@@ -360,12 +359,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
360
359
  const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
361
360
  const keyBase = buildKeyBlock(item);
362
361
  const keyResolved = resolveStyled(keyBase, keyStyleNames);
363
- const keyHeight = measureTextHeight(keyResolved, keyWidthPx);
362
+ const keyHeight = measureTextHeight2(keyResolved, keyWidthPx);
364
363
  const valueBase = buildValueBlock(item);
365
364
  const valueResolved = resolveStyled(valueBase, valueStyleNames);
366
365
  const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
367
366
  const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
368
- const valueHeight = measureTextHeight(valueResolved, valueWidth);
367
+ const valueHeight = measureTextHeight2(valueResolved, valueWidth);
369
368
  const cellHeight = Math.max(keyHeight, valueHeight);
370
369
  if (cellHeight > rowHeight) rowHeight = cellHeight;
371
370
  }
@@ -393,12 +392,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
393
392
  const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
394
393
  const keyBlock = buildKeyBlock(item);
395
394
  const keyResolved = resolveStyled(keyBlock, keyStyleNames);
396
- const keyHeight = measureTextHeight(keyResolved, colWidth);
395
+ const keyHeight = measureTextHeight2(keyResolved, colWidth);
397
396
  drawStyledText(doc, keyResolved, colX, colY, colWidth);
398
397
  colY += keyHeight + keyValueGap;
399
398
  const valueBlock = buildValueBlock(item);
400
399
  const valueResolved = resolveStyled(valueBlock, valueStyleNames);
401
- const valueHeight = measureTextHeight(valueResolved, colWidth);
400
+ const valueHeight = measureTextHeight2(valueResolved, colWidth);
402
401
  drawStyledText(doc, valueResolved, colX, colY, colWidth);
403
402
  colY += valueHeight + rowGap;
404
403
  }
@@ -418,12 +417,12 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
418
417
  const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
419
418
  const keyBase = buildKeyBlock(item);
420
419
  const keyResolved = resolveStyled(keyBase, keyStyleNames);
421
- const keyHeight = measureTextHeight(keyResolved, keyWidthPx);
420
+ const keyHeight = measureTextHeight2(keyResolved, keyWidthPx);
422
421
  const valueBase = buildValueBlock(item);
423
422
  const valueResolved = resolveStyled(valueBase, valueStyleNames);
424
423
  const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
425
424
  const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
426
- const valueHeight = measureTextHeight(valueResolved, valueWidth);
425
+ const valueHeight = measureTextHeight2(valueResolved, valueWidth);
427
426
  const cellHeight = Math.max(keyHeight, valueHeight);
428
427
  if (cellHeight > rowHeight) rowHeight = cellHeight;
429
428
  }
@@ -464,6 +463,149 @@ var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPi
464
463
  return endY;
465
464
  };
466
465
 
466
+ // src/renderer-engine/blocks/list.ts
467
+ var DEFAULT_BULLET = "\u2022";
468
+ var DEFAULT_MARKER_GAP = 6;
469
+ var DEFAULT_ITEM_GAP = 4;
470
+ var MARKER_PADDING = 2;
471
+ var normalizeListItem = (item) => typeof item === "string" ? { text: item } : item;
472
+ var buildTextBlock = (list, item) => ({
473
+ type: "text",
474
+ text: item.text ?? "",
475
+ fontSize: item.fontSize ?? list.fontSize,
476
+ lineGap: item.lineGap ?? list.lineGap,
477
+ color: item.color ?? list.color,
478
+ bold: item.bold,
479
+ italic: item.italic,
480
+ underline: item.underline,
481
+ strike: item.strike,
482
+ link: item.link,
483
+ font: item.font,
484
+ style: item.style ?? list.style
485
+ });
486
+ var getMarker = (list, index) => {
487
+ if ((list.listStyle ?? "bullet") === "number") {
488
+ const start = list.start ?? 1;
489
+ return `${start + index}.`;
490
+ }
491
+ return list.bullet ?? DEFAULT_BULLET;
492
+ };
493
+ var applyTextOptions = (doc, tb) => {
494
+ doc.fontSize(tb.fontSize ?? 10);
495
+ doc.font(getFontNameForText(tb));
496
+ if (tb.color) doc.fillColor(tb.color);
497
+ else doc.fillColor("black");
498
+ };
499
+ var measureTextHeight = (doc, tb, width) => {
500
+ applyTextOptions(doc, tb);
501
+ const lineGap = tb.lineGap ?? 4;
502
+ return doc.heightOfString(tb.text ?? "", { width, lineGap });
503
+ };
504
+ var measureMarkerWidth = (doc, marker, tb) => {
505
+ applyTextOptions(doc, tb);
506
+ return doc.widthOfString(marker) + MARKER_PADDING;
507
+ };
508
+ var computeMarkerWidth = (doc, list, items, tbSample) => {
509
+ if (list.markerWidth !== void 0) return list.markerWidth;
510
+ let maxW = 0;
511
+ for (let i = 0; i < items.length; i++) {
512
+ const marker = getMarker(list, i);
513
+ const w = measureMarkerWidth(doc, marker, tbSample);
514
+ if (w > maxW) maxW = w;
515
+ }
516
+ return maxW;
517
+ };
518
+ var measureItemHeight = (doc, list, item, index, markerWidth, textWidth, styles) => {
519
+ const tb = resolveTextBlock(styles, buildTextBlock(list, item));
520
+ const marker = getMarker(list, index);
521
+ const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
522
+ const markerH = measureTextHeight(doc, markerTb, markerWidth);
523
+ const textH = measureTextHeight(doc, tb, textWidth);
524
+ const lineGap = tb.lineGap ?? 4;
525
+ return Math.max(markerH, textH) + lineGap;
526
+ };
527
+ var measureListHeight = (doc, styles, block, env) => {
528
+ const items = (block.items ?? []).map(normalizeListItem);
529
+ const mt = block.marginTop ?? 0;
530
+ const mb = block.marginBottom ?? 0;
531
+ const itemGap = block.itemGap ?? DEFAULT_ITEM_GAP;
532
+ const markerGap = block.markerGap ?? DEFAULT_MARKER_GAP;
533
+ if (!items.length) return mt + mb;
534
+ const localLeft = block.marginLeft ?? 0;
535
+ const localRight = block.marginRight ?? 0;
536
+ const innerWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
537
+ const firstResolved = resolveTextBlock(styles, buildTextBlock(block, items[0]));
538
+ const markerWidth = computeMarkerWidth(doc, block, items, firstResolved);
539
+ const textWidth = Math.max(innerWidth - markerWidth - markerGap, 1);
540
+ let total = mt;
541
+ for (let i = 0; i < items.length; i++) {
542
+ total += measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
543
+ if (i < items.length - 1) total += itemGap;
544
+ }
545
+ return total + mb;
546
+ };
547
+ var drawListText = (doc, tb, x, y, width) => {
548
+ applyTextOptions(doc, tb);
549
+ const lineGap = tb.lineGap ?? 4;
550
+ doc.text(tb.text ?? "", x, y, {
551
+ width,
552
+ align: tb.align ?? "left",
553
+ underline: !!tb.underline,
554
+ strike: !!tb.strike,
555
+ link: tb.link,
556
+ lineGap
557
+ });
558
+ };
559
+ var processListBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
560
+ const items = (block.items ?? []).map(normalizeListItem);
561
+ const mt = block.marginTop ?? 0;
562
+ const mb = block.marginBottom ?? 0;
563
+ const itemGap = block.itemGap ?? DEFAULT_ITEM_GAP;
564
+ const markerGap = block.markerGap ?? DEFAULT_MARKER_GAP;
565
+ const localLeft = block.marginLeft ?? 0;
566
+ const localRight = block.marginRight ?? 0;
567
+ const xLeft = env.marginLeft + localLeft;
568
+ const innerWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
569
+ if (!items.length) {
570
+ const endY2 = (y ?? ctx.currentY) + mt + mb;
571
+ if (y === null) ctx.currentY = endY2;
572
+ return endY2;
573
+ }
574
+ const firstResolved = resolveTextBlock(styles, buildTextBlock(block, items[0]));
575
+ const markerWidth = computeMarkerWidth(doc, block, items, firstResolved);
576
+ const textX = xLeft + markerWidth + markerGap;
577
+ const textWidth = Math.max(innerWidth - markerWidth - markerGap, 1);
578
+ if (y !== null) {
579
+ let drawY = y + mt;
580
+ for (let i = 0; i < items.length; i++) {
581
+ if (i > 0) drawY += itemGap;
582
+ const tb = resolveTextBlock(styles, buildTextBlock(block, items[i]));
583
+ const marker = getMarker(block, i);
584
+ const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
585
+ const itemHeight = measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
586
+ drawListText(doc, markerTb, xLeft, drawY, markerWidth);
587
+ drawListText(doc, tb, textX, drawY, textWidth);
588
+ drawY += itemHeight;
589
+ }
590
+ return drawY + mb;
591
+ }
592
+ for (let i = 0; i < items.length; i++) {
593
+ const leading = i === 0 ? mt : itemGap;
594
+ const itemHeight = measureItemHeight(doc, block, items[i], i, markerWidth, textWidth, styles);
595
+ ensureSpaceFor(leading + itemHeight, env);
596
+ const drawY = ctx.currentY + leading;
597
+ const tb = resolveTextBlock(styles, buildTextBlock(block, items[i]));
598
+ const marker = getMarker(block, i);
599
+ const markerTb = { ...tb, text: marker, underline: false, strike: false, link: void 0 };
600
+ drawListText(doc, markerTb, xLeft, drawY, markerWidth);
601
+ drawListText(doc, tb, textX, drawY, textWidth);
602
+ ctx.currentY = drawY + itemHeight;
603
+ }
604
+ const endY = ctx.currentY + mb;
605
+ ctx.currentY = endY;
606
+ return endY;
607
+ };
608
+
467
609
  // src/renderer-engine/blocks/line.ts
468
610
  var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
469
611
  const localLeft = block.marginLeft ?? 0;
@@ -472,13 +614,12 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
472
614
  const width = Math.max(env.innerWidth - localLeft - localRight, 1);
473
615
  const mt = block.marginTop ?? 0;
474
616
  const mb = block.marginBottom ?? 0;
475
- const baseY = y ?? ctx.currentY;
476
- const startY = baseY + mt;
477
617
  const lineWidth = block.lineWidth ?? 1;
478
618
  const heightNeeded = mt + lineWidth + mb;
479
619
  if (y === null) {
480
620
  ensureSpaceFor(heightNeeded, env);
481
621
  }
622
+ const startY = (y ?? ctx.currentY) + mt;
482
623
  doc.save();
483
624
  if (block.color) {
484
625
  doc.strokeColor(block.color);
@@ -491,7 +632,99 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
491
632
  return newY;
492
633
  };
493
634
 
635
+ // src/renderer-engine/blocks/shape.ts
636
+ var processShapeBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
637
+ const mt = block.marginTop ?? 0;
638
+ const mb = block.marginBottom ?? 0;
639
+ const ml = block.marginLeft ?? 0;
640
+ const mr = block.marginRight ?? 0;
641
+ const lineWidth = block.lineWidth ?? 1;
642
+ const width = block.width ?? Math.max(env.innerWidth - ml - mr, 1);
643
+ const height = block.height ?? lineWidth;
644
+ const heightNeeded = mt + height + mb;
645
+ if (y === null) {
646
+ ensureSpaceFor(heightNeeded, env);
647
+ }
648
+ const startY = (y ?? ctx.currentY) + mt;
649
+ const availableWidth = Math.max(env.innerWidth - ml - mr, 1);
650
+ let x = env.marginLeft + ml;
651
+ if (block.align === "center") {
652
+ x = env.marginLeft + ml + (availableWidth - width) / 2;
653
+ } else if (block.align === "right") {
654
+ x = env.marginLeft + ml + availableWidth - width;
655
+ }
656
+ const drawY = startY;
657
+ doc.save();
658
+ if (typeof block.opacity === "number") {
659
+ doc.opacity(block.opacity);
660
+ }
661
+ doc.lineWidth(lineWidth);
662
+ if (block.strokeColor) {
663
+ doc.strokeColor(block.strokeColor);
664
+ }
665
+ if (block.fillColor) {
666
+ doc.fillColor(block.fillColor);
667
+ }
668
+ switch (block.shape) {
669
+ case "rect":
670
+ doc.rect(x, drawY, width, height);
671
+ break;
672
+ case "roundedRect":
673
+ doc.roundedRect(x, drawY, width, height, block.radius ?? 6);
674
+ break;
675
+ case "circle": {
676
+ const radius = block.radius ?? Math.min(width, height) / 2;
677
+ doc.circle(x + radius, drawY + radius, radius);
678
+ break;
679
+ }
680
+ case "ellipse":
681
+ doc.ellipse(x + width / 2, drawY + height / 2, width / 2, height / 2);
682
+ break;
683
+ case "line":
684
+ doc.moveTo(x, drawY).lineTo(x + width, drawY);
685
+ break;
686
+ case "polygon":
687
+ if (block.points?.length) {
688
+ const [first, ...rest] = block.points;
689
+ doc.moveTo(x + first.x, drawY + first.y);
690
+ rest.forEach((point) => {
691
+ doc.lineTo(x + point.x, drawY + point.y);
692
+ });
693
+ doc.closePath();
694
+ }
695
+ break;
696
+ case "path":
697
+ if (block.path) {
698
+ const safePath = block.path.trim().replace(/[\n\r\t]+/g, " ").replace(/\s+/g, " ");
699
+ if (safePath.includes("undefined") || safePath.includes("NaN")) {
700
+ throw new Error(`Invalid shape path: ${safePath}`);
701
+ }
702
+ doc.translate(x, drawY);
703
+ doc.path(safePath);
704
+ }
705
+ break;
706
+ }
707
+ if (block.fillColor && block.strokeColor) {
708
+ doc.fillAndStroke(block.fillColor, block.strokeColor);
709
+ } else if (block.fillColor) {
710
+ doc.fill(block.fillColor);
711
+ } else {
712
+ doc.stroke();
713
+ }
714
+ doc.restore();
715
+ const newY = startY + height + mb;
716
+ if (y === null) {
717
+ ctx.currentY = newY;
718
+ }
719
+ return newY;
720
+ };
721
+
494
722
  // src/renderer-engine/blocks/table.ts
723
+ var shouldSkipHeaderOnlyTable = (table, rows) => {
724
+ const headerRows = table.headerRows ?? 0;
725
+ if (headerRows <= 0) return false;
726
+ return rows.length <= headerRows;
727
+ };
495
728
  var normalizeSpan = (value) => {
496
729
  if (!value || value < 1) return 1;
497
730
  return Math.floor(value);
@@ -646,6 +879,9 @@ var measureTableHeight = (doc, table, env, styles, computeColumnPixelWidths2, me
646
879
  const localRight = table.marginRight ?? 0;
647
880
  const width = Math.max(env.innerWidth - localLeft - localRight, 1);
648
881
  const rows = normalizeTableRows(table);
882
+ if (shouldSkipHeaderOnlyTable(table, rows)) {
883
+ return 0;
884
+ }
649
885
  const totalCols = table.widths.length;
650
886
  const colWidths = computeColumnPixelWidths2(table.widths, width);
651
887
  const rowHeights = [];
@@ -722,6 +958,9 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
722
958
  };
723
959
  const headerRows = table.headerRows ?? 0;
724
960
  const rows = normalizeTableRows(table);
961
+ if (shouldSkipHeaderOnlyTable(table, rows)) {
962
+ return baseY;
963
+ }
725
964
  const buildPlacementAndHeights = (rows2) => {
726
965
  const placedRows = [];
727
966
  const rowHeights = [];
@@ -1001,12 +1240,12 @@ var drawCellBorder = (doc, border, x, y, width, height) => {
1001
1240
  // src/renderer-engine/blocks/text.ts
1002
1241
  var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
1003
1242
  const tb = resolveTextBlock(styles, block);
1243
+ const mt = tb.marginTop ?? 0;
1244
+ const mb = tb.marginBottom ?? 0;
1004
1245
  const localLeft = tb.marginLeft ?? 0;
1005
1246
  const localRight = tb.marginRight ?? 0;
1006
1247
  const xLeft = env.marginLeft + localLeft;
1007
1248
  const width = Math.max(env.innerWidth - localLeft - localRight, 1);
1008
- const baseY = y ?? ctx.currentY;
1009
- const startY = baseY;
1010
1249
  doc.fontSize(tb.fontSize ?? 10);
1011
1250
  const isBold = !!tb.bold;
1012
1251
  const isItalic = !!tb.italic;
@@ -1020,11 +1259,12 @@ var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
1020
1259
  doc.font(fontName);
1021
1260
  if (tb.color) doc.fillColor(tb.color);
1022
1261
  else doc.fillColor("black");
1023
- const textHeight = doc.heightOfString(tb.text, { width });
1262
+ const lineGap = tb.lineGap ?? 4;
1263
+ const textHeight = doc.heightOfString(tb.text, { width, lineGap });
1024
1264
  if (y === null) {
1025
- const gapCheck = tb.lineGap ?? 4;
1026
- ensureSpaceFor(textHeight + gapCheck, env);
1265
+ ensureSpaceFor(mt + textHeight + lineGap + mb, env);
1027
1266
  }
1267
+ const startY = (y ?? ctx.currentY) + mt;
1028
1268
  doc.text(tb.text, xLeft, startY, {
1029
1269
  width,
1030
1270
  align: tb.align ?? "left",
@@ -1032,414 +1272,82 @@ var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
1032
1272
  strike: !!tb.strike,
1033
1273
  link: tb.link
1034
1274
  });
1035
- const gap = tb.lineGap ?? 4;
1036
- const newY = startY + textHeight + gap;
1275
+ const newY = doc.y + lineGap + mb;
1037
1276
  if (y === null) ctx.currentY = newY;
1038
1277
  return newY;
1039
1278
  };
1040
1279
 
1041
- // src/renderer-engine/utils/block-renderer.ts
1042
- function createBlockRenderer(deps) {
1043
- const { doc, ctx, styles, computeColumnPixelWidths: computeColumnPixelWidths2, finishPage: finishPage2, processSignatureBlock, defaultImage } = deps;
1044
- const bottomLimitForContent = createBottomLimitForContent(doc, ctx);
1045
- const ensureSpaceFor = createEnsureSpaceFor(ctx, bottomLimitForContent, finishPage2);
1046
- const measureBlockHeight = createMeasureBlockHeight({
1047
- doc,
1048
- styles,
1049
- computeColumnPixelWidths: computeColumnPixelWidths2
1050
- });
1051
- const renderBlock = (block, y, env) => {
1052
- if (block.visible === false) {
1053
- return y ?? ctx.currentY;
1054
- }
1055
- if ((block.backgroundColor || block.backgroundImage) && block.type !== "pageBreak" && block.type !== "signature") {
1056
- const startY = y ?? ctx.currentY;
1057
- const blockHeight = measureBlockHeight(block, env);
1058
- const opacity = block.backgroundOpacity ?? 1;
1059
- doc.save();
1060
- if (block.backgroundColor) {
1061
- doc.fillOpacity(opacity).rect(env.marginLeft, startY, env.innerWidth, blockHeight).fill(block.backgroundColor);
1062
- doc.fillOpacity(1);
1063
- }
1064
- if (block.backgroundImage) {
1065
- try {
1066
- doc.image(block.backgroundImage, env.marginLeft, startY, {
1067
- width: env.innerWidth,
1068
- height: blockHeight
1069
- });
1070
- } catch (_) {
1071
- }
1072
- }
1073
- doc.restore();
1074
- }
1075
- switch (block.type) {
1076
- case "text":
1077
- return processTextBlock(doc, ctx, styles, block, y, env, ensureSpaceFor);
1078
- case "image":
1079
- return processImageBlock(doc, ctx, block, y, env, ensureSpaceFor, defaultImage);
1080
- case "qr": {
1081
- const qb = block;
1082
- const imageLike = {
1083
- type: "image",
1084
- src: qb.src,
1085
- width: qb.size,
1086
- height: qb.size,
1087
- align: qb.align,
1088
- marginTop: qb.marginTop,
1089
- marginBottom: qb.marginBottom,
1090
- marginLeft: qb.marginLeft,
1091
- marginRight: qb.marginRight
1092
- };
1093
- return processImageBlock(doc, ctx, imageLike, y, env, ensureSpaceFor, defaultImage);
1094
- }
1095
- case "barcode":
1096
- return processBarcodeBlock(doc, ctx, block, y, env, ensureSpaceFor);
1097
- case "line":
1098
- return processLineBlock(doc, ctx, block, y, env, ensureSpaceFor);
1099
- case "table":
1100
- return processTableBlock(
1101
- doc,
1102
- ctx,
1103
- styles,
1104
- block,
1105
- y,
1106
- env,
1107
- computeColumnPixelWidths2,
1108
- bottomLimitForContent,
1109
- finishPage2,
1110
- (b, blockY, blockEnv) => renderBlock(b, blockY, blockEnv),
1111
- measureBlockHeight
1112
- );
1113
- case "columns":
1114
- return processColumnsBlock(
1115
- ctx,
1116
- block,
1117
- y,
1118
- env,
1119
- computeColumnPixelWidths2,
1120
- renderBlock,
1121
- ensureSpaceFor,
1122
- measureBlockHeight
1123
- );
1124
- case "keyValueGrid":
1125
- return processKeyValueGridBlock(
1126
- doc,
1127
- ctx,
1128
- styles,
1129
- block,
1130
- y,
1131
- env,
1132
- computeColumnPixelWidths2,
1133
- ensureSpaceFor
1134
- );
1135
- case "signature":
1136
- if (!env.allowPageBreak) {
1137
- throw new PdfEngineError({
1138
- code: "PDF_ERROR_SIGNATURE_NOT_IN_MAIN_FLOW" /* PDF_ERROR_SIGNATURE_NOT_IN_MAIN_FLOW */,
1139
- message: "Signature block is only allowed in main content flow.",
1140
- statusCode: 400,
1141
- details: { blockType: "signature" }
1142
- });
1143
- }
1144
- if (y !== null) {
1145
- throw new PdfEngineError({
1146
- code: "PDF_ERROR_SIGNATURE_EXPLICIT_Y_FORBIDDEN" /* PDF_ERROR_SIGNATURE_EXPLICIT_Y_FORBIDDEN */,
1147
- message: "Signature block must be part of main flow, not drawn at explicit Y.",
1148
- statusCode: 400,
1149
- details: { blockType: "signature", y }
1150
- });
1151
- }
1152
- processSignatureBlock(block);
1153
- return ctx.currentY;
1154
- case "pageBreak":
1155
- if (!env.allowPageBreak || ctx.inFooter) {
1156
- return y ?? ctx.currentY;
1157
- }
1158
- finishPage2(true);
1159
- return ctx.currentY;
1160
- default:
1161
- return ctx.currentY;
1162
- }
1163
- };
1164
- const renderBlockArray = (blocks, startY, env) => {
1165
- let localY = startY;
1166
- for (const block of blocks) {
1167
- if (block.visible === false) {
1168
- continue;
1169
- }
1170
- localY = renderBlock(block, localY, env);
1171
- }
1172
- return localY;
1173
- };
1174
- return {
1175
- renderBlock,
1176
- renderBlockArray
1177
- };
1178
- }
1179
-
1180
- // src/renderer-engine/utils/context.ts
1181
- function createInitialContext(doc) {
1182
- return {
1183
- pageNumber: 1,
1184
- currentY: doc.page.margins.top,
1185
- signatureBlock: null,
1186
- signatureTopY: null,
1187
- signatureHeight: 0,
1188
- signaturePlaced: false,
1189
- afterSignature: false,
1190
- inFooter: false
1191
- };
1192
- }
1193
-
1194
1280
  // src/renderer-engine/utils/ensure-space.ts
1195
- function createEnsureSpaceFor(ctx, bottomLimitForContent, finishPage2) {
1281
+ function createEnsureSpaceFor(ctx, doc, bottomLimitForContent, finishPage2) {
1196
1282
  return (heightNeeded, env) => {
1197
1283
  if (!env.allowPageBreak || ctx.inFooter) return;
1198
1284
  const bottomLimit = bottomLimitForContent();
1285
+ const fullContentHeight = bottomLimit - doc.page.margins.top;
1286
+ if (heightNeeded > fullContentHeight) {
1287
+ console.warn(
1288
+ `[pdf-engine] Block height (${Math.round(heightNeeded)}pt) exceeds page content area (${Math.round(fullContentHeight)}pt). Content will overflow.`
1289
+ );
1290
+ }
1199
1291
  if (ctx.currentY + heightNeeded > bottomLimit) {
1200
1292
  finishPage2(true);
1201
1293
  }
1202
1294
  };
1203
1295
  }
1204
1296
 
1205
- // src/renderer-engine/utils/env.ts
1206
- var contentEnv = (doc) => ({
1207
- marginLeft: doc.page.margins.left,
1208
- innerWidth: doc.page.width - doc.page.margins.left - doc.page.margins.right,
1209
- allowPageBreak: true
1297
+ // src/renderer-engine/utils/spacing.ts
1298
+ var emptySpacing = () => ({
1299
+ top: 0,
1300
+ right: 0,
1301
+ bottom: 0,
1302
+ left: 0
1210
1303
  });
1211
-
1212
- // src/renderer-engine/utils/finish-page.ts
1213
- function finishPage({
1214
- addNewPage,
1215
- doc,
1216
- def,
1217
- ctx,
1218
- footerBandHeight,
1219
- renderBlockArray,
1220
- startNewPageLayout
1221
- }) {
1222
- const mode = def.watermark?.mode;
1223
- if (def.watermark && watermarkUsesLast(mode)) {
1224
- const isLast = !addNewPage;
1225
- drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, isLast);
1226
- }
1227
- if (ctx.signatureBlock && ctx.signatureTopY !== null) {
1228
- const env = contentEnv(doc);
1229
- drawSignatureBlock(doc, ctx.signatureBlock, ctx.signatureTopY, env, renderBlockArray);
1304
+ var resolveSpacing = (value, overrides) => {
1305
+ const result = emptySpacing();
1306
+ if (typeof value === "number") {
1307
+ result.top = value;
1308
+ result.right = value;
1309
+ result.bottom = value;
1310
+ result.left = value;
1311
+ } else if (value) {
1312
+ result.top = value.top ?? 0;
1313
+ result.right = value.right ?? 0;
1314
+ result.bottom = value.bottom ?? 0;
1315
+ result.left = value.left ?? 0;
1230
1316
  }
1231
- drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray);
1232
- if (addNewPage) {
1233
- doc.addPage();
1234
- ctx.pageNumber += 1;
1235
- startNewPageLayout();
1236
- }
1237
- }
1238
-
1239
- // src/renderer-engine/utils/footer.ts
1240
- function normalizeFooter(input, ctx, doc) {
1241
- if (!input) return null;
1242
- if (typeof input === "function") {
1243
- const result = input(ctx.pageNumber, {
1244
- width: doc.page.width,
1245
- height: doc.page.height
1246
- });
1247
- if (!result) return null;
1248
- return normalizeFooter(result, ctx, doc);
1249
- }
1250
- if (input.blocks && Array.isArray(input.blocks)) {
1251
- const f = input;
1252
- return {
1253
- visible: f.visible,
1254
- blocks: f.blocks,
1255
- marginTop: f.marginTop,
1256
- marginBottom: f.marginBottom,
1257
- marginLeft: f.marginLeft,
1258
- marginRight: f.marginRight,
1259
- backgroundColor: f.backgroundColor,
1260
- backgroundImage: f.backgroundImage
1261
- };
1262
- }
1263
- if (input.type) {
1264
- return { blocks: [input] };
1265
- }
1266
- if (Array.isArray(input)) {
1267
- return { blocks: input };
1268
- }
1269
- return null;
1270
- }
1271
- function drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray) {
1272
- const footerConfig = def.footer;
1273
- if (!footerConfig) return;
1274
- const layout = normalizeFooter(footerConfig, ctx, doc);
1275
- if (!layout || !layout.blocks.length) return;
1276
- if (layout.visible === false) return;
1277
- const bandHeight = footerBandHeight;
1278
- if (!bandHeight) return;
1279
- const { marginTop = 4, backgroundColor, backgroundImage, blocks } = layout;
1280
- const footerMarginLeft = layout.marginLeft ?? 0;
1281
- const footerMarginRight = layout.marginRight ?? 0;
1282
- const contentWidth = doc.page.width - footerMarginLeft - footerMarginRight;
1283
- const footerEnv = {
1284
- marginLeft: footerMarginLeft,
1285
- innerWidth: contentWidth,
1286
- allowPageBreak: false
1287
- };
1288
- const pageHeight = doc.page.height;
1289
- const bandTop = pageHeight - bandHeight;
1290
- if (backgroundColor) {
1291
- doc.save();
1292
- doc.rect(0, bandTop, doc.page.width, bandHeight).fill(backgroundColor);
1293
- doc.restore();
1294
- }
1295
- if (backgroundImage) {
1296
- try {
1297
- doc.image(backgroundImage, 0, bandTop, {
1298
- width: doc.page.width,
1299
- height: bandHeight
1300
- });
1301
- } catch (e) {
1302
- console.warn("Failed to load footer background image:", e);
1303
- }
1304
- }
1305
- doc.save();
1306
- doc.rect(footerMarginLeft, bandTop, contentWidth, bandHeight).clip();
1307
- doc.translate(0, bandTop);
1308
- ctx.inFooter = true;
1309
- const localStartY = marginTop;
1310
- renderBlockArray(blocks, localStartY, footerEnv);
1311
- ctx.inFooter = false;
1312
- doc.restore();
1313
- }
1314
-
1315
- // src/renderer-engine/utils/header.ts
1316
- function drawHeader(doc, def, headerBandHeight, header, renderBlockArray) {
1317
- if (!header || !header.blocks.length) return;
1318
- if (header.visible === false) return;
1319
- if (!headerBandHeight) return;
1320
- const bandTop = 0;
1321
- const bandHeight = def.margins.top;
1322
- const headerMarginLeft = header.marginLeft ?? 0;
1323
- const headerMarginRight = header.marginRight ?? 0;
1324
- const contentWidth = doc.page.width - headerMarginLeft - headerMarginRight;
1325
- const headerEnv = {
1326
- marginLeft: headerMarginLeft,
1327
- innerWidth: contentWidth,
1328
- allowPageBreak: false
1329
- };
1330
- if (header.backgroundColor) {
1331
- doc.save();
1332
- doc.rect(0, bandTop, doc.page.width, bandHeight).fill(header.backgroundColor);
1333
- doc.restore();
1334
- }
1335
- if (header.backgroundImage) {
1336
- try {
1337
- doc.image(header.backgroundImage, 0, bandTop, {
1338
- width: doc.page.width,
1339
- height: bandHeight
1340
- });
1341
- } catch (e) {
1342
- console.warn("Failed to load header background image:", e);
1343
- }
1344
- }
1345
- doc.save();
1346
- doc.rect(headerMarginLeft, bandTop, contentWidth, bandHeight).clip();
1347
- const startY = bandTop + (header.marginTop ?? 0);
1348
- renderBlockArray(header.blocks, startY, headerEnv);
1349
- doc.restore();
1350
- }
1351
-
1352
- // src/renderer-engine/utils/image-loader.ts
1353
- import axios from "axios";
1354
- async function normalizeImageSrc(src, fallback) {
1355
- if (!src) return fallback ?? src;
1356
- if (Buffer.isBuffer(src)) return src;
1357
- if (src.startsWith("data:")) {
1358
- const commaIdx = src.indexOf(",");
1359
- if (commaIdx !== -1) {
1360
- return Buffer.from(src.slice(commaIdx + 1), "base64");
1361
- }
1362
- }
1363
- if (src.startsWith("http://") || src.startsWith("https://")) {
1364
- try {
1365
- const res = await axios.get(src, { responseType: "arraybuffer" });
1366
- return Buffer.from(res.data);
1367
- } catch (e) {
1368
- if (fallback !== void 0) {
1369
- console.warn(`Failed to fetch remote image "${src}", using default image.`);
1370
- return fallback;
1371
- }
1372
- throw toPdfEngineError(e, {
1373
- code: "PDF_ERROR_IMAGE_FETCH_FAILED" /* PDF_ERROR_IMAGE_FETCH_FAILED */,
1374
- message: "Failed to fetch remote image for PDF.",
1375
- statusCode: 422,
1376
- details: { url: src },
1377
- retryable: false
1378
- });
1379
- }
1380
- }
1381
- return src;
1382
- }
1383
- async function materializeImagesInBlocks(blocks, fallback) {
1384
- const out = [];
1385
- for (const block of blocks) {
1386
- const blockAny = block;
1387
- if (blockAny.backgroundImage) {
1388
- blockAny.backgroundImage = await normalizeImageSrc(blockAny.backgroundImage, fallback);
1389
- }
1390
- if (block.type === "image") {
1391
- const img = { ...block };
1392
- img.src = await normalizeImageSrc(img.src, fallback);
1393
- out.push(img);
1394
- } else if (block.type === "columns") {
1395
- out.push({
1396
- ...block,
1397
- columns: await Promise.all(block.columns.map((col) => materializeImagesInBlocks(col, fallback)))
1398
- });
1399
- } else if (block.type === "signature" && block.blocks) {
1400
- out.push({
1401
- ...block,
1402
- blocks: await materializeImagesInBlocks(block.blocks, fallback)
1403
- });
1404
- } else if (block.type === "table") {
1405
- const tb = block;
1406
- const newBody = await Promise.all(
1407
- tb.body.map(
1408
- async (entry) => {
1409
- const materializeCell = async (cell) => {
1410
- if (!cell.blocks?.length) return cell;
1411
- return { ...cell, blocks: await materializeImagesInBlocks(cell.blocks, fallback) };
1412
- };
1413
- if (Array.isArray(entry)) {
1414
- return await Promise.all(entry.map(materializeCell));
1415
- }
1416
- if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
1417
- return { ...entry, content: await Promise.all(entry.content.map(materializeCell)) };
1418
- }
1419
- return entry;
1420
- }
1421
- )
1422
- );
1423
- out.push({ ...tb, body: newBody });
1424
- } else {
1425
- out.push(block);
1426
- }
1427
- }
1428
- return out;
1429
- }
1430
-
1431
- // src/renderer-engine/utils/layout.ts
1432
- function computeColumnPixelWidths(widths, totalWidth) {
1433
- let fixedTotal = 0;
1434
- let starCount = 0;
1435
- widths.forEach((w) => {
1436
- if (w === "*") starCount++;
1437
- else fixedTotal += w;
1438
- });
1439
- const remaining = Math.max(totalWidth - fixedTotal, 0);
1440
- const starWidth = starCount > 0 ? remaining / starCount : 0;
1441
- return widths.map((w) => w === "*" ? starWidth : w);
1442
- }
1317
+ return {
1318
+ top: overrides?.top ?? result.top,
1319
+ right: overrides?.right ?? result.right,
1320
+ bottom: overrides?.bottom ?? result.bottom,
1321
+ left: overrides?.left ?? result.left
1322
+ };
1323
+ };
1324
+ var resolveBlockPadding = (block) => {
1325
+ return resolveSpacing(block.padding, {
1326
+ top: block.paddingTop,
1327
+ right: block.paddingRight,
1328
+ bottom: block.paddingBottom,
1329
+ left: block.paddingLeft
1330
+ });
1331
+ };
1332
+ var resolveHeaderPadding = (header) => {
1333
+ return resolveSpacing(header.padding, {
1334
+ top: header.paddingTop,
1335
+ right: header.paddingRight,
1336
+ bottom: header.paddingBottom,
1337
+ left: header.paddingLeft
1338
+ });
1339
+ };
1340
+ var resolveFooterPadding = (footer) => {
1341
+ return resolveSpacing(footer.padding, {
1342
+ top: footer.paddingTop,
1343
+ right: footer.paddingRight,
1344
+ bottom: footer.paddingBottom,
1345
+ left: footer.paddingLeft
1346
+ });
1347
+ };
1348
+ var hasPadding = (padding) => {
1349
+ return padding.top > 0 || padding.right > 0 || padding.bottom > 0 || padding.left > 0;
1350
+ };
1443
1351
 
1444
1352
  // src/renderer-engine/utils/styles.ts
1445
1353
  function mergeStyleDefs(styles, names) {
@@ -1535,191 +1443,772 @@ var drawStyledText = (doc, tb, x, y, width) => {
1535
1443
  // src/renderer-engine/utils/measure-block-height.ts
1536
1444
  function createMeasureBlockHeight(deps) {
1537
1445
  const { doc, styles, computeColumnPixelWidths: computeColumnPixelWidths2 } = deps;
1538
- const measureText = (b, env) => {
1539
- const tb = resolveTextBlock(styles, b);
1540
- const width = Math.max(env.innerWidth, 1);
1446
+ const measureText = (block, env) => {
1447
+ const tb = resolveTextBlock(styles, block);
1448
+ const mt = tb.marginTop ?? 0;
1449
+ const mb = tb.marginBottom ?? 0;
1450
+ const localLeft = tb.marginLeft ?? 0;
1451
+ const localRight = tb.marginRight ?? 0;
1452
+ const width = Math.max(env.innerWidth - localLeft - localRight, 1);
1541
1453
  doc.font(getFontNameForText(tb));
1542
1454
  doc.fontSize(tb.fontSize ?? 10);
1543
- const h = doc.heightOfString(tb.text ?? "", { width });
1544
- const gap = tb.lineGap ?? 4;
1545
- return h + gap;
1455
+ const lineGap = tb.lineGap ?? 4;
1456
+ const height = doc.heightOfString(tb.text ?? "", { width, lineGap });
1457
+ return mt + height + lineGap + mb;
1546
1458
  };
1547
- const measureImageLike = (mt, mb, h) => (mt ?? 0) + h + (mb ?? 0);
1548
- const measureImage = (b) => {
1549
- const h = b.height ?? 50;
1550
- return measureImageLike(b.marginTop, b.marginBottom, h);
1459
+ const measureImage = (block) => {
1460
+ return (block.marginTop ?? 0) + (block.height ?? 50) + (block.marginBottom ?? 0);
1551
1461
  };
1552
- const measureQr = (b) => {
1553
- const size = b.size ?? 80;
1554
- return measureImageLike(b.marginTop, b.marginBottom, size);
1462
+ const measureQr = (block) => {
1463
+ return (block.marginTop ?? 0) + (block.size ?? 80) + (block.marginBottom ?? 0);
1555
1464
  };
1556
- const measureBarcode = (b) => {
1557
- const h = b.height ?? 40;
1558
- return measureImageLike(b.marginTop, b.marginBottom, h);
1465
+ const measureBarcode = (block) => {
1466
+ return (block.marginTop ?? 0) + (block.height ?? 40) + (block.marginBottom ?? 0);
1559
1467
  };
1560
- const measureLine = (b) => {
1561
- const lw = b.lineWidth ?? 1;
1562
- const mt = b.marginTop ?? 0;
1563
- const mb = b.marginBottom ?? 0;
1564
- return mt + lw + mb;
1468
+ const measureShape = (block) => {
1469
+ return (block.marginTop ?? 0) + (block.height ?? block.lineWidth ?? 1) + (block.marginBottom ?? 0);
1565
1470
  };
1566
- const measureTable = (b, env) => {
1567
- const mt = b.marginTop ?? 0;
1568
- const mb = b.marginBottom ?? 0;
1569
- const ml = b.marginLeft ?? 0;
1570
- const mr = b.marginRight ?? 0;
1571
- const innerWidth = Math.max(env.innerWidth - ml - mr, 1);
1572
- const fakeEnv = {
1573
- marginLeft: 0,
1574
- innerWidth,
1575
- allowPageBreak: false
1576
- };
1577
- const h = measureTableHeight(doc, b, fakeEnv, styles, computeColumnPixelWidths2, measure);
1578
- return mt + h + mb;
1471
+ const measureLine = (block) => {
1472
+ return (block.marginTop ?? 0) + (block.lineWidth ?? 1) + (block.marginBottom ?? 0);
1579
1473
  };
1580
- const measureColumns = (b, env) => {
1581
- const mt = b.marginTop ?? 0;
1582
- const mb = b.marginBottom ?? 0;
1583
- const blockLeft = b.marginLeft ?? 0;
1584
- const blockRight = b.marginRight ?? 0;
1474
+ const measureColumns = (block, env) => {
1475
+ const mt = block.marginTop ?? 0;
1476
+ const mb = block.marginBottom ?? 0;
1477
+ const blockLeft = block.marginLeft ?? 0;
1478
+ const blockRight = block.marginRight ?? 0;
1585
1479
  const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
1586
- const cols = b.columns ?? [];
1480
+ const cols = block.columns ?? [];
1587
1481
  const n = cols.length;
1588
1482
  if (!n) return mt + mb;
1589
1483
  let colWidths;
1590
- const mode = b.mode ?? "fixedGap";
1591
- let gap;
1484
+ const mode = block.mode ?? "fixedGap";
1592
1485
  if (mode === "spaceBetween" && n > 1) {
1593
- if (b.widths && b.widths.length === n) {
1594
- colWidths = computeColumnPixelWidths2(b.widths, totalWidth);
1486
+ if (block.widths && block.widths.length === n) {
1487
+ colWidths = computeColumnPixelWidths2(block.widths, totalWidth);
1595
1488
  } else {
1596
1489
  colWidths = Array(n).fill(totalWidth / n);
1597
1490
  }
1598
- const totalColsWidth = colWidths.reduce((a, x) => a + x, 0);
1599
- const remaining = Math.max(totalWidth - totalColsWidth, 0);
1600
- gap = remaining / (n - 1);
1601
- void gap;
1602
1491
  } else {
1603
- gap = b.gap ?? 20;
1492
+ const gap = block.gap ?? 20;
1604
1493
  const totalGapsWidth = gap * (n - 1);
1605
1494
  const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
1606
- if (b.widths && b.widths.length === n) {
1607
- colWidths = computeColumnPixelWidths2(b.widths, widthForCols);
1495
+ if (block.widths && block.widths.length === n) {
1496
+ colWidths = computeColumnPixelWidths2(block.widths, widthForCols);
1608
1497
  } else {
1609
1498
  colWidths = Array(n).fill(widthForCols / n);
1610
1499
  }
1611
1500
  }
1612
- const heights = [];
1613
- for (let i = 0; i < n; i++) {
1614
- const colBlocks = cols[i] ?? [];
1615
- const colEnv = {
1616
- marginLeft: 0,
1617
- innerWidth: colWidths[i],
1618
- allowPageBreak: false
1619
- };
1620
- let colH = 0;
1621
- for (const child of colBlocks) {
1622
- if (!child || child.visible === false) continue;
1623
- colH += measure(child, colEnv);
1624
- }
1625
- heights.push(colH);
1501
+ const heights = [];
1502
+ for (let i = 0; i < n; i++) {
1503
+ const colBlocks = cols[i] ?? [];
1504
+ const colEnv = {
1505
+ marginLeft: 0,
1506
+ innerWidth: colWidths[i],
1507
+ allowPageBreak: false
1508
+ };
1509
+ let colHeight = 0;
1510
+ for (const child of colBlocks) {
1511
+ if (!child || child.visible === false) continue;
1512
+ colHeight += measure(child, colEnv);
1513
+ }
1514
+ heights.push(colHeight);
1515
+ }
1516
+ return mt + Math.max(...heights, 0) + mb;
1517
+ };
1518
+ const measureTable = (block, env) => {
1519
+ const mt = block.marginTop ?? 0;
1520
+ const mb = block.marginBottom ?? 0;
1521
+ const ml = block.marginLeft ?? 0;
1522
+ const mr = block.marginRight ?? 0;
1523
+ const innerWidth = Math.max(env.innerWidth - ml - mr, 1);
1524
+ const fakeEnv = {
1525
+ marginLeft: 0,
1526
+ innerWidth,
1527
+ allowPageBreak: false
1528
+ };
1529
+ const height = measureTableHeight(doc, block, fakeEnv, styles, computeColumnPixelWidths2, measure);
1530
+ return mt + height + mb;
1531
+ };
1532
+ const measureKeyValueGrid = (block, env) => {
1533
+ const mt = block.marginTop ?? 0;
1534
+ const mb = block.marginBottom ?? 0;
1535
+ const ml = block.marginLeft ?? 0;
1536
+ const mr = block.marginRight ?? 0;
1537
+ const totalWidth = Math.max(env.innerWidth - ml - mr, 1);
1538
+ const cols = block.columns ?? [];
1539
+ const colCount = cols.length;
1540
+ if (!colCount) return mt + mb;
1541
+ const rowGap = block.rowGap ?? 4;
1542
+ const keyValueGap = block.verticalKeyValueGap ?? 2;
1543
+ const separatorText = block.separator ?? "";
1544
+ const separatorWidth = separatorText ? doc.widthOfString(separatorText) : 0;
1545
+ let colWidths;
1546
+ if (block.columnWidths && block.columnWidths.length === colCount) {
1547
+ colWidths = computeColumnPixelWidths2(
1548
+ block.columnWidths.map((w) => w ?? "*"),
1549
+ totalWidth
1550
+ );
1551
+ } else {
1552
+ colWidths = Array(colCount).fill(totalWidth / colCount);
1553
+ }
1554
+ const measureTextHeight2 = (text, width) => {
1555
+ doc.fontSize(10);
1556
+ return doc.heightOfString(text ?? "", { width }) + 4;
1557
+ };
1558
+ let totalHeight = mt;
1559
+ if (block.orientation === "vertical") {
1560
+ let maxColHeight = 0;
1561
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
1562
+ const col = cols[colIndex] ?? [];
1563
+ const colWidth = colWidths[colIndex];
1564
+ let colHeight = 0;
1565
+ for (const item of col) {
1566
+ const keyHeight = measureTextHeight2(item.key ?? "", colWidth);
1567
+ const valueHeight = measureTextHeight2(item.value ?? "", colWidth);
1568
+ colHeight += keyHeight + keyValueGap + valueHeight + rowGap;
1569
+ }
1570
+ maxColHeight = Math.max(maxColHeight, colHeight);
1571
+ }
1572
+ totalHeight += maxColHeight;
1573
+ } else {
1574
+ const maxRows = Math.max(...cols.map((c) => c ? c.length : 0), 0);
1575
+ for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
1576
+ let rowHeight = 0;
1577
+ for (let colIndex = 0; colIndex < colCount; colIndex++) {
1578
+ const item = cols[colIndex]?.[rowIndex];
1579
+ if (!item) continue;
1580
+ const colWidth = colWidths[colIndex];
1581
+ const keyWidth = block.keyWidth === "*" ? Math.max(colWidth * 0.35, 20) : block.keyWidth ?? 80;
1582
+ const keyHeight = measureTextHeight2(item.key ?? "", keyWidth);
1583
+ const valueXInsideCol = keyWidth + (separatorText ? separatorWidth + 4 : 4);
1584
+ const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
1585
+ const valueHeight = measureTextHeight2(item.value ?? "", valueWidth);
1586
+ rowHeight = Math.max(rowHeight, keyHeight, valueHeight);
1587
+ }
1588
+ if (rowHeight > 0) {
1589
+ totalHeight += rowHeight + rowGap;
1590
+ }
1591
+ }
1592
+ }
1593
+ return totalHeight + mb;
1594
+ };
1595
+ const stripBoxModelProps = (block) => {
1596
+ return {
1597
+ ...block,
1598
+ marginTop: 0,
1599
+ marginBottom: 0,
1600
+ marginLeft: 0,
1601
+ marginRight: 0,
1602
+ backgroundColor: void 0,
1603
+ backgroundImage: void 0,
1604
+ backgroundOpacity: void 0,
1605
+ backgroundBlocks: void 0,
1606
+ padding: void 0,
1607
+ paddingTop: void 0,
1608
+ paddingRight: void 0,
1609
+ paddingBottom: void 0,
1610
+ paddingLeft: void 0
1611
+ };
1612
+ };
1613
+ const measureRaw = (block, env) => {
1614
+ switch (block.type) {
1615
+ case "text":
1616
+ return measureText(block, env);
1617
+ case "image":
1618
+ return measureImage(block);
1619
+ case "qr":
1620
+ return measureQr(block);
1621
+ case "barcode":
1622
+ return measureBarcode(block);
1623
+ case "line":
1624
+ return measureLine(block);
1625
+ case "shape":
1626
+ return measureShape(block);
1627
+ case "columns":
1628
+ return measureColumns(block, env);
1629
+ case "table":
1630
+ return measureTable(block, env);
1631
+ case "keyValueGrid":
1632
+ return measureKeyValueGrid(block, env);
1633
+ case "list":
1634
+ return measureListHeight(doc, styles, block, env);
1635
+ case "signature":
1636
+ return block.height ?? 0;
1637
+ case "pageBreak":
1638
+ return 0;
1639
+ default:
1640
+ return 0;
1641
+ }
1642
+ };
1643
+ const measure = (block, env) => {
1644
+ if (!block || block.visible === false) return 0;
1645
+ if (env.isBackgroundLayer) {
1646
+ return measureRaw(block, env);
1647
+ }
1648
+ const padding = resolveBlockPadding(block);
1649
+ const hasBoxBackground = !!block.backgroundColor || !!block.backgroundImage || !!block.backgroundBlocks?.length;
1650
+ const shouldUseBoxModel = hasPadding(padding) || hasBoxBackground;
1651
+ if (!shouldUseBoxModel) {
1652
+ return measureRaw(block, env);
1653
+ }
1654
+ const marginTop = block.marginTop ?? 0;
1655
+ const marginBottom = block.marginBottom ?? 0;
1656
+ const marginLeft = block.marginLeft ?? 0;
1657
+ const marginRight = block.marginRight ?? 0;
1658
+ const outerWidth = Math.max(env.innerWidth - marginLeft - marginRight, 1);
1659
+ const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
1660
+ const innerEnv = {
1661
+ ...env,
1662
+ marginLeft: 0,
1663
+ innerWidth
1664
+ };
1665
+ const innerBlock = stripBoxModelProps(block);
1666
+ const innerHeight = measureRaw(innerBlock, innerEnv);
1667
+ return marginTop + padding.top + innerHeight + padding.bottom + marginBottom;
1668
+ };
1669
+ return (block, env) => measure(block, env);
1670
+ }
1671
+
1672
+ // src/renderer-engine/utils/page-limit.ts
1673
+ function createBottomLimitForContent(doc, ctx) {
1674
+ return () => {
1675
+ const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
1676
+ return ctx.signatureTopY ?? pageBottomForContent;
1677
+ };
1678
+ }
1679
+
1680
+ // src/renderer-engine/utils/block-renderer.ts
1681
+ function createBlockRenderer(deps) {
1682
+ const { doc, ctx, styles, computeColumnPixelWidths: computeColumnPixelWidths2, finishPage: finishPage2, processSignatureBlock, defaultImage } = deps;
1683
+ const bottomLimitForContent = createBottomLimitForContent(doc, ctx);
1684
+ const ensureSpaceFor = createEnsureSpaceFor(ctx, doc, bottomLimitForContent, finishPage2);
1685
+ const measureBlockHeight = createMeasureBlockHeight({
1686
+ doc,
1687
+ styles,
1688
+ computeColumnPixelWidths: computeColumnPixelWidths2
1689
+ });
1690
+ const stripBoxModelProps = (block) => {
1691
+ return {
1692
+ ...block,
1693
+ marginTop: 0,
1694
+ marginBottom: 0,
1695
+ marginLeft: 0,
1696
+ marginRight: 0,
1697
+ backgroundColor: void 0,
1698
+ backgroundImage: void 0,
1699
+ backgroundOpacity: void 0,
1700
+ backgroundBlocks: void 0,
1701
+ padding: void 0,
1702
+ paddingTop: void 0,
1703
+ paddingRight: void 0,
1704
+ paddingBottom: void 0,
1705
+ paddingLeft: void 0
1706
+ };
1707
+ };
1708
+ const renderBlockCore = (block, y, env) => {
1709
+ if (block.visible === false) {
1710
+ return y ?? ctx.currentY;
1711
+ }
1712
+ switch (block.type) {
1713
+ case "text":
1714
+ return processTextBlock(doc, ctx, styles, block, y, env, ensureSpaceFor);
1715
+ case "image":
1716
+ return processImageBlock(doc, ctx, block, y, env, ensureSpaceFor, defaultImage);
1717
+ case "qr": {
1718
+ const qb = block;
1719
+ const imageLike = {
1720
+ type: "image",
1721
+ src: qb.src,
1722
+ width: qb.size,
1723
+ height: qb.size,
1724
+ align: qb.align,
1725
+ marginTop: qb.marginTop,
1726
+ marginBottom: qb.marginBottom,
1727
+ marginLeft: qb.marginLeft,
1728
+ marginRight: qb.marginRight
1729
+ };
1730
+ return processImageBlock(doc, ctx, imageLike, y, env, ensureSpaceFor, defaultImage);
1731
+ }
1732
+ case "barcode":
1733
+ return processBarcodeBlock(doc, ctx, block, y, env, ensureSpaceFor);
1734
+ case "line":
1735
+ return processLineBlock(doc, ctx, block, y, env, ensureSpaceFor);
1736
+ case "shape":
1737
+ return processShapeBlock(doc, ctx, block, y, env, ensureSpaceFor);
1738
+ case "columns":
1739
+ return processColumnsBlock(
1740
+ ctx,
1741
+ block,
1742
+ y,
1743
+ env,
1744
+ computeColumnPixelWidths2,
1745
+ renderBlock,
1746
+ ensureSpaceFor,
1747
+ measureBlockHeight
1748
+ );
1749
+ case "table":
1750
+ return processTableBlock(
1751
+ doc,
1752
+ ctx,
1753
+ styles,
1754
+ block,
1755
+ y,
1756
+ env,
1757
+ computeColumnPixelWidths2,
1758
+ bottomLimitForContent,
1759
+ finishPage2,
1760
+ renderBlock,
1761
+ measureBlockHeight
1762
+ );
1763
+ case "keyValueGrid":
1764
+ return processKeyValueGridBlock(
1765
+ doc,
1766
+ ctx,
1767
+ styles,
1768
+ block,
1769
+ y,
1770
+ env,
1771
+ computeColumnPixelWidths2,
1772
+ ensureSpaceFor
1773
+ );
1774
+ case "list":
1775
+ return processListBlock(
1776
+ doc,
1777
+ ctx,
1778
+ styles,
1779
+ block,
1780
+ y,
1781
+ env,
1782
+ ensureSpaceFor
1783
+ );
1784
+ case "pageBreak":
1785
+ finishPage2(true);
1786
+ return ctx.currentY;
1787
+ case "signature":
1788
+ processSignatureBlock(block);
1789
+ return y ?? ctx.currentY;
1790
+ default:
1791
+ throw new PdfEngineError({
1792
+ code: "PDF_ERROR_BLOCK_UNSUPPORTED" /* PDF_ERROR_BLOCK_UNSUPPORTED */,
1793
+ message: `Unsupported block type: ${block.type}`,
1794
+ statusCode: 422,
1795
+ details: { block }
1796
+ });
1797
+ }
1798
+ };
1799
+ const renderBlock = (block, y, env) => {
1800
+ if (block.visible === false) {
1801
+ return y ?? ctx.currentY;
1802
+ }
1803
+ if (env.isBackgroundLayer) {
1804
+ return renderBlockCore(block, y, env);
1805
+ }
1806
+ const padding = resolveBlockPadding(block);
1807
+ const hasBoxBackground = !!block.backgroundColor || !!block.backgroundImage || !!block.backgroundBlocks?.length;
1808
+ const shouldUseBoxModel = hasPadding(padding) || hasBoxBackground;
1809
+ if (!shouldUseBoxModel) {
1810
+ return renderBlockCore(block, y, env);
1811
+ }
1812
+ const marginTop = block.marginTop ?? 0;
1813
+ const marginBottom = block.marginBottom ?? 0;
1814
+ const marginLeft = block.marginLeft ?? 0;
1815
+ const marginRight = block.marginRight ?? 0;
1816
+ const outerX = env.marginLeft + marginLeft;
1817
+ const outerWidth = Math.max(env.innerWidth - marginLeft - marginRight, 1);
1818
+ const innerX = outerX + padding.left;
1819
+ const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
1820
+ const innerEnv = {
1821
+ ...env,
1822
+ marginLeft: innerX,
1823
+ innerWidth
1824
+ };
1825
+ const innerBlock = stripBoxModelProps(block);
1826
+ const innerHeight = measureBlockHeight(innerBlock, innerEnv);
1827
+ const boxHeight = padding.top + innerHeight + padding.bottom;
1828
+ const totalHeight = marginTop + boxHeight + marginBottom;
1829
+ if (y === null && env.allowPageBreak !== false && block.type !== "table") {
1830
+ ensureSpaceFor(totalHeight, env);
1831
+ }
1832
+ const finalOuterY = (y ?? ctx.currentY) + marginTop;
1833
+ if (block.backgroundColor) {
1834
+ doc.save();
1835
+ doc.fillOpacity(block.backgroundOpacity ?? 1).rect(outerX, finalOuterY, outerWidth, boxHeight).fill(block.backgroundColor);
1836
+ doc.restore();
1837
+ }
1838
+ if (block.backgroundImage) {
1839
+ try {
1840
+ doc.save();
1841
+ doc.opacity(block.backgroundOpacity ?? 1);
1842
+ doc.image(block.backgroundImage, outerX, finalOuterY, {
1843
+ width: outerWidth,
1844
+ height: boxHeight
1845
+ });
1846
+ doc.restore();
1847
+ } catch (_) {
1848
+ }
1849
+ }
1850
+ if (block.backgroundBlocks?.length) {
1851
+ doc.save();
1852
+ doc.opacity(block.backgroundOpacity ?? 1);
1853
+ renderBlockArray(block.backgroundBlocks, finalOuterY, {
1854
+ ...env,
1855
+ marginLeft: outerX,
1856
+ innerWidth: outerWidth,
1857
+ allowPageBreak: false,
1858
+ isBackgroundLayer: true
1859
+ });
1860
+ doc.restore();
1861
+ }
1862
+ const innerEndY = renderBlockCore(innerBlock, finalOuterY + padding.top, innerEnv);
1863
+ const newY = innerEndY + padding.bottom + marginBottom;
1864
+ if (y === null) {
1865
+ ctx.currentY = newY;
1866
+ }
1867
+ return newY;
1868
+ };
1869
+ const renderBlockArray = (blocks, startY, env) => {
1870
+ let localY = startY;
1871
+ for (const block of blocks) {
1872
+ localY = renderBlock(block, localY, env);
1873
+ }
1874
+ return localY;
1875
+ };
1876
+ return { renderBlock, renderBlockArray, measureBlockHeight };
1877
+ }
1878
+
1879
+ // src/renderer-engine/utils/context.ts
1880
+ function createInitialContext(doc) {
1881
+ return {
1882
+ pageNumber: 1,
1883
+ currentY: doc.page.margins.top,
1884
+ signatureBlock: null,
1885
+ signatureTopY: null,
1886
+ signatureHeight: 0,
1887
+ signaturePlaced: false,
1888
+ afterSignature: false,
1889
+ inFooter: false,
1890
+ inManualPageAdd: false
1891
+ };
1892
+ }
1893
+
1894
+ // src/renderer-engine/utils/env.ts
1895
+ var contentEnv = (doc) => ({
1896
+ marginLeft: doc.page.margins.left,
1897
+ innerWidth: doc.page.width - doc.page.margins.left - doc.page.margins.right,
1898
+ allowPageBreak: true
1899
+ });
1900
+
1901
+ // src/renderer-engine/utils/finish-page.ts
1902
+ function finishPage({
1903
+ addNewPage,
1904
+ doc,
1905
+ def,
1906
+ ctx,
1907
+ footerBandHeight,
1908
+ renderBlockArray,
1909
+ startNewPageLayout
1910
+ }) {
1911
+ const mode = def.watermark?.mode;
1912
+ if (def.watermark && watermarkUsesLast(mode)) {
1913
+ const isLast = !addNewPage;
1914
+ drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, isLast);
1915
+ }
1916
+ if (ctx.signatureBlock && ctx.signatureTopY !== null) {
1917
+ const env = contentEnv(doc);
1918
+ drawSignatureBlock(doc, ctx.signatureBlock, ctx.signatureTopY, env, renderBlockArray);
1919
+ }
1920
+ drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray);
1921
+ if (addNewPage) {
1922
+ ctx.inManualPageAdd = true;
1923
+ doc.addPage();
1924
+ ctx.inManualPageAdd = false;
1925
+ ctx.pageNumber += 1;
1926
+ startNewPageLayout();
1927
+ }
1928
+ }
1929
+
1930
+ // src/renderer-engine/utils/footer.ts
1931
+ function normalizeFooter(input, ctx, doc) {
1932
+ if (!input) return null;
1933
+ if (typeof input === "function") {
1934
+ const result = input(ctx.pageNumber, {
1935
+ width: doc.page.width,
1936
+ height: doc.page.height
1937
+ });
1938
+ if (!result) return null;
1939
+ return normalizeFooter(result, ctx, doc);
1940
+ }
1941
+ if (input.blocks && Array.isArray(input.blocks)) {
1942
+ const footer = input;
1943
+ return {
1944
+ visible: footer.visible,
1945
+ blocks: footer.blocks,
1946
+ backgroundBlocks: footer.backgroundBlocks,
1947
+ marginTop: footer.marginTop,
1948
+ marginBottom: footer.marginBottom,
1949
+ marginLeft: footer.marginLeft,
1950
+ marginRight: footer.marginRight,
1951
+ padding: footer.padding,
1952
+ paddingTop: footer.paddingTop,
1953
+ paddingRight: footer.paddingRight,
1954
+ paddingBottom: footer.paddingBottom,
1955
+ paddingLeft: footer.paddingLeft,
1956
+ backgroundColor: footer.backgroundColor,
1957
+ backgroundImage: footer.backgroundImage,
1958
+ backgroundOpacity: footer.backgroundOpacity
1959
+ };
1960
+ }
1961
+ if (input.type) {
1962
+ return { blocks: [input] };
1963
+ }
1964
+ if (Array.isArray(input)) {
1965
+ return { blocks: input };
1966
+ }
1967
+ return null;
1968
+ }
1969
+ function drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray) {
1970
+ const footerConfig = def.footer;
1971
+ if (!footerConfig) return;
1972
+ const layout = normalizeFooter(footerConfig, ctx, doc);
1973
+ if (!layout) return;
1974
+ if (layout.visible === false) return;
1975
+ const blocks = layout.blocks ?? [];
1976
+ const backgroundBlocks = layout.backgroundBlocks ?? [];
1977
+ if (!blocks.length && !backgroundBlocks.length && !layout.backgroundColor && !layout.backgroundImage) {
1978
+ return;
1979
+ }
1980
+ const bandHeight = footerBandHeight;
1981
+ if (!bandHeight) return;
1982
+ const footerMarginLeft = layout.marginLeft ?? 0;
1983
+ const footerMarginRight = layout.marginRight ?? 0;
1984
+ const outerX = footerMarginLeft;
1985
+ const outerWidth = doc.page.width - footerMarginLeft - footerMarginRight;
1986
+ const padding = resolveFooterPadding(layout);
1987
+ const innerX = outerX + padding.left;
1988
+ const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
1989
+ const outerEnv = {
1990
+ marginLeft: outerX,
1991
+ innerWidth: outerWidth,
1992
+ allowPageBreak: false
1993
+ };
1994
+ const innerEnv = {
1995
+ marginLeft: innerX,
1996
+ innerWidth,
1997
+ allowPageBreak: false
1998
+ };
1999
+ const pageHeight = doc.page.height;
2000
+ const bandTop = pageHeight - bandHeight;
2001
+ const backgroundOpacity = layout.backgroundOpacity ?? 1;
2002
+ if (layout.backgroundColor) {
2003
+ doc.save();
2004
+ doc.fillOpacity(backgroundOpacity).rect(0, bandTop, doc.page.width, bandHeight).fill(layout.backgroundColor);
2005
+ doc.restore();
2006
+ }
2007
+ if (layout.backgroundImage) {
2008
+ try {
2009
+ doc.save();
2010
+ doc.opacity(backgroundOpacity);
2011
+ doc.image(layout.backgroundImage, 0, bandTop, {
2012
+ width: doc.page.width,
2013
+ height: bandHeight
2014
+ });
2015
+ doc.restore();
2016
+ } catch (e) {
2017
+ console.warn("Failed to load footer background image:", e);
2018
+ }
2019
+ }
2020
+ doc.save();
2021
+ doc.rect(outerX, bandTop, outerWidth, bandHeight).clip();
2022
+ doc.translate(0, bandTop);
2023
+ ctx.inFooter = true;
2024
+ const localStartY = layout.marginTop ?? 4;
2025
+ if (backgroundBlocks.length) {
2026
+ doc.save();
2027
+ doc.opacity(backgroundOpacity);
2028
+ renderBlockArray(backgroundBlocks, localStartY, {
2029
+ ...outerEnv,
2030
+ allowPageBreak: false,
2031
+ isBackgroundLayer: true
2032
+ });
2033
+ doc.restore();
2034
+ }
2035
+ if (blocks.length) {
2036
+ renderBlockArray(blocks, localStartY + padding.top, innerEnv);
2037
+ }
2038
+ ctx.inFooter = false;
2039
+ doc.restore();
2040
+ }
2041
+
2042
+ // src/renderer-engine/utils/header.ts
2043
+ function drawHeader(doc, def, headerBandHeight, header, renderBlockArray) {
2044
+ if (!header) return;
2045
+ if (header.visible === false) return;
2046
+ if (!headerBandHeight) return;
2047
+ const blocks = header.blocks ?? [];
2048
+ const backgroundBlocks = header.backgroundBlocks ?? [];
2049
+ if (!blocks.length && !backgroundBlocks.length && !header.backgroundColor && !header.backgroundImage) {
2050
+ return;
2051
+ }
2052
+ const bandTop = 0;
2053
+ const bandHeight = def.margins.top;
2054
+ const headerMarginLeft = header.marginLeft ?? 0;
2055
+ const headerMarginRight = header.marginRight ?? 0;
2056
+ const outerX = headerMarginLeft;
2057
+ const outerWidth = doc.page.width - headerMarginLeft - headerMarginRight;
2058
+ const padding = resolveHeaderPadding(header);
2059
+ const innerX = outerX + padding.left;
2060
+ const innerWidth = Math.max(outerWidth - padding.left - padding.right, 1);
2061
+ const outerEnv = {
2062
+ marginLeft: outerX,
2063
+ innerWidth: outerWidth,
2064
+ allowPageBreak: false
2065
+ };
2066
+ const innerEnv = {
2067
+ marginLeft: innerX,
2068
+ innerWidth,
2069
+ allowPageBreak: false
2070
+ };
2071
+ const backgroundOpacity = header.backgroundOpacity ?? 1;
2072
+ if (header.backgroundColor) {
2073
+ doc.save();
2074
+ doc.fillOpacity(backgroundOpacity).rect(0, bandTop, doc.page.width, bandHeight).fill(header.backgroundColor);
2075
+ doc.restore();
2076
+ }
2077
+ if (header.backgroundImage) {
2078
+ try {
2079
+ doc.save();
2080
+ doc.opacity(backgroundOpacity);
2081
+ doc.image(header.backgroundImage, 0, bandTop, {
2082
+ width: doc.page.width,
2083
+ height: bandHeight
2084
+ });
2085
+ doc.restore();
2086
+ } catch (e) {
2087
+ console.warn("Failed to load header background image:", e);
2088
+ }
2089
+ }
2090
+ doc.save();
2091
+ doc.rect(outerX, bandTop, outerWidth, bandHeight).clip();
2092
+ const startY = bandTop + (header.marginTop ?? 0);
2093
+ if (backgroundBlocks.length) {
2094
+ doc.save();
2095
+ doc.opacity(backgroundOpacity);
2096
+ renderBlockArray(backgroundBlocks, startY, {
2097
+ ...outerEnv,
2098
+ allowPageBreak: false,
2099
+ isBackgroundLayer: true
2100
+ });
2101
+ doc.restore();
2102
+ }
2103
+ if (blocks.length) {
2104
+ renderBlockArray(blocks, startY + padding.top, innerEnv);
2105
+ }
2106
+ doc.restore();
2107
+ }
2108
+
2109
+ // src/renderer-engine/utils/image-loader.ts
2110
+ import axios from "axios";
2111
+ async function normalizeImageSrc(src, fallback) {
2112
+ if (!src) return fallback ?? src;
2113
+ if (Buffer.isBuffer(src)) return src;
2114
+ if (src.startsWith("data:")) {
2115
+ const commaIdx = src.indexOf(",");
2116
+ if (commaIdx !== -1) {
2117
+ return Buffer.from(src.slice(commaIdx + 1), "base64");
2118
+ }
2119
+ }
2120
+ if (src.startsWith("http://") || src.startsWith("https://")) {
2121
+ try {
2122
+ const res = await axios.get(src, {
2123
+ responseType: "arraybuffer"
2124
+ });
2125
+ return Buffer.from(res.data);
2126
+ } catch (e) {
2127
+ if (fallback !== void 0) {
2128
+ console.warn(`Failed to fetch remote image "${src}", using default image.`);
2129
+ return fallback;
2130
+ }
2131
+ throw toPdfEngineError(e, {
2132
+ code: "PDF_ERROR_IMAGE_FETCH_FAILED" /* PDF_ERROR_IMAGE_FETCH_FAILED */,
2133
+ message: "Failed to fetch remote image for PDF.",
2134
+ statusCode: 422,
2135
+ details: { url: src },
2136
+ retryable: false
2137
+ });
2138
+ }
2139
+ }
2140
+ return src;
2141
+ }
2142
+ async function materializeImagesInBlocks(blocks, fallback) {
2143
+ const out = [];
2144
+ for (const block of blocks) {
2145
+ const blockAny = { ...block };
2146
+ if (blockAny.backgroundImage) {
2147
+ blockAny.backgroundImage = await normalizeImageSrc(blockAny.backgroundImage, fallback);
1626
2148
  }
1627
- const maxH = Math.max(...heights, 0);
1628
- return mt + maxH + mb;
1629
- };
1630
- const measureKeyValueGrid = (b, env) => {
1631
- const mt = b.marginTop ?? 0;
1632
- const mb = b.marginBottom ?? 0;
1633
- const rowGap = b.rowGap ?? 4;
1634
- const orientation = b.orientation ?? "horizontal";
1635
- const cols = b.columns ?? [];
1636
- const colCount = cols.length;
1637
- if (!colCount) return mt + mb;
1638
- const blockLeft = b.marginLeft ?? 0;
1639
- const blockRight = b.marginRight ?? 0;
1640
- const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
1641
- let colWidths;
1642
- if (b.columnWidths && b.columnWidths.length === colCount) {
1643
- const safe = b.columnWidths.map((w) => w === void 0 ? "*" : w);
1644
- colWidths = computeColumnPixelWidths2(safe, totalWidth);
1645
- } else {
1646
- colWidths = Array(colCount).fill(totalWidth / colCount);
2149
+ if (blockAny.backgroundBlocks?.length) {
2150
+ blockAny.backgroundBlocks = await materializeImagesInBlocks(blockAny.backgroundBlocks, fallback);
1647
2151
  }
1648
- const separatorText = b.separator;
1649
- const sepBoxWidth = separatorText ? 10 : 0;
1650
- const baseKeyWidthRatio = 0.35;
1651
- const measureKVText = (tb, width) => {
1652
- const r = resolveTextBlock(styles, tb);
1653
- const fontName = getFontNameForText(r);
1654
- doc.font(fontName);
1655
- doc.fontSize(r.fontSize ?? 10);
1656
- return doc.heightOfString(r.text ?? "", { width });
1657
- };
1658
- let totalHeight = mt;
1659
- if (orientation === "vertical") {
1660
- const keyValueGap = b.verticalKeyValueGap ?? 2;
1661
- let maxColHeight = 0;
1662
- for (let colIndex = 0; colIndex < colCount; colIndex++) {
1663
- const col = cols[colIndex] ?? [];
1664
- const colWidth = colWidths[colIndex];
1665
- let colHeight = 0;
1666
- for (const item of col) {
1667
- const keyH = measureKVText({ type: "text", text: item.key ?? "" }, colWidth);
1668
- const valH = measureKVText({ type: "text", text: item.value ?? "" }, colWidth);
1669
- colHeight += keyH + keyValueGap + valH + rowGap;
1670
- }
1671
- if (colHeight > maxColHeight) maxColHeight = colHeight;
1672
- }
1673
- totalHeight += maxColHeight;
2152
+ if (block.type === "image") {
2153
+ const img = { ...blockAny };
2154
+ img.src = await normalizeImageSrc(img.src, fallback);
2155
+ out.push(img);
2156
+ } else if (block.type === "columns") {
2157
+ out.push({
2158
+ ...blockAny,
2159
+ columns: await Promise.all(
2160
+ block.columns.map((col) => materializeImagesInBlocks(col, fallback))
2161
+ )
2162
+ });
2163
+ } else if (block.type === "signature" && block.blocks) {
2164
+ out.push({
2165
+ ...blockAny,
2166
+ blocks: await materializeImagesInBlocks(block.blocks, fallback)
2167
+ });
2168
+ } else if (block.type === "table") {
2169
+ const tb = blockAny;
2170
+ const newBody = await Promise.all(
2171
+ tb.body.map(
2172
+ async (entry) => {
2173
+ const materializeCell = async (cell) => {
2174
+ if (!cell.blocks?.length) return cell;
2175
+ return {
2176
+ ...cell,
2177
+ blocks: await materializeImagesInBlocks(cell.blocks, fallback)
2178
+ };
2179
+ };
2180
+ if (Array.isArray(entry)) {
2181
+ return await Promise.all(entry.map(materializeCell));
2182
+ }
2183
+ if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
2184
+ return {
2185
+ ...entry,
2186
+ content: await Promise.all(entry.content.map(materializeCell))
2187
+ };
2188
+ }
2189
+ return entry;
2190
+ }
2191
+ )
2192
+ );
2193
+ out.push({ ...tb, body: newBody });
1674
2194
  } else {
1675
- const maxRows = Math.max(...cols.map((c) => c ? c.length : 0), 0);
1676
- for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
1677
- let rowHeight = 0;
1678
- for (let colIndex = 0; colIndex < colCount; colIndex++) {
1679
- const item = cols[colIndex]?.[rowIndex];
1680
- if (!item) continue;
1681
- const colWidth = colWidths[colIndex];
1682
- const keyWidthPx = b.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : b.keyWidth ?? 80;
1683
- const keyH = measureKVText({ type: "text", text: item.key ?? "" }, keyWidthPx);
1684
- const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
1685
- const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
1686
- const valH = measureKVText({ type: "text", text: item.value ?? "" }, valueWidth);
1687
- rowHeight = Math.max(rowHeight, Math.max(keyH, valH));
1688
- }
1689
- if (rowHeight > 0) totalHeight += rowHeight + rowGap;
1690
- }
1691
- }
1692
- totalHeight += mb;
1693
- return totalHeight;
1694
- };
1695
- const measure = (block, env) => {
1696
- if (!block || block.visible === false) return 0;
1697
- switch (block.type) {
1698
- case "text":
1699
- return measureText(block, env);
1700
- case "image":
1701
- return measureImage(block);
1702
- case "qr":
1703
- return measureQr(block);
1704
- case "barcode":
1705
- return measureBarcode(block);
1706
- case "line":
1707
- return measureLine(block);
1708
- case "table":
1709
- return measureTable(block, env);
1710
- case "columns":
1711
- return measureColumns(block, env);
1712
- case "keyValueGrid":
1713
- return measureKeyValueGrid(block, env);
1714
- case "signature":
1715
- return block.height ?? 0;
1716
- case "pageBreak":
1717
- return 0;
1718
- default:
1719
- return 0;
2195
+ out.push(blockAny);
1720
2196
  }
1721
- };
1722
- return (block, env) => measure(block, env);
2197
+ }
2198
+ return out;
2199
+ }
2200
+
2201
+ // src/renderer-engine/utils/layout.ts
2202
+ function computeColumnPixelWidths(widths, totalWidth) {
2203
+ let fixedTotal = 0;
2204
+ let starCount = 0;
2205
+ widths.forEach((w) => {
2206
+ if (w === "*") starCount++;
2207
+ else fixedTotal += w;
2208
+ });
2209
+ const remaining = Math.max(totalWidth - fixedTotal, 0);
2210
+ const starWidth = starCount > 0 ? remaining / starCount : 0;
2211
+ return widths.map((w) => w === "*" ? starWidth : w);
1723
2212
  }
1724
2213
 
1725
2214
  // src/renderer-engine/utils/page-background.ts
@@ -1744,12 +2233,10 @@ function drawPageBackground(doc, pageBackground) {
1744
2233
  doc.opacity(1);
1745
2234
  }
1746
2235
 
1747
- // src/renderer-engine/utils/page-limit.ts
1748
- function createBottomLimitForContent(doc, ctx) {
1749
- return () => {
1750
- const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
1751
- return ctx.signatureTopY ?? pageBottomForContent;
1752
- };
2236
+ // src/renderer-engine/utils/page-flow.ts
2237
+ function getBottomLimitForContent(doc, ctx) {
2238
+ const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
2239
+ return ctx.signatureTopY ?? pageBottomForContent;
1753
2240
  }
1754
2241
 
1755
2242
  // src/renderer-engine/utils/qr-bar-code.ts
@@ -1772,7 +2259,10 @@ function generateQrBuffer(value, size, version, errorCorrectionLevel) {
1772
2259
  code: "PDF_ERROR_QR_GENERATION_FAILED" /* PDF_ERROR_QR_GENERATION_FAILED */,
1773
2260
  message: "Failed to generate QR code.",
1774
2261
  statusCode: 500,
1775
- details: { valuePreview: String(value).slice(0, 60), size },
2262
+ details: {
2263
+ valuePreview: String(value).slice(0, 60),
2264
+ size
2265
+ },
1776
2266
  retryable: false
1777
2267
  })
1778
2268
  );
@@ -1799,109 +2289,101 @@ function mapBarcodeTypeToBcid(bcType) {
1799
2289
  function generateBarcodeBuffer(value, options = {}) {
1800
2290
  const { bcType, scale = 3, barHeight = 10, includetext = false, textalign = "center" } = options;
1801
2291
  const bcid = mapBarcodeTypeToBcid(bcType);
2292
+ if (bcType === "EAN13" && !/^\d{12,13}$/.test(value)) {
2293
+ throw toPdfEngineError(new Error("Invalid EAN13 value"), {
2294
+ code: "PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH" /* PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH */,
2295
+ message: "EAN13 barcode value must be 12 or 13 digits.",
2296
+ statusCode: 422,
2297
+ details: { value },
2298
+ retryable: false
2299
+ });
2300
+ }
1802
2301
  return new Promise((resolve, reject) => {
1803
- let textValue = value;
1804
- if (bcType === "EAN13") {
1805
- const digitsOnly = textValue.replace(/\D/g, "");
1806
- if (digitsOnly.length === 13) {
1807
- textValue = digitsOnly.slice(0, 12);
1808
- } else if (digitsOnly.length === 12) {
1809
- textValue = digitsOnly;
1810
- } else {
1811
- return reject(
1812
- new PdfEngineError({
1813
- code: "PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH" /* PDF_ERROR_BARCODE_EAN13_INVALID_LENGTH */,
1814
- message: `EAN13 barcode value must have 12 or 13 digits, got "${value}"`,
1815
- statusCode: 422,
1816
- details: { value },
1817
- retryable: false
1818
- })
1819
- );
1820
- }
1821
- }
1822
- const bwipOptions = {
1823
- bcid,
1824
- text: textValue,
1825
- scale,
1826
- height: barHeight,
1827
- includetext
1828
- };
1829
- bwipOptions.textxalign = textalign;
1830
- bwipjs.toBuffer(bwipOptions, (err, png) => {
1831
- if (err) {
1832
- return reject(
1833
- toPdfEngineError(err, {
1834
- code: "PDF_ERROR_BARCODE_GENERATION_FAILED" /* PDF_ERROR_BARCODE_GENERATION_FAILED */,
1835
- message: "Failed to generate barcode.",
1836
- statusCode: 500,
1837
- details: { bcType, valuePreview: String(value).slice(0, 60) },
1838
- retryable: false
1839
- })
1840
- );
2302
+ bwipjs.toBuffer(
2303
+ {
2304
+ bcid,
2305
+ text: value,
2306
+ scale,
2307
+ height: barHeight,
2308
+ includetext,
2309
+ textxalign: textalign
2310
+ },
2311
+ (err, png) => {
2312
+ if (err) {
2313
+ return reject(
2314
+ toPdfEngineError(err, {
2315
+ code: "PDF_ERROR_BARCODE_GENERATION_FAILED" /* PDF_ERROR_BARCODE_GENERATION_FAILED */,
2316
+ message: "Failed to generate barcode.",
2317
+ statusCode: 500,
2318
+ details: {
2319
+ valuePreview: String(value).slice(0, 60),
2320
+ bcType
2321
+ },
2322
+ retryable: false
2323
+ })
2324
+ );
2325
+ }
2326
+ resolve(png);
1841
2327
  }
1842
- resolve(png);
1843
- });
2328
+ );
1844
2329
  });
1845
2330
  }
1846
2331
  async function materializeQrAndBarcodesInBlocks(blocks) {
1847
2332
  const out = [];
1848
2333
  for (const block of blocks) {
1849
- if (block.type === "columns") {
1850
- const colBlock = block;
1851
- const newCols = [];
1852
- for (const col of colBlock.columns) {
1853
- newCols.push(await materializeQrAndBarcodesInBlocks(col));
1854
- }
1855
- out.push({
1856
- ...colBlock,
1857
- columns: newCols
1858
- });
1859
- } else if (block.type === "signature") {
1860
- const sig = block;
1861
- if (sig.blocks && sig.blocks.length) {
1862
- const newInner = await materializeQrAndBarcodesInBlocks(sig.blocks);
1863
- out.push({
1864
- ...sig,
1865
- blocks: newInner
1866
- });
1867
- } else {
1868
- out.push(block);
1869
- }
1870
- } else if (block.type === "qr") {
1871
- const qb = { ...block };
2334
+ const blockAny = { ...block };
2335
+ if (blockAny.backgroundBlocks?.length) {
2336
+ blockAny.backgroundBlocks = await materializeQrAndBarcodesInBlocks(blockAny.backgroundBlocks);
2337
+ }
2338
+ if (block.type === "qr") {
2339
+ const qb = blockAny;
1872
2340
  if (!qb.src && qb.value) {
1873
- const size = qb.size ?? 80;
1874
- const buf = await generateQrBuffer(qb.value, size, qb.qrVersion, qb.errorCorrectionLevel);
1875
- qb.src = buf;
2341
+ qb.size = qb.size ?? 80;
2342
+ qb.src = await generateQrBuffer(qb.value, qb.size, qb.qrVersion, qb.errorCorrectionLevel);
1876
2343
  }
1877
2344
  out.push(qb);
1878
2345
  } else if (block.type === "barcode") {
1879
- const bb = { ...block };
2346
+ const bb = blockAny;
1880
2347
  if (!bb.src && bb.value) {
1881
- const buf = await generateBarcodeBuffer(bb.value, {
2348
+ bb.src = await generateBarcodeBuffer(bb.value, {
1882
2349
  bcType: bb.bcType,
1883
2350
  scale: bb.scale,
1884
2351
  barHeight: bb.barHeight,
1885
2352
  includetext: bb.includetext,
1886
2353
  textalign: bb.textalign
1887
2354
  });
1888
- bb.src = buf;
1889
2355
  }
1890
2356
  out.push(bb);
2357
+ } else if (block.type === "columns") {
2358
+ out.push({
2359
+ ...blockAny,
2360
+ columns: await Promise.all(block.columns.map((col) => materializeQrAndBarcodesInBlocks(col)))
2361
+ });
2362
+ } else if (block.type === "signature" && block.blocks) {
2363
+ out.push({
2364
+ ...blockAny,
2365
+ blocks: await materializeQrAndBarcodesInBlocks(block.blocks)
2366
+ });
1891
2367
  } else if (block.type === "table") {
1892
- const tb = block;
2368
+ const tb = blockAny;
1893
2369
  const newBody = await Promise.all(
1894
2370
  tb.body.map(
1895
2371
  async (entry) => {
1896
2372
  const materializeCell = async (cell) => {
1897
2373
  if (!cell.blocks?.length) return cell;
1898
- return { ...cell, blocks: await materializeQrAndBarcodesInBlocks(cell.blocks) };
2374
+ return {
2375
+ ...cell,
2376
+ blocks: await materializeQrAndBarcodesInBlocks(cell.blocks)
2377
+ };
1899
2378
  };
1900
2379
  if (Array.isArray(entry)) {
1901
2380
  return await Promise.all(entry.map(materializeCell));
1902
2381
  }
1903
2382
  if (entry && typeof entry === "object" && "content" in entry && Array.isArray(entry.content)) {
1904
- return { ...entry, content: await Promise.all(entry.content.map(materializeCell)) };
2383
+ return {
2384
+ ...entry,
2385
+ content: await Promise.all(entry.content.map(materializeCell))
2386
+ };
1905
2387
  }
1906
2388
  return entry;
1907
2389
  }
@@ -1909,7 +2391,7 @@ async function materializeQrAndBarcodesInBlocks(blocks) {
1909
2391
  );
1910
2392
  out.push({ ...tb, body: newBody });
1911
2393
  } else {
1912
- out.push(block);
2394
+ out.push(blockAny);
1913
2395
  }
1914
2396
  }
1915
2397
  return out;
@@ -2163,102 +2645,143 @@ function drawWatermarkForPage(doc, watermark, pageNumber, isLast) {
2163
2645
  }
2164
2646
 
2165
2647
  // src/renderer-engine/index.ts
2166
- var BUILT_IN_DEFAULT_IMAGE = Buffer.from(
2167
- images.default.slice(images.default.indexOf(",") + 1),
2168
- "base64"
2169
- );
2170
- async function renderCustomPdf(def, outputPath) {
2171
- const fallbackImage = def.defaultImage ?? BUILT_IN_DEFAULT_IMAGE;
2172
- if (def.header) {
2648
+ var BUILT_IN_DEFAULT_IMAGE = Buffer.from(images.default.slice(images.default.indexOf(",") + 1), "base64");
2649
+ async function materializeFooterAssets(footer, fallbackImage) {
2650
+ if (!footer) return footer;
2651
+ if (typeof footer === "function") {
2652
+ return footer;
2653
+ }
2654
+ if (Array.isArray(footer)) {
2655
+ let blocks = await materializeQrAndBarcodesInBlocks(footer);
2656
+ blocks = await materializeImagesInBlocks(blocks, fallbackImage);
2657
+ return blocks;
2658
+ }
2659
+ if (footer.type) {
2660
+ let blocks = await materializeQrAndBarcodesInBlocks([footer]);
2661
+ blocks = await materializeImagesInBlocks(blocks, fallbackImage);
2662
+ return blocks[0];
2663
+ }
2664
+ const footerDef = footer;
2665
+ if (footerDef.backgroundImage) {
2666
+ footerDef.backgroundImage = await normalizeImageSrc(footerDef.backgroundImage, fallbackImage);
2667
+ }
2668
+ if (footerDef.blocks?.length) {
2669
+ footerDef.blocks = await materializeQrAndBarcodesInBlocks(footerDef.blocks);
2670
+ footerDef.blocks = await materializeImagesInBlocks(footerDef.blocks, fallbackImage);
2671
+ }
2672
+ if (footerDef.backgroundBlocks?.length) {
2673
+ footerDef.backgroundBlocks = await materializeQrAndBarcodesInBlocks(footerDef.backgroundBlocks);
2674
+ footerDef.backgroundBlocks = await materializeImagesInBlocks(footerDef.backgroundBlocks, fallbackImage);
2675
+ }
2676
+ return footerDef;
2677
+ }
2678
+ async function materializeDocAssets(def, fallbackImage) {
2679
+ if (def.header?.blocks?.length) {
2173
2680
  def.header.blocks = await materializeQrAndBarcodesInBlocks(def.header.blocks);
2174
2681
  def.header.blocks = await materializeImagesInBlocks(def.header.blocks, fallbackImage);
2175
2682
  }
2683
+ if (def.header?.backgroundImage) {
2684
+ def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
2685
+ }
2686
+ if (def.header?.backgroundBlocks?.length) {
2687
+ def.header.backgroundBlocks = await materializeQrAndBarcodesInBlocks(def.header.backgroundBlocks);
2688
+ def.header.backgroundBlocks = await materializeImagesInBlocks(def.header.backgroundBlocks, fallbackImage);
2689
+ }
2176
2690
  def.content = await materializeQrAndBarcodesInBlocks(def.content);
2177
2691
  def.content = await materializeImagesInBlocks(def.content, fallbackImage);
2178
2692
  if (def.pageBackground?.src) {
2179
2693
  def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src, fallbackImage);
2180
2694
  }
2181
- if (def.header?.backgroundImage) {
2182
- def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
2695
+ def.footer = await materializeFooterAssets(def.footer, fallbackImage);
2696
+ }
2697
+ function runRender(doc, def, fallbackImage) {
2698
+ const headerBandHeight = def.margins.top ?? 0;
2699
+ const footerBandHeight = def.margins.bottom ?? 0;
2700
+ if (def.fonts && def.fonts.length) {
2701
+ for (const font of def.fonts) {
2702
+ try {
2703
+ doc.registerFont(font.name, font.src);
2704
+ } catch (e) {
2705
+ console.warn("Failed to register font:", font.name, e);
2706
+ }
2707
+ }
2183
2708
  }
2184
- if (def.footer && typeof def.footer !== "function") {
2185
- const footer = def.footer;
2186
- if (footer.backgroundImage) {
2187
- footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage, fallbackImage);
2709
+ const ctx = createInitialContext(doc);
2710
+ const styles = def.styles ?? {};
2711
+ const finishPage2 = (addNewPage) => finishPage({
2712
+ addNewPage,
2713
+ doc,
2714
+ def,
2715
+ ctx,
2716
+ footerBandHeight,
2717
+ renderBlockArray,
2718
+ startNewPageLayout
2719
+ });
2720
+ const processSignatureBlock = createProcessSignatureBlock({
2721
+ doc,
2722
+ ctx,
2723
+ styles,
2724
+ finishPage: finishPage2,
2725
+ contentEnvFn: contentEnv
2726
+ });
2727
+ const { renderBlock, renderBlockArray } = createBlockRenderer({
2728
+ doc,
2729
+ ctx,
2730
+ styles,
2731
+ computeColumnPixelWidths,
2732
+ finishPage: finishPage2,
2733
+ processSignatureBlock,
2734
+ defaultImage: fallbackImage
2735
+ });
2736
+ const startNewPageLayout = createStartNewPageLayout({
2737
+ doc,
2738
+ def,
2739
+ ctx,
2740
+ headerBandHeight,
2741
+ renderBlockArray
2742
+ });
2743
+ doc.on("pageAdded", () => {
2744
+ if (ctx.inManualPageAdd) return;
2745
+ ctx.pageNumber += 1;
2746
+ drawPageBackground(doc, def.pageBackground);
2747
+ drawHeader(doc, def, headerBandHeight, def.header ?? void 0, renderBlockArray);
2748
+ const mode = def.watermark?.mode;
2749
+ if (def.watermark && !watermarkUsesLast(mode)) {
2750
+ drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, false);
2188
2751
  }
2752
+ ctx.currentY = doc.page.margins.top;
2753
+ });
2754
+ startNewPageLayout();
2755
+ for (const block of def.content) {
2756
+ if (block.type === "signature") {
2757
+ processSignatureBlock(block);
2758
+ continue;
2759
+ }
2760
+ if (ctx.afterSignature) {
2761
+ finishPage2(true);
2762
+ ctx.afterSignature = false;
2763
+ }
2764
+ renderBlock(block, null, contentEnv(doc));
2189
2765
  }
2766
+ finishPage2(false);
2767
+ doc.end();
2768
+ }
2769
+ async function renderCustomPdf(def, outputPath) {
2770
+ const fallbackImage = def.defaultImage ?? BUILT_IN_DEFAULT_IMAGE;
2771
+ await materializeDocAssets(def, fallbackImage);
2190
2772
  return new Promise((resolve, reject) => {
2191
- const headerBandHeight = def.margins.top ?? 0;
2192
- const footerBandHeight = def.margins.bottom ?? 0;
2193
2773
  const doc = new PDFDocument({
2194
2774
  size: def.pageSize || "A4",
2195
2775
  layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
2196
2776
  margins: {
2197
- top: headerBandHeight,
2198
- bottom: footerBandHeight,
2777
+ top: def.margins.top ?? 0,
2778
+ bottom: def.margins.bottom ?? 0,
2199
2779
  left: def.margins.left,
2200
2780
  right: def.margins.right
2201
2781
  }
2202
2782
  });
2203
- if (def.fonts && def.fonts.length) {
2204
- for (const f of def.fonts) {
2205
- try {
2206
- doc.registerFont(f.name, f.src);
2207
- } catch (e) {
2208
- console.warn("Failed to register font:", f.name, e);
2209
- }
2210
- }
2211
- }
2212
2783
  const stream = fs.createWriteStream(outputPath);
2213
2784
  doc.pipe(stream);
2214
- const ctx = createInitialContext(doc);
2215
- const styles = def.styles ?? {};
2216
- const finishPage2 = (addNewPage) => finishPage({
2217
- addNewPage,
2218
- doc,
2219
- def,
2220
- ctx,
2221
- footerBandHeight,
2222
- renderBlockArray,
2223
- startNewPageLayout
2224
- });
2225
- const processSignatureBlock = createProcessSignatureBlock({
2226
- doc,
2227
- ctx,
2228
- styles,
2229
- finishPage: finishPage2,
2230
- contentEnvFn: contentEnv
2231
- });
2232
- const { renderBlock, renderBlockArray } = createBlockRenderer({
2233
- doc,
2234
- ctx,
2235
- styles,
2236
- computeColumnPixelWidths,
2237
- finishPage: finishPage2,
2238
- processSignatureBlock,
2239
- defaultImage: fallbackImage
2240
- });
2241
- const startNewPageLayout = createStartNewPageLayout({
2242
- doc,
2243
- def,
2244
- ctx,
2245
- headerBandHeight,
2246
- renderBlockArray
2247
- });
2248
- startNewPageLayout();
2249
- for (const block of def.content) {
2250
- if (block.type === "signature") {
2251
- processSignatureBlock(block);
2252
- continue;
2253
- }
2254
- if (ctx.afterSignature) {
2255
- finishPage2(true);
2256
- ctx.afterSignature = false;
2257
- }
2258
- renderBlock(block, null, contentEnv(doc));
2259
- }
2260
- finishPage2(false);
2261
- doc.end();
2262
2785
  stream.on("finish", () => resolve());
2263
2786
  stream.on(
2264
2787
  "error",
@@ -2272,115 +2795,38 @@ async function renderCustomPdf(def, outputPath) {
2272
2795
  })
2273
2796
  )
2274
2797
  );
2798
+ runRender(doc, def, fallbackImage);
2275
2799
  });
2276
2800
  }
2277
2801
  async function renderCustomPdfToBuffer(def) {
2278
2802
  const fallbackImage = def.defaultImage ?? BUILT_IN_DEFAULT_IMAGE;
2279
- if (def.header) {
2280
- def.header.blocks = await materializeQrAndBarcodesInBlocks(def.header.blocks);
2281
- def.header.blocks = await materializeImagesInBlocks(def.header.blocks, fallbackImage);
2282
- }
2283
- def.content = await materializeQrAndBarcodesInBlocks(def.content);
2284
- def.content = await materializeImagesInBlocks(def.content, fallbackImage);
2285
- if (def.pageBackground?.src) {
2286
- def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src, fallbackImage);
2287
- }
2288
- if (def.header?.backgroundImage) {
2289
- def.header.backgroundImage = await normalizeImageSrc(def.header.backgroundImage, fallbackImage);
2290
- }
2291
- if (def.footer && typeof def.footer !== "function") {
2292
- const footer = def.footer;
2293
- if (footer.backgroundImage) {
2294
- footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage, fallbackImage);
2295
- }
2296
- }
2803
+ await materializeDocAssets(def, fallbackImage);
2297
2804
  return new Promise((resolve, reject) => {
2298
- const headerBandHeight = def.margins.top ?? 0;
2299
- const footerBandHeight = def.margins.bottom ?? 0;
2300
2805
  const doc = new PDFDocument({
2301
2806
  size: def.pageSize || "A4",
2302
2807
  layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
2303
2808
  margins: {
2304
- top: headerBandHeight,
2305
- bottom: footerBandHeight,
2809
+ top: def.margins.top ?? 0,
2810
+ bottom: def.margins.bottom ?? 0,
2306
2811
  left: def.margins.left,
2307
2812
  right: def.margins.right
2308
2813
  }
2309
2814
  });
2310
2815
  const chunks = [];
2311
- doc.on("data", (chunk) => {
2312
- chunks.push(chunk);
2313
- });
2314
- doc.on("end", () => {
2315
- resolve(Buffer.concat(chunks));
2316
- });
2317
- doc.on("error", (err) => {
2318
- reject(
2816
+ doc.on("data", (chunk) => chunks.push(chunk));
2817
+ doc.on("end", () => resolve(Buffer.concat(chunks)));
2818
+ doc.on(
2819
+ "error",
2820
+ (err) => reject(
2319
2821
  toPdfEngineError(err, {
2320
2822
  code: "PDF_ERROR_PDFKIT_ERROR" /* PDF_ERROR_PDFKIT_ERROR */,
2321
2823
  message: "PDFKit emitted an error while rendering.",
2322
2824
  statusCode: 500,
2323
2825
  retryable: true
2324
2826
  })
2325
- );
2326
- });
2327
- if (def.fonts && def.fonts.length) {
2328
- for (const f of def.fonts) {
2329
- try {
2330
- doc.registerFont(f.name, f.src);
2331
- } catch (e) {
2332
- console.warn("Failed to register font:", f.name, e);
2333
- }
2334
- }
2335
- }
2336
- const ctx = createInitialContext(doc);
2337
- const styles = def.styles ?? {};
2338
- const finishPage2 = (addNewPage) => finishPage({
2339
- addNewPage,
2340
- doc,
2341
- def,
2342
- ctx,
2343
- footerBandHeight,
2344
- renderBlockArray,
2345
- startNewPageLayout
2346
- });
2347
- const processSignatureBlock = createProcessSignatureBlock({
2348
- doc,
2349
- ctx,
2350
- styles,
2351
- finishPage: finishPage2,
2352
- contentEnvFn: contentEnv
2353
- });
2354
- const { renderBlock, renderBlockArray } = createBlockRenderer({
2355
- doc,
2356
- ctx,
2357
- styles,
2358
- computeColumnPixelWidths,
2359
- finishPage: finishPage2,
2360
- processSignatureBlock,
2361
- defaultImage: fallbackImage
2362
- });
2363
- const startNewPageLayout = createStartNewPageLayout({
2364
- doc,
2365
- def,
2366
- ctx,
2367
- headerBandHeight,
2368
- renderBlockArray
2369
- });
2370
- startNewPageLayout();
2371
- for (const block of def.content) {
2372
- if (block.type === "signature") {
2373
- processSignatureBlock(block);
2374
- continue;
2375
- }
2376
- if (ctx.afterSignature) {
2377
- finishPage2(true);
2378
- ctx.afterSignature = false;
2379
- }
2380
- renderBlock(block, null, contentEnv(doc));
2381
- }
2382
- finishPage2(false);
2383
- doc.end();
2827
+ )
2828
+ );
2829
+ runRender(doc, def, fallbackImage);
2384
2830
  });
2385
2831
  }
2386
2832
  export {
@@ -2388,9 +2834,42 @@ export {
2388
2834
  PdfEngineError,
2389
2835
  PdfEngineErrorCode,
2390
2836
  QR_ERROR_LEVEL,
2837
+ computeColumnPixelWidths,
2838
+ contentEnv,
2839
+ createBlockRenderer,
2840
+ createBottomLimitForContent,
2841
+ createEnsureSpaceFor,
2842
+ createInitialContext,
2843
+ createMeasureBlockHeight,
2844
+ createProcessSignatureBlock,
2845
+ createStartNewPageLayout,
2846
+ drawFooter,
2847
+ drawHeader,
2848
+ drawPageBackground,
2849
+ drawSignatureBlock,
2850
+ drawStyledText,
2851
+ drawWatermarkForPage,
2852
+ finishPage,
2853
+ generateBarcodeBuffer,
2854
+ generateQrBuffer,
2855
+ getBottomLimitForContent,
2856
+ getFontNameForText,
2857
+ hasPadding,
2391
2858
  images,
2392
2859
  isPdfEngineError,
2860
+ mapBarcodeTypeToBcid,
2861
+ materializeImagesInBlocks,
2862
+ materializeQrAndBarcodesInBlocks,
2863
+ mergeStyleDefs,
2864
+ normalizeImageSrc,
2393
2865
  renderCustomPdf,
2394
2866
  renderCustomPdfToBuffer,
2395
- toPdfEngineError
2867
+ resolveBlockPadding,
2868
+ resolveFooterPadding,
2869
+ resolveHeaderPadding,
2870
+ resolveSpacing,
2871
+ resolveTableCell,
2872
+ resolveTextBlock,
2873
+ toPdfEngineError,
2874
+ watermarkUsesLast
2396
2875
  };