av6-pdf-engine 1.0.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -61,12 +61,18 @@ interface TableCell {
61
61
  color?: string;
62
62
  fillColor?: string;
63
63
  style?: StyleRef;
64
+ colSpan?: number;
65
+ rowSpan?: number;
66
+ paddingTop?: number;
67
+ paddingRight?: number;
68
+ paddingBottom?: number;
69
+ paddingLeft?: number;
64
70
  }
65
71
  interface TableBlock extends BaseBlock {
66
72
  type: "table";
67
73
  headerRows?: number;
68
74
  widths: Width[];
69
- body: TableCell[][];
75
+ body: TableCell[][] | TableBodyIterable[] | Array<TableCell[] | TableBodyIterable>;
70
76
  layout?: {
71
77
  border?: "all" | "none";
72
78
  hLineColor?: string;
@@ -77,6 +83,18 @@ interface TableBlock extends BaseBlock {
77
83
  marginBottom?: number;
78
84
  marginLeft?: number;
79
85
  marginRight?: number;
86
+ [key: string]: unknown;
87
+ }
88
+ interface TableBodyIterable {
89
+ isIterable: boolean;
90
+ iterableKey: string;
91
+ content: Array<TableCell>;
92
+ }
93
+ interface PlacedCell {
94
+ cell: TableCell;
95
+ startCol: number;
96
+ colSpan: number;
97
+ rowSpan: number;
80
98
  }
81
99
 
82
100
  interface ColumnsBlock extends BaseBlock {
@@ -217,6 +235,7 @@ interface FontRegistration {
217
235
  interface CustomDocDefinition {
218
236
  pageSize?: string;
219
237
  margins: PageMargins;
238
+ pageOrientation?: "portrait" | "landscape";
220
239
  /** Optional font registrations (custom TTF/OTF) */
221
240
  fonts?: FontRegistration[];
222
241
  header?: HeaderDef;
@@ -339,4 +358,4 @@ declare function toPdfEngineError(cause: unknown, fallback: {
339
358
  declare function renderCustomPdf(def: CustomDocDefinition, outputPath: string): Promise<void>;
340
359
  declare function renderCustomPdfToBuffer(def: CustomDocDefinition): Promise<Buffer>;
341
360
 
342
- export { type Alignment, BARCODE_TYPES, type BarcodeBlock, type BarcodeType, type BaseBlock, type Block, type BuiltInFont, type ColumnsBlock, type CustomDocDefinition, type FontName, type FontRegistration, type FooterDef, type FooterInput, type HeaderDef, type ImageBlock, type InlineTextStyle, type KeyValueGridBlock, type KeyValueItem, type LineBlock, type Orientation, type PDFDoc, type PageBackgroundDef, type PageBreakBlock, type PageContext, type PageMargins, PdfEngineError, PdfEngineErrorCode, type QRErrorLevel, QR_ERROR_LEVEL, type QrCodeBlock, type RenderEnv, type SignatureBlock, type StyleDef, type StyleRef, type TableBlock, type TableCell, type TextBlock, type WatermarkDef, type WatermarkMode, type Width, isPdfEngineError, renderCustomPdf, renderCustomPdfToBuffer, toPdfEngineError };
361
+ export { type Alignment, BARCODE_TYPES, type BarcodeBlock, type BarcodeType, type BaseBlock, type Block, type BuiltInFont, type ColumnsBlock, type CustomDocDefinition, type FontName, type FontRegistration, type FooterDef, type FooterInput, type HeaderDef, type ImageBlock, type InlineTextStyle, type KeyValueGridBlock, type KeyValueItem, type LineBlock, type Orientation, type PDFDoc, type PageBackgroundDef, type PageBreakBlock, type PageContext, type PageMargins, PdfEngineError, PdfEngineErrorCode, type PlacedCell, type QRErrorLevel, QR_ERROR_LEVEL, type QrCodeBlock, type RenderEnv, type SignatureBlock, type StyleDef, type StyleRef, type TableBlock, type TableBodyIterable, type TableCell, type TextBlock, type WatermarkDef, type WatermarkMode, type Width, isPdfEngineError, renderCustomPdf, renderCustomPdfToBuffer, toPdfEngineError };
package/dist/index.d.ts CHANGED
@@ -61,12 +61,18 @@ interface TableCell {
61
61
  color?: string;
62
62
  fillColor?: string;
63
63
  style?: StyleRef;
64
+ colSpan?: number;
65
+ rowSpan?: number;
66
+ paddingTop?: number;
67
+ paddingRight?: number;
68
+ paddingBottom?: number;
69
+ paddingLeft?: number;
64
70
  }
65
71
  interface TableBlock extends BaseBlock {
66
72
  type: "table";
67
73
  headerRows?: number;
68
74
  widths: Width[];
69
- body: TableCell[][];
75
+ body: TableCell[][] | TableBodyIterable[] | Array<TableCell[] | TableBodyIterable>;
70
76
  layout?: {
71
77
  border?: "all" | "none";
72
78
  hLineColor?: string;
@@ -77,6 +83,18 @@ interface TableBlock extends BaseBlock {
77
83
  marginBottom?: number;
78
84
  marginLeft?: number;
79
85
  marginRight?: number;
86
+ [key: string]: unknown;
87
+ }
88
+ interface TableBodyIterable {
89
+ isIterable: boolean;
90
+ iterableKey: string;
91
+ content: Array<TableCell>;
92
+ }
93
+ interface PlacedCell {
94
+ cell: TableCell;
95
+ startCol: number;
96
+ colSpan: number;
97
+ rowSpan: number;
80
98
  }
81
99
 
82
100
  interface ColumnsBlock extends BaseBlock {
@@ -217,6 +235,7 @@ interface FontRegistration {
217
235
  interface CustomDocDefinition {
218
236
  pageSize?: string;
219
237
  margins: PageMargins;
238
+ pageOrientation?: "portrait" | "landscape";
220
239
  /** Optional font registrations (custom TTF/OTF) */
221
240
  fonts?: FontRegistration[];
222
241
  header?: HeaderDef;
@@ -339,4 +358,4 @@ declare function toPdfEngineError(cause: unknown, fallback: {
339
358
  declare function renderCustomPdf(def: CustomDocDefinition, outputPath: string): Promise<void>;
340
359
  declare function renderCustomPdfToBuffer(def: CustomDocDefinition): Promise<Buffer>;
341
360
 
342
- export { type Alignment, BARCODE_TYPES, type BarcodeBlock, type BarcodeType, type BaseBlock, type Block, type BuiltInFont, type ColumnsBlock, type CustomDocDefinition, type FontName, type FontRegistration, type FooterDef, type FooterInput, type HeaderDef, type ImageBlock, type InlineTextStyle, type KeyValueGridBlock, type KeyValueItem, type LineBlock, type Orientation, type PDFDoc, type PageBackgroundDef, type PageBreakBlock, type PageContext, type PageMargins, PdfEngineError, PdfEngineErrorCode, type QRErrorLevel, QR_ERROR_LEVEL, type QrCodeBlock, type RenderEnv, type SignatureBlock, type StyleDef, type StyleRef, type TableBlock, type TableCell, type TextBlock, type WatermarkDef, type WatermarkMode, type Width, isPdfEngineError, renderCustomPdf, renderCustomPdfToBuffer, toPdfEngineError };
361
+ export { type Alignment, BARCODE_TYPES, type BarcodeBlock, type BarcodeType, type BaseBlock, type Block, type BuiltInFont, type ColumnsBlock, type CustomDocDefinition, type FontName, type FontRegistration, type FooterDef, type FooterInput, type HeaderDef, type ImageBlock, type InlineTextStyle, type KeyValueGridBlock, type KeyValueItem, type LineBlock, type Orientation, type PDFDoc, type PageBackgroundDef, type PageBreakBlock, type PageContext, type PageMargins, PdfEngineError, PdfEngineErrorCode, type PlacedCell, type QRErrorLevel, QR_ERROR_LEVEL, type QrCodeBlock, type RenderEnv, type SignatureBlock, type StyleDef, type StyleRef, type TableBlock, type TableBodyIterable, type TableCell, type TextBlock, type WatermarkDef, type WatermarkMode, type Width, isPdfEngineError, renderCustomPdf, renderCustomPdfToBuffer, toPdfEngineError };
package/dist/index.js CHANGED
@@ -519,29 +519,200 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
519
519
  };
520
520
 
521
521
  // src/renderer-engine/blocks/table.ts
522
+ var normalizeSpan = (value) => {
523
+ if (!value || value < 1) return 1;
524
+ return Math.floor(value);
525
+ };
526
+ var sum = (arr) => arr.reduce((a, b) => a + b, 0);
527
+ var getCellFontName = (cell) => {
528
+ const isBold = !!cell.bold;
529
+ const isItalic = !!cell.italic;
530
+ if (cell.font) return cell.font;
531
+ if (isBold && isItalic) return "Helvetica-BoldOblique";
532
+ if (isBold) return "Helvetica-Bold";
533
+ if (isItalic) return "Helvetica-Oblique";
534
+ return "Helvetica";
535
+ };
536
+ var decrementActiveSpans = (activeRowSpans) => {
537
+ for (let i = 0; i < activeRowSpans.length; i++) {
538
+ if (activeRowSpans[i] > 0) {
539
+ activeRowSpans[i] -= 1;
540
+ }
541
+ }
542
+ };
543
+ var findNextFreeCol = (activeRowSpans, fromIndex) => {
544
+ let col = fromIndex;
545
+ while (col < activeRowSpans.length && activeRowSpans[col] > 0) {
546
+ col++;
547
+ }
548
+ return col;
549
+ };
550
+ var buildPlacedRow = (row, totalCols, activeRowSpans) => {
551
+ const placed = [];
552
+ let cursor = 0;
553
+ for (const rawCell of row) {
554
+ cursor = findNextFreeCol(activeRowSpans, cursor);
555
+ if (cursor >= totalCols) break;
556
+ const colSpan = Math.min(normalizeSpan(rawCell.colSpan), totalCols - cursor);
557
+ const rowSpan = normalizeSpan(rawCell.rowSpan);
558
+ placed.push({
559
+ cell: rawCell,
560
+ startCol: cursor,
561
+ colSpan,
562
+ rowSpan
563
+ });
564
+ if (rowSpan > 1) {
565
+ for (let c = cursor; c < cursor + colSpan; c++) {
566
+ activeRowSpans[c] = Math.max(activeRowSpans[c], rowSpan - 1);
567
+ }
568
+ }
569
+ cursor += colSpan;
570
+ }
571
+ return placed;
572
+ };
573
+ var getPlacedCellWidth = (colWidths, startCol, colSpan) => {
574
+ return sum(colWidths.slice(startCol, startCol + colSpan));
575
+ };
576
+ var getRowSpanGroupHeight = (rowHeights, rowIndex, rowSpan) => {
577
+ return sum(rowHeights.slice(rowIndex, rowIndex + rowSpan));
578
+ };
579
+ var isTableBodyIterableEntry = (entry) => {
580
+ if (!entry || typeof entry !== "object") return false;
581
+ const row = entry;
582
+ return typeof row.isIterable === "boolean" && typeof row.iterableKey === "string" && Array.isArray(row.content);
583
+ };
584
+ var isTableCellInput = (rawBlock) => !("type" in rawBlock);
585
+ var toTableCell = (rawBlock) => {
586
+ if ("type" in rawBlock && rawBlock.type !== "text") {
587
+ return null;
588
+ }
589
+ const tableCellInput = isTableCellInput(rawBlock) ? rawBlock : void 0;
590
+ return {
591
+ text: rawBlock.text,
592
+ italic: rawBlock.italic,
593
+ underline: rawBlock.underline,
594
+ link: rawBlock.link,
595
+ strike: rawBlock.strike,
596
+ bold: rawBlock.bold,
597
+ fontSize: rawBlock.fontSize,
598
+ font: rawBlock.font,
599
+ align: rawBlock.align,
600
+ color: rawBlock.color,
601
+ fillColor: tableCellInput?.fillColor,
602
+ style: rawBlock.style,
603
+ colSpan: tableCellInput?.colSpan,
604
+ rowSpan: tableCellInput?.rowSpan,
605
+ paddingTop: tableCellInput?.paddingTop,
606
+ paddingRight: tableCellInput?.paddingRight,
607
+ paddingBottom: tableCellInput?.paddingBottom,
608
+ paddingLeft: tableCellInput?.paddingLeft
609
+ };
610
+ };
611
+ var resolvePathValue = (obj, dottedPath) => {
612
+ if (!obj || typeof obj !== "object") return void 0;
613
+ return dottedPath.split(".").reduce((acc, key) => {
614
+ if (!acc || typeof acc !== "object") return void 0;
615
+ return acc[key];
616
+ }, obj);
617
+ };
618
+ var interpolateCellText = (text, item, rowIndex) => {
619
+ return text.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, tokenRaw) => {
620
+ const token = tokenRaw.trim();
621
+ if (!token) return "";
622
+ if (token === "index") return String(rowIndex + 1);
623
+ const normalizedPath = token.startsWith("item.") ? token.slice(5) : token;
624
+ const value = resolvePathValue(item, normalizedPath);
625
+ if (value === void 0 || value === null) return "";
626
+ return typeof value === "string" ? value : String(value);
627
+ });
628
+ };
629
+ var applyIterableData = (cell, item, rowIndex) => ({
630
+ ...cell,
631
+ text: interpolateCellText(cell.text, item, rowIndex)
632
+ });
633
+ var expandIterableRow = (table, bodyDef) => {
634
+ const templateRow = bodyDef.content.map(toTableCell).filter((cell) => cell !== null);
635
+ if (!templateRow.length) {
636
+ return [];
637
+ }
638
+ if (!bodyDef.isIterable) {
639
+ return [templateRow];
640
+ }
641
+ const iterableSource = table[bodyDef.iterableKey];
642
+ if (!Array.isArray(iterableSource) || !iterableSource.length) {
643
+ return [templateRow];
644
+ }
645
+ return iterableSource.map((item, rowIndex) => templateRow.map((cell) => applyIterableData(cell, item, rowIndex)));
646
+ };
647
+ var normalizeTableRows = (table) => {
648
+ if (!Array.isArray(table.body)) return [];
649
+ const rows = [];
650
+ for (const entry of table.body) {
651
+ if (Array.isArray(entry)) {
652
+ rows.push(entry);
653
+ continue;
654
+ }
655
+ if (isTableBodyIterableEntry(entry)) {
656
+ rows.push(...expandIterableRow(table, entry));
657
+ }
658
+ }
659
+ return rows;
660
+ };
522
661
  var measureTableHeight = (doc, table, env, styles, computeColumnPixelWidths2) => {
523
662
  const localLeft = table.marginLeft ?? 0;
524
663
  const localRight = table.marginRight ?? 0;
525
664
  const width = Math.max(env.innerWidth - localLeft - localRight, 1);
665
+ const rows = normalizeTableRows(table);
666
+ const totalCols = table.widths.length;
526
667
  const colWidths = computeColumnPixelWidths2(table.widths, width);
527
- let totalHeight = 0;
528
- totalHeight += table.marginTop ?? 0;
529
- table.body.forEach((row) => {
530
- let rowHeight = 0;
531
- row.forEach((rawCell, colIndex) => {
668
+ const rowHeights = [];
669
+ const activeRowSpans = Array(totalCols).fill(0);
670
+ const placedRows = [];
671
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
672
+ const row = rows[rowIndex];
673
+ const placedRow = buildPlacedRow(row, totalCols, activeRowSpans);
674
+ placedRows.push(placedRow);
675
+ let rowHeight = 12;
676
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan }) => {
532
677
  const cell = resolveTableCell(styles, rawCell);
533
- const colW = colWidths[colIndex] ?? 50;
534
- const fontSize = cell.fontSize ?? 10;
535
- const isBold = !!cell.bold;
536
- const isItalic = !!cell.italic;
537
- const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
538
- doc.font(fontName);
539
- doc.fontSize(fontSize);
540
- const h = doc.heightOfString(cell.text, { width: colW });
541
- rowHeight = Math.max(rowHeight, h + 4);
678
+ const paddingLeft = cell.paddingLeft ?? 2;
679
+ const paddingRight = cell.paddingRight ?? 2;
680
+ const paddingTop = cell.paddingTop ?? 2;
681
+ const paddingBottom = cell.paddingBottom ?? 2;
682
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
683
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
684
+ doc.font(getCellFontName(cell));
685
+ doc.fontSize(cell.fontSize ?? 10);
686
+ const h = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
687
+ rowHeight = Math.max(rowHeight, h);
542
688
  });
543
- totalHeight += rowHeight;
544
- });
689
+ rowHeights.push(rowHeight);
690
+ decrementActiveSpans(activeRowSpans);
691
+ }
692
+ for (let rowIndex = 0; rowIndex < placedRows.length; rowIndex++) {
693
+ const placedRow = placedRows[rowIndex];
694
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
695
+ if (rowSpan <= 1) return;
696
+ const cell = resolveTableCell(styles, rawCell);
697
+ const paddingLeft = cell.paddingLeft ?? 2;
698
+ const paddingRight = cell.paddingRight ?? 2;
699
+ const paddingTop = cell.paddingTop ?? 2;
700
+ const paddingBottom = cell.paddingBottom ?? 2;
701
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
702
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
703
+ doc.font(getCellFontName(cell));
704
+ doc.fontSize(cell.fontSize ?? 10);
705
+ const neededHeight = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
706
+ const currentGroupHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
707
+ if (neededHeight > currentGroupHeight) {
708
+ const deficit = neededHeight - currentGroupHeight;
709
+ rowHeights[rowIndex + rowSpan - 1] = (rowHeights[rowIndex + rowSpan - 1] ?? 12) + deficit;
710
+ }
711
+ });
712
+ }
713
+ let totalHeight = 0;
714
+ totalHeight += table.marginTop ?? 0;
715
+ totalHeight += sum(rowHeights);
545
716
  totalHeight += table.lineGap ?? 6;
546
717
  totalHeight += table.marginBottom ?? 0;
547
718
  return totalHeight;
@@ -555,6 +726,7 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
555
726
  const mb = table.marginBottom ?? 0;
556
727
  const baseY = y ?? ctx.currentY;
557
728
  const startY = baseY + mt;
729
+ const totalCols = table.widths.length;
558
730
  const colWidths = computeColumnPixelWidths2(table.widths, width);
559
731
  const layout = table.layout ?? {
560
732
  border: "all",
@@ -563,56 +735,85 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
563
735
  };
564
736
  const borderAll = layout.border !== "none";
565
737
  const headerRows = table.headerRows ?? 0;
566
- const measureRowHeight = (row) => {
567
- let rowHeight = 0;
568
- row.forEach((rawCell, colIndex) => {
569
- const cell = resolveTableCell(styles, rawCell);
570
- const colW = colWidths[colIndex] ?? 50;
571
- const fontSize = cell.fontSize ?? 10;
572
- const fontName = cell.font ? cell.font : cell.bold ? "Helvetica-Bold" : "Helvetica";
573
- doc.font(fontName);
574
- doc.fontSize(fontSize);
575
- const textWidth = Math.max(colW - 4, 1);
576
- const h = doc.heightOfString(cell.text, {
577
- width: textWidth
578
- }) + 4;
579
- rowHeight = Math.max(rowHeight, h);
580
- });
581
- return rowHeight || 12;
738
+ const rows = normalizeTableRows(table);
739
+ const buildPlacementAndHeights = (rows2) => {
740
+ const placedRows = [];
741
+ const rowHeights = [];
742
+ const activeRowSpans = Array(totalCols).fill(0);
743
+ for (let rowIndex = 0; rowIndex < rows2.length; rowIndex++) {
744
+ const row = rows2[rowIndex];
745
+ const placedRow = buildPlacedRow(row, totalCols, activeRowSpans);
746
+ placedRows.push(placedRow);
747
+ let rowHeight = 12;
748
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan }) => {
749
+ const cell = resolveTableCell(styles, rawCell);
750
+ const paddingLeft = cell.paddingLeft ?? 2;
751
+ const paddingRight = cell.paddingRight ?? 2;
752
+ const paddingTop = cell.paddingTop ?? 2;
753
+ const paddingBottom = cell.paddingBottom ?? 2;
754
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
755
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
756
+ doc.font(getCellFontName(cell));
757
+ doc.fontSize(cell.fontSize ?? 10);
758
+ const h = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
759
+ rowHeight = Math.max(rowHeight, h);
760
+ });
761
+ rowHeights.push(rowHeight);
762
+ decrementActiveSpans(activeRowSpans);
763
+ }
764
+ for (let rowIndex = 0; rowIndex < placedRows.length; rowIndex++) {
765
+ const placedRow = placedRows[rowIndex];
766
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
767
+ if (rowSpan <= 1) return;
768
+ const cell = resolveTableCell(styles, rawCell);
769
+ const paddingLeft = cell.paddingLeft ?? 2;
770
+ const paddingRight = cell.paddingRight ?? 2;
771
+ const paddingTop = cell.paddingTop ?? 2;
772
+ const paddingBottom = cell.paddingBottom ?? 2;
773
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
774
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
775
+ doc.font(getCellFontName(cell));
776
+ doc.fontSize(cell.fontSize ?? 10);
777
+ const neededHeight = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
778
+ const currentGroupHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
779
+ if (neededHeight > currentGroupHeight) {
780
+ const deficit = neededHeight - currentGroupHeight;
781
+ rowHeights[rowIndex + rowSpan - 1] = (rowHeights[rowIndex + rowSpan - 1] ?? 12) + deficit;
782
+ }
783
+ });
784
+ }
785
+ return { placedRows, rowHeights };
582
786
  };
583
- const drawRow = (row, rowTop2, rowHeight, isHeaderRow, drawTopBorder) => {
584
- row.forEach((rawCell, colIndex) => {
787
+ const allLayout = buildPlacementAndHeights(rows);
788
+ const drawPlacedRow = (placedRow, rowTop2, rowHeight, rowIndex, rowHeights, isHeaderRow, drawTopBorder) => {
789
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
585
790
  const cell = resolveTableCell(styles, rawCell);
586
- const colW = colWidths[colIndex] ?? 50;
587
- const x = baseLeft + colWidths.slice(0, colIndex).reduce((a, b) => a + b, 0);
791
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
792
+ const x = baseLeft + sum(colWidths.slice(0, startCol));
588
793
  const fontSize = cell.fontSize ?? 10;
794
+ const drawHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
795
+ const paddingLeft = cell.paddingLeft ?? 2;
796
+ const paddingRight = cell.paddingRight ?? 2;
797
+ const paddingTop = cell.paddingTop ?? 2;
798
+ const paddingBottom = cell.paddingBottom ?? 2;
589
799
  doc.save();
590
800
  if (cell.fillColor) {
591
- doc.rect(x, rowTop2, colW, rowHeight).fillOpacity(1).fill(cell.fillColor);
801
+ doc.rect(x, rowTop2, colW, drawHeight).fillOpacity(1).fill(cell.fillColor);
592
802
  doc.fillOpacity(1);
593
803
  }
594
- const isBold = !!cell.bold;
595
- const isItalic = !!cell.italic;
596
- const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
597
- doc.font(fontName);
804
+ doc.font(getCellFontName(cell));
598
805
  doc.fontSize(fontSize);
599
- if (cell.color) {
600
- doc.fillColor(cell.color);
601
- } else {
602
- doc.fillColor("black");
603
- }
806
+ doc.fillColor(cell.color ?? "black");
604
807
  const align = cell.align ?? "left";
605
- const textWidth = Math.max(colW - 4, 1);
606
- const textHeight = doc.heightOfString(cell.text, {
607
- width: textWidth
608
- });
609
- let textY = rowTop2 + 2;
808
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
809
+ const textHeight = doc.heightOfString(cell.text, { width: textWidth });
810
+ let textY = rowTop2 + paddingTop;
610
811
  if (isHeaderRow) {
611
- const available = rowHeight - 4;
812
+ const available = Math.max(drawHeight - paddingTop - paddingBottom, 0);
612
813
  const offset = Math.max((available - textHeight) / 2, 0);
613
- textY = rowTop2 + 2 + offset;
814
+ textY = rowTop2 + paddingTop + offset;
614
815
  }
615
- doc.text(cell.text, x + 2, textY, {
816
+ doc.text(cell.text, x + paddingLeft, textY, {
616
817
  width: textWidth,
617
818
  align,
618
819
  underline: !!cell.underline,
@@ -620,52 +821,58 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
620
821
  link: cell.link
621
822
  });
622
823
  doc.restore();
623
- });
624
- if (borderAll) {
625
- const bottomY = rowTop2 + rowHeight;
626
- doc.save();
627
- doc.lineWidth(0.5);
628
- doc.strokeColor(layout.hLineColor ?? "#ccc");
629
- if (drawTopBorder) {
630
- doc.moveTo(baseLeft, rowTop2).lineTo(baseLeft + width, rowTop2).stroke();
631
- }
632
- doc.moveTo(baseLeft, bottomY).lineTo(baseLeft + width, bottomY).stroke();
633
- doc.restore();
634
- doc.save();
635
- doc.lineWidth(0.5);
636
- doc.strokeColor(layout.vLineColor ?? "#ccc");
637
- let x = baseLeft;
638
- colWidths.forEach((colW) => {
824
+ if (borderAll) {
825
+ const bottomY = rowTop2 + drawHeight;
826
+ doc.save();
827
+ doc.lineWidth(0.5);
828
+ doc.strokeColor(layout.hLineColor ?? "#ccc");
829
+ if (drawTopBorder) {
830
+ doc.moveTo(x, rowTop2).lineTo(x + colW, rowTop2).stroke();
831
+ }
832
+ doc.moveTo(x, bottomY).lineTo(x + colW, bottomY).stroke();
833
+ doc.restore();
834
+ doc.save();
835
+ doc.lineWidth(0.5);
836
+ doc.strokeColor(layout.vLineColor ?? "#ccc");
639
837
  doc.moveTo(x, rowTop2).lineTo(x, bottomY).stroke();
640
- x += colW;
641
- });
642
- doc.moveTo(baseLeft + width, rowTop2).lineTo(baseLeft + width, bottomY).stroke();
643
- doc.restore();
644
- }
838
+ doc.moveTo(x + colW, rowTop2).lineTo(x + colW, bottomY).stroke();
839
+ doc.restore();
840
+ }
841
+ });
645
842
  return rowTop2 + rowHeight;
646
843
  };
844
+ const headerRowsSource = rows.slice(0, headerRows);
647
845
  const drawHeaderRowsOnNewPage = () => {
648
846
  if (!headerRows) return doc.page.margins.top + mt;
847
+ const headerLayout = buildPlacementAndHeights(headerRowsSource);
649
848
  let rowTop2 = doc.page.margins.top + mt;
650
- for (let i = 0; i < headerRows; i++) {
651
- const headerRow = table.body[i];
652
- const headerHeight = measureRowHeight(headerRow);
653
- rowTop2 = drawRow(headerRow, rowTop2, headerHeight, true, true);
849
+ for (let i = 0; i < headerLayout.placedRows.length; i++) {
850
+ rowTop2 = drawPlacedRow(
851
+ headerLayout.placedRows[i],
852
+ rowTop2,
853
+ headerLayout.rowHeights[i],
854
+ i,
855
+ headerLayout.rowHeights,
856
+ true,
857
+ true
858
+ );
654
859
  }
655
860
  return rowTop2;
656
861
  };
657
862
  let rowTop = startY;
658
- table.body.forEach((row, rowIndex) => {
863
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
659
864
  const isHeaderRow = rowIndex < headerRows;
660
- const rowHeight = measureRowHeight(row);
865
+ const placedRow = allLayout.placedRows[rowIndex];
866
+ const rowHeight = allLayout.rowHeights[rowIndex];
867
+ const maxRowSpan = Math.max(...placedRow.map((p) => p.rowSpan), 1);
868
+ const reservedHeight = getRowSpanGroupHeight(allLayout.rowHeights, rowIndex, maxRowSpan);
661
869
  const bottomLimit = bottomLimitForContent();
662
- if (rowTop + rowHeight > bottomLimit) {
870
+ if (rowTop + reservedHeight > bottomLimit) {
663
871
  finishPage2(true);
664
872
  rowTop = headerRows ? drawHeaderRowsOnNewPage() : doc.page.margins.top + mt;
665
873
  }
666
- const drawTopBorder = true;
667
- rowTop = drawRow(row, rowTop, rowHeight, isHeaderRow, drawTopBorder);
668
- });
874
+ rowTop = drawPlacedRow(placedRow, rowTop, rowHeight, rowIndex, allLayout.rowHeights, isHeaderRow, true);
875
+ }
669
876
  const newY = rowTop + (table.lineGap ?? 6) + mb;
670
877
  if (y === null) ctx.currentY = newY;
671
878
  return newY;
@@ -1109,7 +1316,11 @@ function resolveTableCell(styles, cell) {
1109
1316
  italic: cell.italic ?? styleDef.italic,
1110
1317
  color: cell.color ?? styleDef.color,
1111
1318
  fillColor: cell.fillColor ?? styleDef.fillColor,
1112
- font: cell.font ?? styleDef.font
1319
+ font: cell.font ?? styleDef.font,
1320
+ paddingTop: cell.paddingTop,
1321
+ paddingRight: cell.paddingRight,
1322
+ paddingBottom: cell.paddingBottom,
1323
+ paddingLeft: cell.paddingLeft
1113
1324
  };
1114
1325
  }
1115
1326
  var getFontNameForText = (tb) => {
@@ -1780,6 +1991,7 @@ async function renderCustomPdf(def, outputPath) {
1780
1991
  const footerBandHeight = def.margins.bottom ?? 0;
1781
1992
  const doc = new import_pdfkit.default({
1782
1993
  size: def.pageSize || "A4",
1994
+ layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
1783
1995
  margins: {
1784
1996
  top: headerBandHeight,
1785
1997
  bottom: footerBandHeight,
@@ -1884,6 +2096,7 @@ async function renderCustomPdfToBuffer(def) {
1884
2096
  const footerBandHeight = def.margins.bottom ?? 0;
1885
2097
  const doc = new import_pdfkit.default({
1886
2098
  size: def.pageSize || "A4",
2099
+ layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
1887
2100
  margins: {
1888
2101
  top: headerBandHeight,
1889
2102
  bottom: footerBandHeight,
package/dist/index.mjs CHANGED
@@ -476,29 +476,200 @@ var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
476
476
  };
477
477
 
478
478
  // src/renderer-engine/blocks/table.ts
479
+ var normalizeSpan = (value) => {
480
+ if (!value || value < 1) return 1;
481
+ return Math.floor(value);
482
+ };
483
+ var sum = (arr) => arr.reduce((a, b) => a + b, 0);
484
+ var getCellFontName = (cell) => {
485
+ const isBold = !!cell.bold;
486
+ const isItalic = !!cell.italic;
487
+ if (cell.font) return cell.font;
488
+ if (isBold && isItalic) return "Helvetica-BoldOblique";
489
+ if (isBold) return "Helvetica-Bold";
490
+ if (isItalic) return "Helvetica-Oblique";
491
+ return "Helvetica";
492
+ };
493
+ var decrementActiveSpans = (activeRowSpans) => {
494
+ for (let i = 0; i < activeRowSpans.length; i++) {
495
+ if (activeRowSpans[i] > 0) {
496
+ activeRowSpans[i] -= 1;
497
+ }
498
+ }
499
+ };
500
+ var findNextFreeCol = (activeRowSpans, fromIndex) => {
501
+ let col = fromIndex;
502
+ while (col < activeRowSpans.length && activeRowSpans[col] > 0) {
503
+ col++;
504
+ }
505
+ return col;
506
+ };
507
+ var buildPlacedRow = (row, totalCols, activeRowSpans) => {
508
+ const placed = [];
509
+ let cursor = 0;
510
+ for (const rawCell of row) {
511
+ cursor = findNextFreeCol(activeRowSpans, cursor);
512
+ if (cursor >= totalCols) break;
513
+ const colSpan = Math.min(normalizeSpan(rawCell.colSpan), totalCols - cursor);
514
+ const rowSpan = normalizeSpan(rawCell.rowSpan);
515
+ placed.push({
516
+ cell: rawCell,
517
+ startCol: cursor,
518
+ colSpan,
519
+ rowSpan
520
+ });
521
+ if (rowSpan > 1) {
522
+ for (let c = cursor; c < cursor + colSpan; c++) {
523
+ activeRowSpans[c] = Math.max(activeRowSpans[c], rowSpan - 1);
524
+ }
525
+ }
526
+ cursor += colSpan;
527
+ }
528
+ return placed;
529
+ };
530
+ var getPlacedCellWidth = (colWidths, startCol, colSpan) => {
531
+ return sum(colWidths.slice(startCol, startCol + colSpan));
532
+ };
533
+ var getRowSpanGroupHeight = (rowHeights, rowIndex, rowSpan) => {
534
+ return sum(rowHeights.slice(rowIndex, rowIndex + rowSpan));
535
+ };
536
+ var isTableBodyIterableEntry = (entry) => {
537
+ if (!entry || typeof entry !== "object") return false;
538
+ const row = entry;
539
+ return typeof row.isIterable === "boolean" && typeof row.iterableKey === "string" && Array.isArray(row.content);
540
+ };
541
+ var isTableCellInput = (rawBlock) => !("type" in rawBlock);
542
+ var toTableCell = (rawBlock) => {
543
+ if ("type" in rawBlock && rawBlock.type !== "text") {
544
+ return null;
545
+ }
546
+ const tableCellInput = isTableCellInput(rawBlock) ? rawBlock : void 0;
547
+ return {
548
+ text: rawBlock.text,
549
+ italic: rawBlock.italic,
550
+ underline: rawBlock.underline,
551
+ link: rawBlock.link,
552
+ strike: rawBlock.strike,
553
+ bold: rawBlock.bold,
554
+ fontSize: rawBlock.fontSize,
555
+ font: rawBlock.font,
556
+ align: rawBlock.align,
557
+ color: rawBlock.color,
558
+ fillColor: tableCellInput?.fillColor,
559
+ style: rawBlock.style,
560
+ colSpan: tableCellInput?.colSpan,
561
+ rowSpan: tableCellInput?.rowSpan,
562
+ paddingTop: tableCellInput?.paddingTop,
563
+ paddingRight: tableCellInput?.paddingRight,
564
+ paddingBottom: tableCellInput?.paddingBottom,
565
+ paddingLeft: tableCellInput?.paddingLeft
566
+ };
567
+ };
568
+ var resolvePathValue = (obj, dottedPath) => {
569
+ if (!obj || typeof obj !== "object") return void 0;
570
+ return dottedPath.split(".").reduce((acc, key) => {
571
+ if (!acc || typeof acc !== "object") return void 0;
572
+ return acc[key];
573
+ }, obj);
574
+ };
575
+ var interpolateCellText = (text, item, rowIndex) => {
576
+ return text.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, tokenRaw) => {
577
+ const token = tokenRaw.trim();
578
+ if (!token) return "";
579
+ if (token === "index") return String(rowIndex + 1);
580
+ const normalizedPath = token.startsWith("item.") ? token.slice(5) : token;
581
+ const value = resolvePathValue(item, normalizedPath);
582
+ if (value === void 0 || value === null) return "";
583
+ return typeof value === "string" ? value : String(value);
584
+ });
585
+ };
586
+ var applyIterableData = (cell, item, rowIndex) => ({
587
+ ...cell,
588
+ text: interpolateCellText(cell.text, item, rowIndex)
589
+ });
590
+ var expandIterableRow = (table, bodyDef) => {
591
+ const templateRow = bodyDef.content.map(toTableCell).filter((cell) => cell !== null);
592
+ if (!templateRow.length) {
593
+ return [];
594
+ }
595
+ if (!bodyDef.isIterable) {
596
+ return [templateRow];
597
+ }
598
+ const iterableSource = table[bodyDef.iterableKey];
599
+ if (!Array.isArray(iterableSource) || !iterableSource.length) {
600
+ return [templateRow];
601
+ }
602
+ return iterableSource.map((item, rowIndex) => templateRow.map((cell) => applyIterableData(cell, item, rowIndex)));
603
+ };
604
+ var normalizeTableRows = (table) => {
605
+ if (!Array.isArray(table.body)) return [];
606
+ const rows = [];
607
+ for (const entry of table.body) {
608
+ if (Array.isArray(entry)) {
609
+ rows.push(entry);
610
+ continue;
611
+ }
612
+ if (isTableBodyIterableEntry(entry)) {
613
+ rows.push(...expandIterableRow(table, entry));
614
+ }
615
+ }
616
+ return rows;
617
+ };
479
618
  var measureTableHeight = (doc, table, env, styles, computeColumnPixelWidths2) => {
480
619
  const localLeft = table.marginLeft ?? 0;
481
620
  const localRight = table.marginRight ?? 0;
482
621
  const width = Math.max(env.innerWidth - localLeft - localRight, 1);
622
+ const rows = normalizeTableRows(table);
623
+ const totalCols = table.widths.length;
483
624
  const colWidths = computeColumnPixelWidths2(table.widths, width);
484
- let totalHeight = 0;
485
- totalHeight += table.marginTop ?? 0;
486
- table.body.forEach((row) => {
487
- let rowHeight = 0;
488
- row.forEach((rawCell, colIndex) => {
625
+ const rowHeights = [];
626
+ const activeRowSpans = Array(totalCols).fill(0);
627
+ const placedRows = [];
628
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
629
+ const row = rows[rowIndex];
630
+ const placedRow = buildPlacedRow(row, totalCols, activeRowSpans);
631
+ placedRows.push(placedRow);
632
+ let rowHeight = 12;
633
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan }) => {
489
634
  const cell = resolveTableCell(styles, rawCell);
490
- const colW = colWidths[colIndex] ?? 50;
491
- const fontSize = cell.fontSize ?? 10;
492
- const isBold = !!cell.bold;
493
- const isItalic = !!cell.italic;
494
- const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
495
- doc.font(fontName);
496
- doc.fontSize(fontSize);
497
- const h = doc.heightOfString(cell.text, { width: colW });
498
- rowHeight = Math.max(rowHeight, h + 4);
635
+ const paddingLeft = cell.paddingLeft ?? 2;
636
+ const paddingRight = cell.paddingRight ?? 2;
637
+ const paddingTop = cell.paddingTop ?? 2;
638
+ const paddingBottom = cell.paddingBottom ?? 2;
639
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
640
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
641
+ doc.font(getCellFontName(cell));
642
+ doc.fontSize(cell.fontSize ?? 10);
643
+ const h = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
644
+ rowHeight = Math.max(rowHeight, h);
499
645
  });
500
- totalHeight += rowHeight;
501
- });
646
+ rowHeights.push(rowHeight);
647
+ decrementActiveSpans(activeRowSpans);
648
+ }
649
+ for (let rowIndex = 0; rowIndex < placedRows.length; rowIndex++) {
650
+ const placedRow = placedRows[rowIndex];
651
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
652
+ if (rowSpan <= 1) return;
653
+ const cell = resolveTableCell(styles, rawCell);
654
+ const paddingLeft = cell.paddingLeft ?? 2;
655
+ const paddingRight = cell.paddingRight ?? 2;
656
+ const paddingTop = cell.paddingTop ?? 2;
657
+ const paddingBottom = cell.paddingBottom ?? 2;
658
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
659
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
660
+ doc.font(getCellFontName(cell));
661
+ doc.fontSize(cell.fontSize ?? 10);
662
+ const neededHeight = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
663
+ const currentGroupHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
664
+ if (neededHeight > currentGroupHeight) {
665
+ const deficit = neededHeight - currentGroupHeight;
666
+ rowHeights[rowIndex + rowSpan - 1] = (rowHeights[rowIndex + rowSpan - 1] ?? 12) + deficit;
667
+ }
668
+ });
669
+ }
670
+ let totalHeight = 0;
671
+ totalHeight += table.marginTop ?? 0;
672
+ totalHeight += sum(rowHeights);
502
673
  totalHeight += table.lineGap ?? 6;
503
674
  totalHeight += table.marginBottom ?? 0;
504
675
  return totalHeight;
@@ -512,6 +683,7 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
512
683
  const mb = table.marginBottom ?? 0;
513
684
  const baseY = y ?? ctx.currentY;
514
685
  const startY = baseY + mt;
686
+ const totalCols = table.widths.length;
515
687
  const colWidths = computeColumnPixelWidths2(table.widths, width);
516
688
  const layout = table.layout ?? {
517
689
  border: "all",
@@ -520,56 +692,85 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
520
692
  };
521
693
  const borderAll = layout.border !== "none";
522
694
  const headerRows = table.headerRows ?? 0;
523
- const measureRowHeight = (row) => {
524
- let rowHeight = 0;
525
- row.forEach((rawCell, colIndex) => {
526
- const cell = resolveTableCell(styles, rawCell);
527
- const colW = colWidths[colIndex] ?? 50;
528
- const fontSize = cell.fontSize ?? 10;
529
- const fontName = cell.font ? cell.font : cell.bold ? "Helvetica-Bold" : "Helvetica";
530
- doc.font(fontName);
531
- doc.fontSize(fontSize);
532
- const textWidth = Math.max(colW - 4, 1);
533
- const h = doc.heightOfString(cell.text, {
534
- width: textWidth
535
- }) + 4;
536
- rowHeight = Math.max(rowHeight, h);
537
- });
538
- return rowHeight || 12;
695
+ const rows = normalizeTableRows(table);
696
+ const buildPlacementAndHeights = (rows2) => {
697
+ const placedRows = [];
698
+ const rowHeights = [];
699
+ const activeRowSpans = Array(totalCols).fill(0);
700
+ for (let rowIndex = 0; rowIndex < rows2.length; rowIndex++) {
701
+ const row = rows2[rowIndex];
702
+ const placedRow = buildPlacedRow(row, totalCols, activeRowSpans);
703
+ placedRows.push(placedRow);
704
+ let rowHeight = 12;
705
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan }) => {
706
+ const cell = resolveTableCell(styles, rawCell);
707
+ const paddingLeft = cell.paddingLeft ?? 2;
708
+ const paddingRight = cell.paddingRight ?? 2;
709
+ const paddingTop = cell.paddingTop ?? 2;
710
+ const paddingBottom = cell.paddingBottom ?? 2;
711
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
712
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
713
+ doc.font(getCellFontName(cell));
714
+ doc.fontSize(cell.fontSize ?? 10);
715
+ const h = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
716
+ rowHeight = Math.max(rowHeight, h);
717
+ });
718
+ rowHeights.push(rowHeight);
719
+ decrementActiveSpans(activeRowSpans);
720
+ }
721
+ for (let rowIndex = 0; rowIndex < placedRows.length; rowIndex++) {
722
+ const placedRow = placedRows[rowIndex];
723
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
724
+ if (rowSpan <= 1) return;
725
+ const cell = resolveTableCell(styles, rawCell);
726
+ const paddingLeft = cell.paddingLeft ?? 2;
727
+ const paddingRight = cell.paddingRight ?? 2;
728
+ const paddingTop = cell.paddingTop ?? 2;
729
+ const paddingBottom = cell.paddingBottom ?? 2;
730
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
731
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
732
+ doc.font(getCellFontName(cell));
733
+ doc.fontSize(cell.fontSize ?? 10);
734
+ const neededHeight = doc.heightOfString(cell.text, { width: textWidth }) + paddingTop + paddingBottom;
735
+ const currentGroupHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
736
+ if (neededHeight > currentGroupHeight) {
737
+ const deficit = neededHeight - currentGroupHeight;
738
+ rowHeights[rowIndex + rowSpan - 1] = (rowHeights[rowIndex + rowSpan - 1] ?? 12) + deficit;
739
+ }
740
+ });
741
+ }
742
+ return { placedRows, rowHeights };
539
743
  };
540
- const drawRow = (row, rowTop2, rowHeight, isHeaderRow, drawTopBorder) => {
541
- row.forEach((rawCell, colIndex) => {
744
+ const allLayout = buildPlacementAndHeights(rows);
745
+ const drawPlacedRow = (placedRow, rowTop2, rowHeight, rowIndex, rowHeights, isHeaderRow, drawTopBorder) => {
746
+ placedRow.forEach(({ cell: rawCell, startCol, colSpan, rowSpan }) => {
542
747
  const cell = resolveTableCell(styles, rawCell);
543
- const colW = colWidths[colIndex] ?? 50;
544
- const x = baseLeft + colWidths.slice(0, colIndex).reduce((a, b) => a + b, 0);
748
+ const colW = getPlacedCellWidth(colWidths, startCol, colSpan);
749
+ const x = baseLeft + sum(colWidths.slice(0, startCol));
545
750
  const fontSize = cell.fontSize ?? 10;
751
+ const drawHeight = getRowSpanGroupHeight(rowHeights, rowIndex, rowSpan);
752
+ const paddingLeft = cell.paddingLeft ?? 2;
753
+ const paddingRight = cell.paddingRight ?? 2;
754
+ const paddingTop = cell.paddingTop ?? 2;
755
+ const paddingBottom = cell.paddingBottom ?? 2;
546
756
  doc.save();
547
757
  if (cell.fillColor) {
548
- doc.rect(x, rowTop2, colW, rowHeight).fillOpacity(1).fill(cell.fillColor);
758
+ doc.rect(x, rowTop2, colW, drawHeight).fillOpacity(1).fill(cell.fillColor);
549
759
  doc.fillOpacity(1);
550
760
  }
551
- const isBold = !!cell.bold;
552
- const isItalic = !!cell.italic;
553
- const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
554
- doc.font(fontName);
761
+ doc.font(getCellFontName(cell));
555
762
  doc.fontSize(fontSize);
556
- if (cell.color) {
557
- doc.fillColor(cell.color);
558
- } else {
559
- doc.fillColor("black");
560
- }
763
+ doc.fillColor(cell.color ?? "black");
561
764
  const align = cell.align ?? "left";
562
- const textWidth = Math.max(colW - 4, 1);
563
- const textHeight = doc.heightOfString(cell.text, {
564
- width: textWidth
565
- });
566
- let textY = rowTop2 + 2;
765
+ const textWidth = Math.max(colW - paddingLeft - paddingRight, 1);
766
+ const textHeight = doc.heightOfString(cell.text, { width: textWidth });
767
+ let textY = rowTop2 + paddingTop;
567
768
  if (isHeaderRow) {
568
- const available = rowHeight - 4;
769
+ const available = Math.max(drawHeight - paddingTop - paddingBottom, 0);
569
770
  const offset = Math.max((available - textHeight) / 2, 0);
570
- textY = rowTop2 + 2 + offset;
771
+ textY = rowTop2 + paddingTop + offset;
571
772
  }
572
- doc.text(cell.text, x + 2, textY, {
773
+ doc.text(cell.text, x + paddingLeft, textY, {
573
774
  width: textWidth,
574
775
  align,
575
776
  underline: !!cell.underline,
@@ -577,52 +778,58 @@ var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidt
577
778
  link: cell.link
578
779
  });
579
780
  doc.restore();
580
- });
581
- if (borderAll) {
582
- const bottomY = rowTop2 + rowHeight;
583
- doc.save();
584
- doc.lineWidth(0.5);
585
- doc.strokeColor(layout.hLineColor ?? "#ccc");
586
- if (drawTopBorder) {
587
- doc.moveTo(baseLeft, rowTop2).lineTo(baseLeft + width, rowTop2).stroke();
588
- }
589
- doc.moveTo(baseLeft, bottomY).lineTo(baseLeft + width, bottomY).stroke();
590
- doc.restore();
591
- doc.save();
592
- doc.lineWidth(0.5);
593
- doc.strokeColor(layout.vLineColor ?? "#ccc");
594
- let x = baseLeft;
595
- colWidths.forEach((colW) => {
781
+ if (borderAll) {
782
+ const bottomY = rowTop2 + drawHeight;
783
+ doc.save();
784
+ doc.lineWidth(0.5);
785
+ doc.strokeColor(layout.hLineColor ?? "#ccc");
786
+ if (drawTopBorder) {
787
+ doc.moveTo(x, rowTop2).lineTo(x + colW, rowTop2).stroke();
788
+ }
789
+ doc.moveTo(x, bottomY).lineTo(x + colW, bottomY).stroke();
790
+ doc.restore();
791
+ doc.save();
792
+ doc.lineWidth(0.5);
793
+ doc.strokeColor(layout.vLineColor ?? "#ccc");
596
794
  doc.moveTo(x, rowTop2).lineTo(x, bottomY).stroke();
597
- x += colW;
598
- });
599
- doc.moveTo(baseLeft + width, rowTop2).lineTo(baseLeft + width, bottomY).stroke();
600
- doc.restore();
601
- }
795
+ doc.moveTo(x + colW, rowTop2).lineTo(x + colW, bottomY).stroke();
796
+ doc.restore();
797
+ }
798
+ });
602
799
  return rowTop2 + rowHeight;
603
800
  };
801
+ const headerRowsSource = rows.slice(0, headerRows);
604
802
  const drawHeaderRowsOnNewPage = () => {
605
803
  if (!headerRows) return doc.page.margins.top + mt;
804
+ const headerLayout = buildPlacementAndHeights(headerRowsSource);
606
805
  let rowTop2 = doc.page.margins.top + mt;
607
- for (let i = 0; i < headerRows; i++) {
608
- const headerRow = table.body[i];
609
- const headerHeight = measureRowHeight(headerRow);
610
- rowTop2 = drawRow(headerRow, rowTop2, headerHeight, true, true);
806
+ for (let i = 0; i < headerLayout.placedRows.length; i++) {
807
+ rowTop2 = drawPlacedRow(
808
+ headerLayout.placedRows[i],
809
+ rowTop2,
810
+ headerLayout.rowHeights[i],
811
+ i,
812
+ headerLayout.rowHeights,
813
+ true,
814
+ true
815
+ );
611
816
  }
612
817
  return rowTop2;
613
818
  };
614
819
  let rowTop = startY;
615
- table.body.forEach((row, rowIndex) => {
820
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
616
821
  const isHeaderRow = rowIndex < headerRows;
617
- const rowHeight = measureRowHeight(row);
822
+ const placedRow = allLayout.placedRows[rowIndex];
823
+ const rowHeight = allLayout.rowHeights[rowIndex];
824
+ const maxRowSpan = Math.max(...placedRow.map((p) => p.rowSpan), 1);
825
+ const reservedHeight = getRowSpanGroupHeight(allLayout.rowHeights, rowIndex, maxRowSpan);
618
826
  const bottomLimit = bottomLimitForContent();
619
- if (rowTop + rowHeight > bottomLimit) {
827
+ if (rowTop + reservedHeight > bottomLimit) {
620
828
  finishPage2(true);
621
829
  rowTop = headerRows ? drawHeaderRowsOnNewPage() : doc.page.margins.top + mt;
622
830
  }
623
- const drawTopBorder = true;
624
- rowTop = drawRow(row, rowTop, rowHeight, isHeaderRow, drawTopBorder);
625
- });
831
+ rowTop = drawPlacedRow(placedRow, rowTop, rowHeight, rowIndex, allLayout.rowHeights, isHeaderRow, true);
832
+ }
626
833
  const newY = rowTop + (table.lineGap ?? 6) + mb;
627
834
  if (y === null) ctx.currentY = newY;
628
835
  return newY;
@@ -1066,7 +1273,11 @@ function resolveTableCell(styles, cell) {
1066
1273
  italic: cell.italic ?? styleDef.italic,
1067
1274
  color: cell.color ?? styleDef.color,
1068
1275
  fillColor: cell.fillColor ?? styleDef.fillColor,
1069
- font: cell.font ?? styleDef.font
1276
+ font: cell.font ?? styleDef.font,
1277
+ paddingTop: cell.paddingTop,
1278
+ paddingRight: cell.paddingRight,
1279
+ paddingBottom: cell.paddingBottom,
1280
+ paddingLeft: cell.paddingLeft
1070
1281
  };
1071
1282
  }
1072
1283
  var getFontNameForText = (tb) => {
@@ -1737,6 +1948,7 @@ async function renderCustomPdf(def, outputPath) {
1737
1948
  const footerBandHeight = def.margins.bottom ?? 0;
1738
1949
  const doc = new PDFDocument({
1739
1950
  size: def.pageSize || "A4",
1951
+ layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
1740
1952
  margins: {
1741
1953
  top: headerBandHeight,
1742
1954
  bottom: footerBandHeight,
@@ -1841,6 +2053,7 @@ async function renderCustomPdfToBuffer(def) {
1841
2053
  const footerBandHeight = def.margins.bottom ?? 0;
1842
2054
  const doc = new PDFDocument({
1843
2055
  size: def.pageSize || "A4",
2056
+ layout: def.pageOrientation === "landscape" ? "landscape" : "portrait",
1844
2057
  margins: {
1845
2058
  top: headerBandHeight,
1846
2059
  bottom: footerBandHeight,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "av6-pdf-engine",
3
- "version": "1.0.9",
3
+ "version": "1.2.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/index.mjs",