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/README.md +686 -88
- package/dist/index.d.mts +245 -23
- package/dist/index.d.ts +245 -23
- package/dist/index.js +1325 -813
- package/dist/index.mjs +1291 -812
- package/package.json +6 -1
- package/.prettierignore +0 -4
- package/.prettierrc +0 -6
- package/tmp/border-showcase.pdf +0 -0
- package/tmp/quote-details.pdf +0 -0
package/dist/index.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
|
|
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 =
|
|
339
|
+
const keyHeight = measureTextHeight2(keyResolved, colWidth);
|
|
341
340
|
const valueBase = buildValueBlock(item);
|
|
342
341
|
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
343
|
-
const valueHeight =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1262
|
+
const lineGap = tb.lineGap ?? 4;
|
|
1263
|
+
const textHeight = doc.heightOfString(tb.text, { width, lineGap });
|
|
1024
1264
|
if (y === null) {
|
|
1025
|
-
|
|
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
|
|
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/
|
|
1206
|
-
var
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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 = (
|
|
1539
|
-
const tb = resolveTextBlock(styles,
|
|
1540
|
-
const
|
|
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
|
|
1544
|
-
const
|
|
1545
|
-
return
|
|
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
|
|
1548
|
-
|
|
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 = (
|
|
1553
|
-
|
|
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 = (
|
|
1557
|
-
|
|
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
|
|
1561
|
-
|
|
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
|
|
1567
|
-
|
|
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 = (
|
|
1581
|
-
const mt =
|
|
1582
|
-
const mb =
|
|
1583
|
-
const blockLeft =
|
|
1584
|
-
const blockRight =
|
|
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 =
|
|
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 =
|
|
1591
|
-
let gap;
|
|
1484
|
+
const mode = block.mode ?? "fixedGap";
|
|
1592
1485
|
if (mode === "spaceBetween" && n > 1) {
|
|
1593
|
-
if (
|
|
1594
|
-
colWidths = computeColumnPixelWidths2(
|
|
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 =
|
|
1492
|
+
const gap = block.gap ?? 20;
|
|
1604
1493
|
const totalGapsWidth = gap * (n - 1);
|
|
1605
1494
|
const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
|
|
1606
|
-
if (
|
|
1607
|
-
colWidths = computeColumnPixelWidths2(
|
|
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
|
|
1621
|
-
for (const child of colBlocks) {
|
|
1622
|
-
if (!child || child.visible === false) continue;
|
|
1623
|
-
|
|
1624
|
-
}
|
|
1625
|
-
heights.push(
|
|
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
|
-
|
|
1628
|
-
|
|
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
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if (
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
1748
|
-
function
|
|
1749
|
-
|
|
1750
|
-
|
|
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: {
|
|
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
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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
|
-
|
|
1843
|
-
});
|
|
2328
|
+
);
|
|
1844
2329
|
});
|
|
1845
2330
|
}
|
|
1846
2331
|
async function materializeQrAndBarcodesInBlocks(blocks) {
|
|
1847
2332
|
const out = [];
|
|
1848
2333
|
for (const block of blocks) {
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
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
|
-
|
|
1874
|
-
|
|
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 =
|
|
2346
|
+
const bb = blockAny;
|
|
1880
2347
|
if (!bb.src && bb.value) {
|
|
1881
|
-
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
2168
|
-
|
|
2169
|
-
)
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
if (
|
|
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
|
-
|
|
2182
|
-
|
|
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
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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:
|
|
2198
|
-
bottom:
|
|
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
|
-
|
|
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:
|
|
2305
|
-
bottom:
|
|
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
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2867
|
+
resolveBlockPadding,
|
|
2868
|
+
resolveFooterPadding,
|
|
2869
|
+
resolveHeaderPadding,
|
|
2870
|
+
resolveSpacing,
|
|
2871
|
+
resolveTableCell,
|
|
2872
|
+
resolveTextBlock,
|
|
2873
|
+
toPdfEngineError,
|
|
2874
|
+
watermarkUsesLast
|
|
2396
2875
|
};
|