medical-form-printer 0.2.0 → 0.3.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/node.js CHANGED
@@ -561,6 +561,7 @@ function generateSectionStyles(theme, cls2, config) {
561
561
  font-size: ${theme.fontSize.sectionTitle};
562
562
  font-weight: bold;
563
563
  margin-bottom: ${theme.spacing.xs};
564
+ text-align: center;
564
565
  }`;
565
566
  }
566
567
  function generateInfoGridStyles(_theme, cls2) {
@@ -806,6 +807,7 @@ function generateFooterStyles(theme, cls2) {
806
807
  /* Footer */
807
808
  .${cls2("print-footer")} {
808
809
  margin-top: ${theme.spacing.footerMarginTop};
810
+ min-height: ${theme.fontSize.small};
809
811
  display: flex;
810
812
  justify-content: space-between;
811
813
  font-size: ${theme.fontSize.small};
@@ -1088,6 +1090,7 @@ function createInlineStyles(theme = defaultTheme) {
1088
1090
  // Footer
1089
1091
  printFooter: {
1090
1092
  marginTop: "10mm",
1093
+ minHeight: theme.fontSize.small,
1091
1094
  display: "flex",
1092
1095
  justifyContent: "space-between",
1093
1096
  fontSize: theme.fontSize.small,
@@ -1353,6 +1356,10 @@ function each(items, renderer) {
1353
1356
  }).join("");
1354
1357
  }
1355
1358
  var div = () => h("div");
1359
+ var span = () => h("span");
1360
+ var thead = () => h("thead");
1361
+ var tr = () => h("tr");
1362
+ var th = () => h("th");
1356
1363
  var header = () => h("header");
1357
1364
  var footer = () => h("footer");
1358
1365
  var main = () => h("main");
@@ -1484,9 +1491,11 @@ function renderCheckboxTextCell(cell, data, options) {
1484
1491
  }
1485
1492
  function renderTextareaCell(cell, data, options) {
1486
1493
  const value = getCellValue(cell, data);
1494
+ const isOverflowHtml = data[`__overflow_html_${cell.field}`] === true;
1495
+ const contentHtml = isOverflowHtml ? value : escapeHtml(value);
1487
1496
  return `<div class="${cls("info-item", options)} ${cls("textarea-item", options)}">
1488
1497
  <span class="${cls("label", options)}">${escapeHtml(cell.label)}:</span>
1489
- <span class="${cls("textarea-content", options)}">${escapeHtml(value)}</span>
1498
+ <span class="${cls("textarea-content", options)}">${contentHtml}</span>
1490
1499
  </div>`;
1491
1500
  }
1492
1501
 
@@ -1551,14 +1560,231 @@ function isChecked(values, optionValue) {
1551
1560
  return String(values) === String(optionValue);
1552
1561
  }
1553
1562
 
1563
+ // src/renderer/section-renderers/table/header-strategy.ts
1564
+ var SimpleHeaderStrategy = class {
1565
+ /**
1566
+ * Check if this strategy can handle the configuration
1567
+ * Returns true when headerRows is not provided or empty
1568
+ */
1569
+ canHandle(config) {
1570
+ return !config.headerRows || config.headerRows.length === 0;
1571
+ }
1572
+ /**
1573
+ * Render single-row header from columns configuration
1574
+ */
1575
+ render(config, options) {
1576
+ const headers = config.columns.map((col) => {
1577
+ const thBuilder = th().text(col.header);
1578
+ if (col.width) {
1579
+ thBuilder.style("width", col.width);
1580
+ }
1581
+ return thBuilder.build();
1582
+ }).join("\n");
1583
+ return tr().raw(headers).build();
1584
+ }
1585
+ };
1586
+ var MultiRowHeaderStrategy = class {
1587
+ /**
1588
+ * Check if this strategy can handle the configuration
1589
+ * Returns true when headerRows is provided and not empty
1590
+ */
1591
+ canHandle(config) {
1592
+ return !!config.headerRows && config.headerRows.length > 0;
1593
+ }
1594
+ /**
1595
+ * Render multi-row header from headerRows configuration
1596
+ */
1597
+ render(config, options) {
1598
+ if (!config.headerRows || config.headerRows.length === 0) {
1599
+ return "";
1600
+ }
1601
+ const totalColumns = config.columns.length;
1602
+ const matrix = calculateCellMatrix(config.headerRows, totalColumns);
1603
+ const rows = [];
1604
+ for (let rowIndex = 0; rowIndex < config.headerRows.length; rowIndex++) {
1605
+ const headerRow = config.headerRows[rowIndex];
1606
+ const cells = [];
1607
+ let cellIndex = 0;
1608
+ for (let colIndex = 0; colIndex < totalColumns; colIndex++) {
1609
+ const position = matrix[rowIndex]?.[colIndex];
1610
+ if (position?.isOccupied) {
1611
+ continue;
1612
+ }
1613
+ const cell = headerRow.cells[cellIndex];
1614
+ if (!cell) {
1615
+ cellIndex++;
1616
+ continue;
1617
+ }
1618
+ const thBuilder = th().text(cell.text);
1619
+ const colspan = Math.min(cell.colspan || 1, totalColumns - colIndex);
1620
+ if (colspan > 1) {
1621
+ thBuilder.attr("colspan", colspan);
1622
+ }
1623
+ const rowspan = Math.min(cell.rowspan || 1, config.headerRows.length - rowIndex);
1624
+ if (rowspan > 1) {
1625
+ thBuilder.attr("rowspan", rowspan);
1626
+ }
1627
+ if (cell.width) {
1628
+ thBuilder.style("width", cell.width);
1629
+ }
1630
+ cells.push(thBuilder.build());
1631
+ cellIndex++;
1632
+ colIndex += colspan - 1;
1633
+ }
1634
+ rows.push(tr().raw(cells.join("\n")).build());
1635
+ }
1636
+ return rows.join("\n");
1637
+ }
1638
+ };
1639
+ function calculateCellMatrix(headerRows, totalColumns) {
1640
+ const rowCount = headerRows.length;
1641
+ const matrix = [];
1642
+ for (let row = 0; row < rowCount; row++) {
1643
+ matrix[row] = [];
1644
+ for (let col = 0; col < totalColumns; col++) {
1645
+ matrix[row][col] = {
1646
+ row,
1647
+ col,
1648
+ cell: null,
1649
+ isOccupied: false
1650
+ };
1651
+ }
1652
+ }
1653
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
1654
+ const headerRow = headerRows[rowIndex];
1655
+ let cellIndex = 0;
1656
+ let colIndex = 0;
1657
+ while (colIndex < totalColumns && cellIndex < headerRow.cells.length) {
1658
+ while (colIndex < totalColumns && matrix[rowIndex][colIndex].isOccupied) {
1659
+ colIndex++;
1660
+ }
1661
+ if (colIndex >= totalColumns) break;
1662
+ const cell = headerRow.cells[cellIndex];
1663
+ const colspan = Math.min(cell.colspan || 1, totalColumns - colIndex);
1664
+ const rowspan = Math.min(cell.rowspan || 1, rowCount - rowIndex);
1665
+ for (let r = 0; r < rowspan; r++) {
1666
+ for (let c = 0; c < colspan; c++) {
1667
+ const targetRow = rowIndex + r;
1668
+ const targetCol = colIndex + c;
1669
+ if (targetRow < rowCount && targetCol < totalColumns) {
1670
+ matrix[targetRow][targetCol] = {
1671
+ row: targetRow,
1672
+ col: targetCol,
1673
+ cell: r === 0 && c === 0 ? cell : null,
1674
+ isOccupied: r > 0
1675
+ // Only mark as occupied for rows below the cell
1676
+ };
1677
+ }
1678
+ }
1679
+ }
1680
+ colIndex += colspan;
1681
+ cellIndex++;
1682
+ }
1683
+ }
1684
+ return matrix;
1685
+ }
1686
+
1687
+ // src/renderer/section-renderers/table/header-renderer.ts
1688
+ var BaseHeaderRenderer = class {
1689
+ /**
1690
+ * Create a new base header renderer
1691
+ * @param strategies - Optional array of strategies (defaults to Simple and MultiRow)
1692
+ */
1693
+ constructor(strategies) {
1694
+ this.strategies = strategies || [new MultiRowHeaderStrategy(), new SimpleHeaderStrategy()];
1695
+ }
1696
+ /**
1697
+ * Render the table header using the appropriate strategy
1698
+ * @param config - Table configuration
1699
+ * @param options - Render options
1700
+ * @returns Rendered header HTML string with thead wrapper
1701
+ */
1702
+ renderHeader(config, options) {
1703
+ const strategy = this.selectStrategy(config);
1704
+ const headerContent = strategy.render(config, options);
1705
+ return thead().raw(headerContent).build();
1706
+ }
1707
+ /**
1708
+ * Select the appropriate strategy for the configuration
1709
+ * @param config - Table configuration
1710
+ * @returns The selected strategy
1711
+ */
1712
+ selectStrategy(config) {
1713
+ for (const strategy of this.strategies) {
1714
+ if (strategy.canHandle(config)) {
1715
+ return strategy;
1716
+ }
1717
+ }
1718
+ return new SimpleHeaderStrategy();
1719
+ }
1720
+ };
1721
+ var RowNumberHeaderDecorator = class {
1722
+ /**
1723
+ * Create a new row number header decorator
1724
+ * @param renderer - The header renderer to wrap
1725
+ */
1726
+ constructor(renderer) {
1727
+ this.wrapped = renderer;
1728
+ }
1729
+ /**
1730
+ * Render the table header with row number column
1731
+ * @param config - Table configuration
1732
+ * @param options - Render options
1733
+ * @returns Rendered header HTML string with row number column
1734
+ */
1735
+ renderHeader(config, options) {
1736
+ if (!config.showRowNumber) {
1737
+ return this.wrapped.renderHeader(config, options);
1738
+ }
1739
+ const rowCount = this.getHeaderRowCount(config);
1740
+ const rowNumCell = th().text("No.");
1741
+ if (rowCount > 1) {
1742
+ rowNumCell.attr("rowspan", rowCount);
1743
+ }
1744
+ const rowNumHtml = rowNumCell.build();
1745
+ const baseHtml = this.wrapped.renderHeader(config, options);
1746
+ return this.insertRowNumberCell(baseHtml, rowNumHtml);
1747
+ }
1748
+ /**
1749
+ * Get the number of header rows
1750
+ * @param config - Table configuration
1751
+ * @returns Number of header rows
1752
+ */
1753
+ getHeaderRowCount(config) {
1754
+ if (config.headerRows && config.headerRows.length > 0) {
1755
+ return config.headerRows.length;
1756
+ }
1757
+ return 1;
1758
+ }
1759
+ /**
1760
+ * Insert row number cell into the first row of the header
1761
+ * @param headerHtml - The base header HTML
1762
+ * @param rowNumHtml - The row number cell HTML
1763
+ * @returns Modified header HTML with row number cell
1764
+ */
1765
+ insertRowNumberCell(headerHtml, rowNumHtml) {
1766
+ const firstTrMatch = headerHtml.match(/<tr>/);
1767
+ if (!firstTrMatch) {
1768
+ return headerHtml;
1769
+ }
1770
+ const insertIndex = headerHtml.indexOf("<tr>") + 4;
1771
+ return headerHtml.slice(0, insertIndex) + "\n" + rowNumHtml + headerHtml.slice(insertIndex);
1772
+ }
1773
+ };
1774
+ function createHeaderRenderer(config) {
1775
+ const baseRenderer = new BaseHeaderRenderer();
1776
+ if (config.showRowNumber) {
1777
+ return new RowNumberHeaderDecorator(baseRenderer);
1778
+ }
1779
+ return baseRenderer;
1780
+ }
1781
+
1554
1782
  // src/renderer/section-renderers/table.ts
1555
- function renderTable(config, data, options) {
1556
- const headers = config.columns.map((col) => {
1557
- const width = col.width ? ` style="width: ${col.width}"` : "";
1558
- return `<th${width}>${escapeHtml(col.header)}</th>`;
1559
- }).join("\n");
1560
- const rows = data[config.dataField] || [];
1561
- const body = rows.map((row, index) => {
1783
+ function renderTable(config, data, options, partialOptions) {
1784
+ const { rowIndices, includeHeader = true } = partialOptions || {};
1785
+ const allRows = data[config.dataField] || [];
1786
+ const rowsToRender = rowIndices ? rowIndices.filter((idx) => idx >= 0 && idx < allRows.length).map((idx) => ({ row: allRows[idx], originalIndex: idx })) : allRows.map((row, idx) => ({ row, originalIndex: idx }));
1787
+ const body = rowsToRender.map(({ row, originalIndex }) => {
1562
1788
  const cells = config.columns.map((col) => {
1563
1789
  const value = row[col.field];
1564
1790
  const formattedValue = formatValue(value, col.type, {
@@ -1567,26 +1793,26 @@ function renderTable(config, data, options) {
1567
1793
  });
1568
1794
  return `<td>${escapeHtml(formattedValue)}</td>`;
1569
1795
  }).join("\n");
1570
- const rowNumber = config.showRowNumber ? `<td>${index + 1}</td>
1796
+ const rowNumber = config.showRowNumber ? `<td>${originalIndex + 1}</td>
1571
1797
  ` : "";
1572
1798
  return `<tr>
1573
1799
  ${rowNumber}${cells}
1574
1800
  </tr>`;
1575
1801
  }).join("\n");
1576
- const rowNumberHeader = config.showRowNumber ? "<th>No.</th>\n" : "";
1802
+ const headerHtml = includeHeader ? renderTableHeader(config, options) : "";
1577
1803
  return `<div class="${cls("print-section", options)} ${cls("data-table", options)}">
1578
1804
  <table>
1579
- <thead>
1580
- <tr>
1581
- ${rowNumberHeader}${headers}
1582
- </tr>
1583
- </thead>
1805
+ ${headerHtml}
1584
1806
  <tbody>
1585
1807
  ${body}
1586
1808
  </tbody>
1587
1809
  </table>
1588
1810
  </div>`;
1589
1811
  }
1812
+ function renderTableHeader(config, options) {
1813
+ const renderer = createHeaderRenderer(config);
1814
+ return renderer.renderHeader(config, options);
1815
+ }
1590
1816
 
1591
1817
  // src/renderer/section-renderers/checkbox-grid.ts
1592
1818
  function renderCheckboxGrid(config, data, options) {
@@ -1851,7 +2077,7 @@ function renderFooter(schema) {
1851
2077
  }
1852
2078
  let pageNumberHtml = "";
1853
2079
  if (footer3.showPageNumber) {
1854
- pageNumberHtml = '<span class="page-number"></span>';
2080
+ pageNumberHtml = '<span class="page-number">Page 1 of 1</span>';
1855
2081
  }
1856
2082
  return `<footer class="print-footer">
1857
2083
  ${notesHtml}
@@ -1874,7 +2100,7 @@ function renderWatermark(text, opacity) {
1874
2100
  return `<div class="watermark"${style}>${escapeHtml(text)}</div>`;
1875
2101
  }
1876
2102
  function renderToHtml(schema, data, options) {
1877
- const theme = mergeTheme(options?.theme);
2103
+ const theme = schema.baseUnit ? createThemeWithBaseUnit(schema.baseUnit) : mergeTheme(options?.theme);
1878
2104
  const css = generateCss(theme);
1879
2105
  const pageClasses = [
1880
2106
  "print-page",
@@ -1926,7 +2152,7 @@ function renderFooter2(schema) {
1926
2152
  const { footer: footer3 } = schema;
1927
2153
  if (!footer3) return "";
1928
2154
  const notes = footer3.notes ? `<span class="${ns}-footer-notes">${escapeHtml(footer3.notes)}</span>` : "";
1929
- const pageNumber = footer3.showPageNumber ? `<span class="${ns}-page-number"></span>` : "";
2155
+ const pageNumber = footer3.showPageNumber ? `<span class="${ns}-page-number">Page 1 of 1</span>` : "";
1930
2156
  return `<footer class="${ns}-print-footer">
1931
2157
  ${notes}
1932
2158
  ${pageNumber}
@@ -1961,8 +2187,13 @@ function getPageClasses(schema) {
1961
2187
  ].filter(Boolean).join(" ");
1962
2188
  }
1963
2189
  function createRenderContext(schema, data, options) {
2190
+ let themeOverride = options?.theme;
2191
+ if (schema.baseUnit) {
2192
+ const scaledTheme = createThemeWithBaseUnit(schema.baseUnit);
2193
+ themeOverride = { ...scaledTheme, ...options?.theme };
2194
+ }
1964
2195
  return {
1965
- css: generateIsolatedCss(options?.theme),
2196
+ css: generateIsolatedCss(themeOverride),
1966
2197
  pageClasses: getPageClasses(schema),
1967
2198
  header: renderHeader2(schema),
1968
2199
  sections: renderSections2(schema, data, options),
@@ -2597,9 +2828,9 @@ var HtmlElementBuilder = class _HtmlElementBuilder {
2597
2828
  var div2 = () => new HtmlElementBuilder("div");
2598
2829
  var span2 = () => new HtmlElementBuilder("span");
2599
2830
  var table2 = () => new HtmlElementBuilder("table");
2600
- var thead2 = () => new HtmlElementBuilder("thead");
2831
+ var thead3 = () => new HtmlElementBuilder("thead");
2601
2832
  var tbody2 = () => new HtmlElementBuilder("tbody");
2602
- var tr2 = () => new HtmlElementBuilder("tr");
2833
+ var tr3 = () => new HtmlElementBuilder("tr");
2603
2834
  var th2 = () => new HtmlElementBuilder("th");
2604
2835
  var td2 = () => new HtmlElementBuilder("td");
2605
2836
  var p2 = () => new HtmlElementBuilder("p");
@@ -3622,6 +3853,8 @@ function createFormDataTraverser() {
3622
3853
  var PAGINATION_DEFAULTS = {
3623
3854
  /** Maximum characters for overflow field first page */
3624
3855
  OVERFLOW_FIRST_LINE_CHARS: 60,
3856
+ /** Minimum table row height estimate (mm) */
3857
+ MIN_ROW_HEIGHT: 8,
3625
3858
  /** Default DPI (dots per inch), standard screen DPI is 96 */
3626
3859
  DPI: 96,
3627
3860
  /** 1 inch = 25.4 millimeters */
@@ -3637,8 +3870,35 @@ var PAGINATION_DEFAULTS = {
3637
3870
  var DEFAULT_DPI = PAGINATION_DEFAULTS.DPI;
3638
3871
  var MM_PER_INCH = PAGINATION_DEFAULTS.MM_PER_INCH;
3639
3872
  var MEASURABLE_ITEM_TYPES = {
3873
+ /** Page header with hospital, department, title */
3874
+ HEADER: "header",
3875
+ /** Content section (info-grid, checkbox-grid, etc.) */
3876
+ SECTION: "section",
3877
+ /** Table header row */
3640
3878
  TABLE_HEADER: "table-header",
3641
- TABLE_ROW: "table-row"};
3879
+ /** Table body row */
3880
+ TABLE_ROW: "table-row",
3881
+ /** Signature area (separate from footer, has own pagination rules) */
3882
+ SIGNATURE: "signature",
3883
+ /** Page footer with page number and notes */
3884
+ FOOTER: "footer"
3885
+ };
3886
+ var DEFAULT_OVERFLOW_TEXT = {
3887
+ /** Continuation marker on first page */
3888
+ seeNextMarker: "\uFF08\u7EED\u89C1\u9644\u9875\uFF09",
3889
+ /** Label suffix on continuation page */
3890
+ continuationSuffix: "\uFF08\u7EED\uFF09",
3891
+ /** Page title suffix for continuation pages */
3892
+ pageTitleSuffix: "\uFF08\u7EED\uFF09"
3893
+ };
3894
+ var ENGLISH_OVERFLOW_TEXT = {
3895
+ /** Continuation marker on first page */
3896
+ seeNextMarker: "(continued on next page)",
3897
+ /** Label suffix on continuation page */
3898
+ continuationSuffix: "(continued)",
3899
+ /** Page title suffix for continuation pages */
3900
+ pageTitleSuffix: "(continued)"
3901
+ };
3642
3902
 
3643
3903
  // src/pagination/page-dimensions.ts
3644
3904
  var PAGE_16K = {
@@ -3710,7 +3970,7 @@ function createPageDimensions(width, height, margins = {}) {
3710
3970
  };
3711
3971
  }
3712
3972
 
3713
- // src/pagination/page-break-calculator.ts
3973
+ // src/pagination/strategies/smart/page-break-calculator.ts
3714
3974
  function findTableHeader(items, tableId) {
3715
3975
  return items.find(
3716
3976
  (item) => item.type === MEASURABLE_ITEM_TYPES.TABLE_HEADER && item.tableId === tableId
@@ -3741,7 +4001,8 @@ function calculatePageBreaks(items, options) {
3741
4001
  pageHeight,
3742
4002
  headerHeight = 0,
3743
4003
  footerHeight = 0,
3744
- repeatTableHeaders = true
4004
+ repeatTableHeaders = true,
4005
+ lastPageExtraHeight = 0
3745
4006
  } = options;
3746
4007
  if (items.length === 0) {
3747
4008
  return {
@@ -3762,6 +4023,10 @@ function calculatePageBreaks(items, options) {
3762
4023
  currentPageTableHeaders.add(item.tableId);
3763
4024
  }
3764
4025
  let heightAfterAdd = currentHeight + item.height;
4026
+ const isLastItem = i === items.length - 1;
4027
+ if (isLastItem && lastPageExtraHeight > 0) {
4028
+ heightAfterAdd += lastPageExtraHeight;
4029
+ }
3765
4030
  let needsHeaderRepeat = false;
3766
4031
  let tableHeader;
3767
4032
  if (isTableRow(item) && repeatTableHeaders) {
@@ -3787,6 +4052,9 @@ function calculatePageBreaks(items, options) {
3787
4052
  currentPageTableHeaders.add(item.tableId);
3788
4053
  }
3789
4054
  }
4055
+ if (isLastItem && lastPageExtraHeight > 0) {
4056
+ currentHeight + item.height + lastPageExtraHeight;
4057
+ }
3790
4058
  } else if (needsHeaderRepeat && tableHeader) {
3791
4059
  currentPage.repeatedHeaders.push(tableHeader.id);
3792
4060
  currentHeight += tableHeader.height;
@@ -3850,7 +4118,7 @@ function getPageContentHeight(page, items) {
3850
4118
  return height;
3851
4119
  }
3852
4120
 
3853
- // src/pagination/overflow-handler.ts
4121
+ // src/pagination/strategies/overflow/overflow-handler.ts
3854
4122
  function toSafeString(value) {
3855
4123
  if (value == null) return "";
3856
4124
  return String(value);
@@ -3920,6 +4188,757 @@ function hasAnyOverflowContent(data, configs) {
3920
4188
  });
3921
4189
  }
3922
4190
 
4191
+ // src/pagination/strategies/overflow/overflow-pagination.ts
4192
+ var OVERFLOW_CSS_CLASSES = {
4193
+ /** First page truncated content container */
4194
+ OVERFLOW_FIRST_LINE: "overflow-first-line",
4195
+ /** Continuation page content container */
4196
+ OVERFLOW_CONTINUATION: "overflow-continuation",
4197
+ /** "see next page" marker (red color) */
4198
+ SEE_NEXT: "see-next",
4199
+ /** Field label on continuation page */
4200
+ OVERFLOW_LABEL: "overflow-label",
4201
+ /** Overflow content (preserves whitespace) */
4202
+ OVERFLOW_CONTENT: "overflow-content"
4203
+ };
4204
+ function isOverflowSection(section2, overflowFields) {
4205
+ if (section2.type !== "info-grid") {
4206
+ return false;
4207
+ }
4208
+ if (overflowFields.length === 0) {
4209
+ return false;
4210
+ }
4211
+ const config = section2.config;
4212
+ for (const row of config.rows) {
4213
+ for (const cell of row.cells) {
4214
+ if (overflowFields.includes(cell.field)) {
4215
+ return true;
4216
+ }
4217
+ }
4218
+ }
4219
+ return false;
4220
+ }
4221
+ function findOverflowFieldLabel(section2, fieldName) {
4222
+ if (section2.type !== "info-grid") {
4223
+ return fieldName;
4224
+ }
4225
+ const config = section2.config;
4226
+ for (const row of config.rows) {
4227
+ for (const cell of row.cells) {
4228
+ if (cell.field === fieldName) {
4229
+ return cell.label || fieldName;
4230
+ }
4231
+ }
4232
+ }
4233
+ return fieldName;
4234
+ }
4235
+ function findOverflowFieldCell(section2, fieldName) {
4236
+ if (section2.type !== "info-grid") {
4237
+ return void 0;
4238
+ }
4239
+ const config = section2.config;
4240
+ for (const row of config.rows) {
4241
+ for (const cell of row.cells) {
4242
+ if (cell.field === fieldName) {
4243
+ return cell;
4244
+ }
4245
+ }
4246
+ }
4247
+ return void 0;
4248
+ }
4249
+ function getOverflowFieldsFromConfig(paginationConfig) {
4250
+ if (!paginationConfig) {
4251
+ return [];
4252
+ }
4253
+ const fields = paginationConfig.overflow?.fields ?? paginationConfig.overflowFields;
4254
+ if (!fields || fields.length === 0) {
4255
+ return [];
4256
+ }
4257
+ const maxChars = paginationConfig.overflow?.firstLineChars ?? paginationConfig.overflowFirstLineChars ?? PAGINATION_DEFAULTS.OVERFLOW_FIRST_LINE_CHARS;
4258
+ return fields.map((fieldName) => ({
4259
+ fieldName,
4260
+ maxFirstLineChars: maxChars
4261
+ }));
4262
+ }
4263
+ function getOverflowFieldNames(paginationConfig) {
4264
+ if (!paginationConfig) {
4265
+ return [];
4266
+ }
4267
+ return paginationConfig.overflow?.fields ?? paginationConfig.overflowFields ?? [];
4268
+ }
4269
+ function renderOverflowFirstLine(value, maxChars, textConfig, cls2) {
4270
+ const firstLine = getOverflowFirstLine(value, maxChars);
4271
+ const hasOverflow = hasOverflowContent(value, maxChars);
4272
+ if (!hasOverflow) {
4273
+ return span().class(cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_FIRST_LINE)).text(firstLine).build();
4274
+ }
4275
+ const marker = span().class(cls2(OVERFLOW_CSS_CLASSES.SEE_NEXT)).text(textConfig.seeNextMarker).build();
4276
+ const escapedFirstLine = escapeHtml(firstLine + " ");
4277
+ return span().class(cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_FIRST_LINE)).raw(escapedFirstLine + marker).build();
4278
+ }
4279
+ function renderOverflowContinuation(result, fieldLabel, textConfig, cls2) {
4280
+ if (!result.hasOverflow || !result.rest) {
4281
+ return "";
4282
+ }
4283
+ const labelText = `${fieldLabel}${textConfig.continuationSuffix}\uFF1A`;
4284
+ const labelHtml = div().class(cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_LABEL)).text(labelText).build();
4285
+ const contentHtml = div().class(cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_CONTENT)).text(result.rest).build();
4286
+ return div().class(cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_CONTINUATION)).raw(labelHtml + "\n" + contentHtml).build();
4287
+ }
4288
+ function mergeOverflowTextConfig(config) {
4289
+ return {
4290
+ ...DEFAULT_OVERFLOW_TEXT,
4291
+ ...config
4292
+ };
4293
+ }
4294
+ var PAGE_CSS_CLASSES = {
4295
+ PRINT_PAGE: "print-page",
4296
+ CONTINUATION_PAGE: "continuation-page",
4297
+ PRINT_HEADER: "print-header",
4298
+ PRINT_FOOTER: "print-footer",
4299
+ PRINT_CONTENT: "print-content",
4300
+ HOSPITAL_NAME: "hospital-name",
4301
+ DEPARTMENT_NAME: "department-name",
4302
+ FORM_TITLE: "form-title",
4303
+ PAGE_NUMBER: "page-number"
4304
+ };
4305
+ function renderOverflowPageHeader(ctx, cls2) {
4306
+ const parts = [];
4307
+ if (ctx.hospital) {
4308
+ parts.push(
4309
+ div().class(cls2(PAGE_CSS_CLASSES.HOSPITAL_NAME)).text(ctx.hospital).build()
4310
+ );
4311
+ }
4312
+ if (ctx.department) {
4313
+ parts.push(
4314
+ div().class(cls2(PAGE_CSS_CLASSES.DEPARTMENT_NAME)).text(ctx.department).build()
4315
+ );
4316
+ }
4317
+ const titleText = `${ctx.title} ${ctx.textConfig.pageTitleSuffix}`;
4318
+ parts.push(
4319
+ div().class(cls2(PAGE_CSS_CLASSES.FORM_TITLE)).text(titleText).build()
4320
+ );
4321
+ return div().class(cls2(PAGE_CSS_CLASSES.PRINT_HEADER)).raw(parts.join("\n")).build();
4322
+ }
4323
+ function renderOverflowPageFooter(ctx, cls2) {
4324
+ const format = ctx.pageNumberFormat ?? "Page {current} of {total}";
4325
+ const pageNumberText = format.replace("{current}", String(ctx.pageNumber)).replace("{total}", String(ctx.totalPages));
4326
+ const pageNumberHtml = span().class(cls2(PAGE_CSS_CLASSES.PAGE_NUMBER)).text(pageNumberText).build();
4327
+ return div().class(cls2(PAGE_CSS_CLASSES.PRINT_FOOTER)).raw(pageNumberHtml).build();
4328
+ }
4329
+ function renderOverflowPageContent(ctx, cls2) {
4330
+ const parts = [];
4331
+ for (const { result, label } of ctx.overflowFields) {
4332
+ if (result.hasOverflow && result.rest) {
4333
+ const fieldHtml = renderOverflowContinuation(result, label, ctx.textConfig, cls2);
4334
+ if (fieldHtml) {
4335
+ parts.push(fieldHtml);
4336
+ }
4337
+ }
4338
+ }
4339
+ return div().class(cls2(PAGE_CSS_CLASSES.PRINT_CONTENT)).raw(parts.join("\n")).build();
4340
+ }
4341
+ function renderOverflowContinuationPage(ctx, cls2, pageSize = "16k", orientation = "portrait") {
4342
+ const pageClasses = [
4343
+ cls2(PAGE_CSS_CLASSES.PRINT_PAGE),
4344
+ cls2(pageSize),
4345
+ cls2(orientation),
4346
+ cls2(PAGE_CSS_CLASSES.CONTINUATION_PAGE)
4347
+ ].join(" ");
4348
+ const parts = [];
4349
+ parts.push(renderOverflowPageHeader(ctx, cls2));
4350
+ parts.push(renderOverflowPageContent(ctx, cls2));
4351
+ if (ctx.showSignature && ctx.signatureHtml) {
4352
+ parts.push(ctx.signatureHtml);
4353
+ }
4354
+ parts.push(renderOverflowPageFooter(ctx, cls2));
4355
+ return div().class(pageClasses).attr("data-page", ctx.pageNumber).raw(parts.join("\n")).build();
4356
+ }
4357
+ function hasAnyContinuationContent(overflowFields) {
4358
+ return overflowFields.some(({ result }) => result.hasOverflow && result.rest);
4359
+ }
4360
+
4361
+ // src/pagination/strategies/pagination-strategy.ts
4362
+ var PaginationContext = class {
4363
+ /**
4364
+ * Create pagination context with strategies
4365
+ * @param strategies - Array of pagination strategies
4366
+ * @requirements 4.1 - Context accepts strategies in constructor
4367
+ */
4368
+ constructor(strategies) {
4369
+ this.strategies = strategies;
4370
+ }
4371
+ /**
4372
+ * Get strategies that apply to the given schema
4373
+ * @param schema - Print schema with pagination configuration
4374
+ * @returns Array of applicable strategies
4375
+ * @requirements 4.2 - Context provides getApplicableStrategies method
4376
+ */
4377
+ getApplicableStrategies(schema) {
4378
+ return this.strategies.filter((strategy) => strategy.shouldApply(schema));
4379
+ }
4380
+ /**
4381
+ * Render content using applicable strategies
4382
+ * Uses the first applicable strategy, falls back to non-paginated rendering if none apply
4383
+ * @param schema - Print schema with pagination configuration
4384
+ * @param data - Form data to render
4385
+ * @param options - Render options
4386
+ * @returns Rendered HTML string
4387
+ * @requirements 4.3, 4.4 - Context provides render method and executes strategies
4388
+ */
4389
+ render(schema, data, options) {
4390
+ const applicableStrategies = this.getApplicableStrategies(schema);
4391
+ if (applicableStrategies.length === 0) {
4392
+ throw new Error("No applicable pagination strategy found and fallback not implemented yet");
4393
+ }
4394
+ const strategy = applicableStrategies[0];
4395
+ return strategy.render(schema, data, options);
4396
+ }
4397
+ /**
4398
+ * Add a strategy to the context
4399
+ * @param strategy - Strategy to add
4400
+ */
4401
+ addStrategy(strategy) {
4402
+ this.strategies.push(strategy);
4403
+ }
4404
+ /**
4405
+ * Remove a strategy from the context
4406
+ * @param strategyName - Name of strategy to remove
4407
+ * @returns Whether strategy was found and removed
4408
+ */
4409
+ removeStrategy(strategyName) {
4410
+ const index = this.strategies.findIndex((s) => s.name === strategyName);
4411
+ if (index >= 0) {
4412
+ this.strategies.splice(index, 1);
4413
+ return true;
4414
+ }
4415
+ return false;
4416
+ }
4417
+ /**
4418
+ * Get all registered strategies
4419
+ * @returns Array of all strategies
4420
+ */
4421
+ getAllStrategies() {
4422
+ return [...this.strategies];
4423
+ }
4424
+ /**
4425
+ * Get strategy by name
4426
+ * @param name - Strategy name
4427
+ * @returns Strategy if found, undefined otherwise
4428
+ */
4429
+ getStrategy(name) {
4430
+ return this.strategies.find((s) => s.name === name);
4431
+ }
4432
+ };
4433
+
4434
+ // src/pagination/measurer-types.ts
4435
+ var DEFAULT_MEASURE_CONFIG = {
4436
+ containerWidth: 624,
4437
+ // Approximately 165mm @ 96dpi (16K paper usable width)
4438
+ fontSize: "10pt",
4439
+ lineHeight: 1.8,
4440
+ fontFamily: "'Source Han Serif SC', 'SimSun', 'Song Ti', serif"
4441
+ };
4442
+ var MEASURE_CONTAINER_CLASS = "print-measure-container";
4443
+ var DEFAULT_TEXT_ESTIMATE_OPTIONS = {
4444
+ containerWidth: 624,
4445
+ fontSize: 13.33,
4446
+ // 10pt ≈ 13.33px
4447
+ lineHeight: 1.8,
4448
+ isChinese: true
4449
+ };
4450
+ function createDualSelector(classNames) {
4451
+ return classNames.flatMap((cn) => [`.${cn}`, `.${CSS_NAMESPACE}-${cn}`]).join(", ");
4452
+ }
4453
+ function createSectionWrapperSelector(wrapperClass, sectionClass) {
4454
+ return [
4455
+ // Wrapper with data-section-id
4456
+ `.${wrapperClass}[data-section-id]`,
4457
+ `.${CSS_NAMESPACE}-${wrapperClass}[data-section-id]`,
4458
+ // Direct section class
4459
+ `.${sectionClass}`,
4460
+ `.${CSS_NAMESPACE}-${sectionClass}`,
4461
+ // Compound class (div.print-section.section-class)
4462
+ `div.print-section.${sectionClass}`,
4463
+ `div.${CSS_NAMESPACE}-print-section.${CSS_NAMESPACE}-${sectionClass}`
4464
+ ].join(", ");
4465
+ }
4466
+ var MEASURE_SELECTORS = {
4467
+ /** Header selector - matches .print-header and .mpr-print-header */
4468
+ HEADER: createDualSelector(["print-header"]),
4469
+ /** Footer selector - matches .print-footer and .mpr-print-footer */
4470
+ FOOTER: createDualSelector(["print-footer"]),
4471
+ /** Page body selector - matches .print-body/.print-content variants */
4472
+ BODY: createDualSelector(["print-body", "print-content"]),
4473
+ /** Section title selector */
4474
+ SECTION_TITLE: createDualSelector(["section-title"]),
4475
+ /** Info grid wrapper selector */
4476
+ INFO_GRID_WRAPPER: createSectionWrapperSelector("info-grid-wrapper", "info-grid"),
4477
+ /** Data table wrapper selector */
4478
+ TABLE_WRAPPER: createSectionWrapperSelector("data-table-wrapper", "data-table"),
4479
+ /** Checkbox grid wrapper selector */
4480
+ CHECKBOX_GRID_WRAPPER: createSectionWrapperSelector("checkbox-grid-wrapper", "checkbox-grid"),
4481
+ /** Medical checkbox row wrapper selector */
4482
+ MEDICAL_CHECKBOX_ROW_WRAPPER: createDualSelector(["medical-checkbox-row-wrapper[data-section-id]"]),
4483
+ /** Notes selector */
4484
+ NOTES: createDualSelector(["notes-text", "notes-section"]),
4485
+ /** Signature area selector */
4486
+ SIGNATURE: createDualSelector(["signature-area"]),
4487
+ /** Table header selector (standard HTML element, no namespace needed) */
4488
+ TABLE_HEADER: "thead",
4489
+ /** Table rows selector (standard HTML element, no namespace needed) */
4490
+ TABLE_ROWS: "tbody tr"
4491
+ };
4492
+
4493
+ // src/pagination/content-measurer.ts
4494
+ function isBrowserEnvironment() {
4495
+ return typeof window !== "undefined" && typeof document !== "undefined" && typeof document.createElement === "function";
4496
+ }
4497
+ function ensureBrowserEnvironment() {
4498
+ if (!isBrowserEnvironment()) {
4499
+ throw new Error(
4500
+ "Content measurer requires browser environment. Use Puppeteer for Node.js environment."
4501
+ );
4502
+ }
4503
+ }
4504
+ function createMeasureContainer(config = DEFAULT_MEASURE_CONFIG, options = {}) {
4505
+ ensureBrowserEnvironment();
4506
+ const mergedConfig = {
4507
+ ...DEFAULT_MEASURE_CONFIG,
4508
+ ...config
4509
+ };
4510
+ const {
4511
+ className = MEASURE_CONTAINER_CLASS,
4512
+ appendToBody = true,
4513
+ customStyles = {}
4514
+ } = options;
4515
+ const container = document.createElement("div");
4516
+ container.className = className;
4517
+ Object.assign(container.style, {
4518
+ // Hide container
4519
+ position: "absolute",
4520
+ left: "-9999px",
4521
+ top: "-9999px",
4522
+ visibility: "hidden",
4523
+ // Dimensions
4524
+ width: `${mergedConfig.containerWidth}px`,
4525
+ // Print styles
4526
+ fontSize: mergedConfig.fontSize,
4527
+ lineHeight: String(mergedConfig.lineHeight),
4528
+ fontFamily: mergedConfig.fontFamily,
4529
+ // Ensure consistent box model
4530
+ boxSizing: "border-box",
4531
+ // Prevent content overflow affecting measurement
4532
+ overflow: "hidden",
4533
+ // Custom styles
4534
+ ...customStyles
4535
+ });
4536
+ if (appendToBody) {
4537
+ document.body.appendChild(container);
4538
+ }
4539
+ return container;
4540
+ }
4541
+ function destroyMeasureContainer(container) {
4542
+ if (container && container.parentNode) {
4543
+ container.parentNode.removeChild(container);
4544
+ }
4545
+ }
4546
+ function measureElementHeight(element, container) {
4547
+ ensureBrowserEnvironment();
4548
+ const clone = element.cloneNode(true);
4549
+ clone.style.visibility = "visible";
4550
+ clone.style.position = "static";
4551
+ clone.style.display = "";
4552
+ container.appendChild(clone);
4553
+ const computedStyle = window.getComputedStyle(clone);
4554
+ const marginTop = parseFloat(computedStyle.marginTop) || 0;
4555
+ const marginBottom = parseFloat(computedStyle.marginBottom) || 0;
4556
+ const rect = clone.getBoundingClientRect();
4557
+ const height = rect.height + marginTop + marginBottom;
4558
+ container.removeChild(clone);
4559
+ return height;
4560
+ }
4561
+ function measureElementWithOptions(element, container, options) {
4562
+ const height = measureElementHeight(element, container);
4563
+ return {
4564
+ id: options.id,
4565
+ type: options.type,
4566
+ height,
4567
+ tableId: options.tableId,
4568
+ dataIndex: options.dataIndex
4569
+ };
4570
+ }
4571
+ function measureTableRows(tableElement, container, options) {
4572
+ ensureBrowserEnvironment();
4573
+ const { tableId, includeHeader = true, includeRows = true } = options;
4574
+ const results = [];
4575
+ const tableClone = tableElement.cloneNode(true);
4576
+ tableClone.style.visibility = "visible";
4577
+ tableClone.style.position = "static";
4578
+ container.appendChild(tableClone);
4579
+ if (includeHeader) {
4580
+ const thead4 = tableClone.querySelector(MEASURE_SELECTORS.TABLE_HEADER);
4581
+ if (thead4) {
4582
+ const theadRect = thead4.getBoundingClientRect();
4583
+ results.push({
4584
+ id: `${tableId}-header`,
4585
+ type: "table-header",
4586
+ height: theadRect.height,
4587
+ tableId
4588
+ });
4589
+ }
4590
+ }
4591
+ if (includeRows) {
4592
+ const rows = tableClone.querySelectorAll(MEASURE_SELECTORS.TABLE_ROWS);
4593
+ rows.forEach((row, index) => {
4594
+ const rowRect = row.getBoundingClientRect();
4595
+ results.push({
4596
+ id: `${tableId}-row-${index}`,
4597
+ type: "table-row",
4598
+ height: rowRect.height,
4599
+ tableId,
4600
+ dataIndex: index
4601
+ });
4602
+ });
4603
+ }
4604
+ container.removeChild(tableClone);
4605
+ return results;
4606
+ }
4607
+ function estimateTextHeight(text, options = DEFAULT_TEXT_ESTIMATE_OPTIONS) {
4608
+ if (!text) return 0;
4609
+ const {
4610
+ containerWidth,
4611
+ fontSize = DEFAULT_TEXT_ESTIMATE_OPTIONS.fontSize,
4612
+ lineHeight = DEFAULT_TEXT_ESTIMATE_OPTIONS.lineHeight,
4613
+ isChinese = DEFAULT_TEXT_ESTIMATE_OPTIONS.isChinese
4614
+ } = options;
4615
+ const charWidth = isChinese ? fontSize : fontSize * 0.5;
4616
+ const charsPerLine = Math.floor(containerWidth / charWidth);
4617
+ const lines = text.split("\n");
4618
+ let totalLines = 0;
4619
+ for (const line of lines) {
4620
+ if (line.length === 0) {
4621
+ totalLines += 1;
4622
+ } else {
4623
+ const effectiveLength = isChinese ? line.length : countEffectiveChars(line);
4624
+ totalLines += Math.ceil(effectiveLength / charsPerLine);
4625
+ }
4626
+ }
4627
+ return totalLines * fontSize * lineHeight;
4628
+ }
4629
+ function countEffectiveChars(text) {
4630
+ let count = 0;
4631
+ for (const char of text) {
4632
+ if (char.charCodeAt(0) > 127) {
4633
+ count += 2;
4634
+ } else {
4635
+ count += 1;
4636
+ }
4637
+ }
4638
+ return count;
4639
+ }
4640
+ var ALTERNATIVE_TABLE_SELECTORS = "div.data-table, div.mpr-data-table, table";
4641
+ function findPrintPageContainer(container) {
4642
+ const printPage = container.querySelector(".print-page, .mpr-print-page");
4643
+ if (printPage) {
4644
+ return printPage;
4645
+ }
4646
+ return container;
4647
+ }
4648
+ function isTableElement(element) {
4649
+ return element.matches(MEASURE_SELECTORS.TABLE_WRAPPER) || element.matches(ALTERNATIVE_TABLE_SELECTORS) || element.tagName === "TABLE";
4650
+ }
4651
+ function measureTableInto(element, tableId, results) {
4652
+ const tableElement = element.tagName === "TABLE" ? element : element.querySelector("table");
4653
+ if (!tableElement) return;
4654
+ const thead4 = tableElement.querySelector(MEASURE_SELECTORS.TABLE_HEADER);
4655
+ if (thead4) {
4656
+ results.push({
4657
+ id: `${tableId}-header`,
4658
+ type: "table-header",
4659
+ height: thead4.getBoundingClientRect().height,
4660
+ tableId
4661
+ });
4662
+ }
4663
+ const rows = tableElement.querySelectorAll(MEASURE_SELECTORS.TABLE_ROWS);
4664
+ rows.forEach((row, rowIndex) => {
4665
+ results.push({
4666
+ id: `${tableId}-row-${rowIndex}`,
4667
+ type: "table-row",
4668
+ height: row.getBoundingClientRect().height,
4669
+ tableId,
4670
+ dataIndex: rowIndex
4671
+ });
4672
+ });
4673
+ }
4674
+ function measureHeaderInto(pageContainer, results) {
4675
+ const header3 = pageContainer.querySelector(MEASURE_SELECTORS.HEADER);
4676
+ if (header3) {
4677
+ results.push({
4678
+ id: "page-header",
4679
+ type: "header",
4680
+ height: header3.getBoundingClientRect().height
4681
+ });
4682
+ }
4683
+ }
4684
+ function measureFooterInto(pageContainer, printBody, results) {
4685
+ const footer3 = pageContainer.querySelector(MEASURE_SELECTORS.FOOTER);
4686
+ if (footer3) {
4687
+ const rect = footer3.getBoundingClientRect();
4688
+ const computedStyle = window.getComputedStyle(footer3);
4689
+ const marginTop = parseFloat(computedStyle.marginTop) || 0;
4690
+ const marginBottom = parseFloat(computedStyle.marginBottom) || 0;
4691
+ const footerHeight = rect.height + marginTop + marginBottom;
4692
+ if (footerHeight > 0) {
4693
+ results.push({
4694
+ id: "page-footer",
4695
+ type: "footer",
4696
+ height: footerHeight
4697
+ });
4698
+ }
4699
+ }
4700
+ if (printBody) {
4701
+ const notes = printBody.querySelectorAll(MEASURE_SELECTORS.NOTES);
4702
+ notes.forEach((note, index) => {
4703
+ const rect = note.getBoundingClientRect();
4704
+ const computedStyle = window.getComputedStyle(note);
4705
+ const marginTop = parseFloat(computedStyle.marginTop) || 0;
4706
+ const marginBottom = parseFloat(computedStyle.marginBottom) || 0;
4707
+ const noteHeight = rect.height + marginTop + marginBottom;
4708
+ if (noteHeight > 0) {
4709
+ results.push({
4710
+ id: `notes-${index}`,
4711
+ type: "footer",
4712
+ height: noteHeight
4713
+ });
4714
+ }
4715
+ });
4716
+ }
4717
+ }
4718
+ function measureSignaturesInto(container, results) {
4719
+ const signatures = container.querySelectorAll(MEASURE_SELECTORS.SIGNATURE);
4720
+ signatures.forEach((sig, index) => {
4721
+ results.push({
4722
+ id: `signature-${index}`,
4723
+ type: "signature",
4724
+ height: sig.getBoundingClientRect().height
4725
+ });
4726
+ });
4727
+ }
4728
+ function measureSectionsInto(printBody, results, options) {
4729
+ const { measureSections, measureTables } = options;
4730
+ let sectionIndex = 0;
4731
+ const allSectionSelectors = [
4732
+ MEASURE_SELECTORS.INFO_GRID_WRAPPER,
4733
+ MEASURE_SELECTORS.TABLE_WRAPPER,
4734
+ MEASURE_SELECTORS.CHECKBOX_GRID_WRAPPER,
4735
+ MEASURE_SELECTORS.MEDICAL_CHECKBOX_ROW_WRAPPER
4736
+ ].join(", ");
4737
+ const allSectionElements = printBody.querySelectorAll(allSectionSelectors);
4738
+ allSectionElements.forEach((element) => {
4739
+ const isTable = isTableElement(element);
4740
+ if (isTable && measureTables) {
4741
+ measureTableInto(element, `table-${sectionIndex}`, results);
4742
+ sectionIndex++;
4743
+ } else if (!isTable && measureSections) {
4744
+ results.push({
4745
+ id: `section-${sectionIndex}`,
4746
+ type: "section",
4747
+ height: element.getBoundingClientRect().height
4748
+ });
4749
+ sectionIndex++;
4750
+ }
4751
+ });
4752
+ if (allSectionElements.length === 0 && measureTables) {
4753
+ const tableContainers = printBody.querySelectorAll(ALTERNATIVE_TABLE_SELECTORS);
4754
+ tableContainers.forEach((tableContainer) => {
4755
+ measureTableInto(tableContainer, `table-${sectionIndex}`, results);
4756
+ sectionIndex++;
4757
+ });
4758
+ }
4759
+ }
4760
+ function measureAll(contentContainer, container, options = {}) {
4761
+ ensureBrowserEnvironment();
4762
+ const {
4763
+ measureHeader = true,
4764
+ measureFooter = true,
4765
+ measureSignature = true,
4766
+ measureTables = true,
4767
+ measureSections = true
4768
+ } = options;
4769
+ const results = [];
4770
+ const clone = contentContainer.cloneNode(true);
4771
+ clone.style.visibility = "visible";
4772
+ clone.style.position = "static";
4773
+ container.appendChild(clone);
4774
+ const pageContainer = findPrintPageContainer(clone);
4775
+ if (measureHeader) {
4776
+ measureHeaderInto(pageContainer, results);
4777
+ }
4778
+ const printBody = pageContainer.querySelector(MEASURE_SELECTORS.BODY);
4779
+ if (printBody && (measureSections || measureTables)) {
4780
+ measureSectionsInto(printBody, results, { measureSections, measureTables });
4781
+ }
4782
+ if (measureFooter) {
4783
+ measureFooterInto(pageContainer, printBody, results);
4784
+ }
4785
+ if (measureSignature) {
4786
+ measureSignaturesInto(clone, results);
4787
+ }
4788
+ container.removeChild(clone);
4789
+ return results;
4790
+ }
4791
+ function createContentMeasurer(config = DEFAULT_MEASURE_CONFIG) {
4792
+ const state = {
4793
+ container: null,
4794
+ config: { ...DEFAULT_MEASURE_CONFIG, ...config }
4795
+ };
4796
+ const getContainer = () => {
4797
+ if (!state.container) {
4798
+ state.container = createMeasureContainer(state.config);
4799
+ }
4800
+ return state.container;
4801
+ };
4802
+ const measureElement = (element) => {
4803
+ const container = getContainer();
4804
+ return measureElementHeight(element, container);
4805
+ };
4806
+ const measureElementWith = (element, options) => {
4807
+ const container = getContainer();
4808
+ return measureElementWithOptions(element, container, options);
4809
+ };
4810
+ const measureTable = (tableElement, options) => {
4811
+ const container = getContainer();
4812
+ return measureTableRows(tableElement, container, options);
4813
+ };
4814
+ const measureAllContent = (contentContainer, options) => {
4815
+ const container = getContainer();
4816
+ return measureAll(contentContainer, container, options);
4817
+ };
4818
+ const cleanup = () => {
4819
+ if (state.container) {
4820
+ destroyMeasureContainer(state.container);
4821
+ state.container = null;
4822
+ }
4823
+ };
4824
+ return {
4825
+ /** Measure single element height */
4826
+ measureElement,
4827
+ /** Measure element with options */
4828
+ measureElementWith,
4829
+ /** Batch measure table rows */
4830
+ measureTable,
4831
+ /** Measure all content */
4832
+ measureAll: measureAllContent,
4833
+ /** Estimate text height */
4834
+ estimateTextHeight,
4835
+ /** Clean up measurement container */
4836
+ cleanup,
4837
+ /** Measurement configuration */
4838
+ config: state.config,
4839
+ /** Check if in browser environment */
4840
+ isBrowserEnvironment
4841
+ };
4842
+ }
4843
+
4844
+ // src/pagination/strategies/smart/dom-measurement-strategy.ts
4845
+ var DomMeasurementStrategy = class {
4846
+ /**
4847
+ * Measure content and return measurable items with actual DOM heights
4848
+ *
4849
+ * @param schema - Print schema with pagination configuration
4850
+ * @param data - Form data containing actual content (e.g., table rows)
4851
+ * @param _config - Measurement configuration options (unused, kept for interface compliance)
4852
+ * @returns Array of measurable items with heights in pixels
4853
+ *
4854
+ * @throws Error if not running in browser environment
4855
+ * @throws Error if DOM measurement fails
4856
+ *
4857
+ * @requirements 3.1, 3.2, 3.3, 3.4, 3.5 - DOM measurement implementation
4858
+ */
4859
+ measure(schema, data, _config) {
4860
+ if (!isBrowserEnvironment()) {
4861
+ throw new Error(
4862
+ "DomMeasurementStrategy requires browser environment. Smart pagination with DOM measurement is only available in browser. For Node.js, use Puppeteer or provide pre-measured items."
4863
+ );
4864
+ }
4865
+ const containerWidth = this.getContainerWidth(schema);
4866
+ const measurer = createContentMeasurer({ containerWidth });
4867
+ let tempContainer = null;
4868
+ try {
4869
+ const tempHtml = renderToIsolatedFragment(schema, data);
4870
+ tempContainer = this.createTempContainer(tempHtml, containerWidth);
4871
+ document.body.appendChild(tempContainer);
4872
+ const items = measurer.measureAll(tempContainer);
4873
+ return this.filterInvalidItems(items);
4874
+ } catch (error) {
4875
+ const message = error instanceof Error ? error.message : String(error);
4876
+ throw new Error(`DOM measurement failed: ${message}`);
4877
+ } finally {
4878
+ if (tempContainer?.parentNode) {
4879
+ tempContainer.parentNode.removeChild(tempContainer);
4880
+ }
4881
+ measurer.cleanup();
4882
+ }
4883
+ }
4884
+ // ==================== Private Helper Methods ====================
4885
+ /**
4886
+ * Get container width for measurement based on page dimensions
4887
+ * Reuses page dimension presets from page-dimensions module
4888
+ *
4889
+ * @param schema - Print schema with page configuration
4890
+ * @returns Container width in pixels
4891
+ */
4892
+ getContainerWidth(schema) {
4893
+ const pageSize = schema.pageSize?.toUpperCase() ?? "16K";
4894
+ const dimensions = getPageDimensions(pageSize);
4895
+ return calculateUsableWidth(dimensions);
4896
+ }
4897
+ /**
4898
+ * Create temporary hidden container for measurement
4899
+ *
4900
+ * @param html - HTML content to measure
4901
+ * @param width - Container width in pixels
4902
+ * @returns Hidden container element
4903
+ *
4904
+ * @requirements 3.1 - Render content to hidden DOM container
4905
+ */
4906
+ createTempContainer(html, width) {
4907
+ const container = document.createElement("div");
4908
+ container.style.position = "absolute";
4909
+ container.style.left = "-9999px";
4910
+ container.style.top = "-9999px";
4911
+ container.style.visibility = "hidden";
4912
+ container.style.width = `${width}px`;
4913
+ container.style.boxSizing = "border-box";
4914
+ container.style.overflow = "hidden";
4915
+ container.innerHTML = html;
4916
+ return container;
4917
+ }
4918
+ /**
4919
+ * Filter out items with invalid heights
4920
+ *
4921
+ * Removes items with zero or negative heights that would cause
4922
+ * pagination calculation issues.
4923
+ *
4924
+ * @param items - Measured items from DOM
4925
+ * @returns Valid measurable items with positive heights
4926
+ */
4927
+ filterInvalidItems(items) {
4928
+ return items.filter((item) => {
4929
+ if (item.height <= 0) {
4930
+ if (item.type !== "footer") {
4931
+ console.warn(
4932
+ `DomMeasurementStrategy: Item "${item.id}" has invalid height ${item.height}, skipping`
4933
+ );
4934
+ }
4935
+ return false;
4936
+ }
4937
+ return true;
4938
+ });
4939
+ }
4940
+ };
4941
+
3923
4942
  // src/pagination/paginated-renderer.ts
3924
4943
  var CSS_CLASSES = {
3925
4944
  // Page structure
@@ -3956,7 +4975,8 @@ var DEFAULT_PAGINATED_RENDER_CONFIG = {
3956
4975
  continuationSuffix: "(continued)",
3957
4976
  pageNumberFormat: "Page {current} of {total}",
3958
4977
  pageDimensions: PAGE_16K,
3959
- isolated: false
4978
+ isolated: false,
4979
+ overflowText: DEFAULT_OVERFLOW_TEXT
3960
4980
  };
3961
4981
  function mergeConfig(config) {
3962
4982
  return {
@@ -4078,15 +5098,24 @@ var contentRenderers = {
4078
5098
  if (!section2) return "";
4079
5099
  return renderSectionWithTitle(section2, data, options, cls2);
4080
5100
  },
4081
- // Table row rendering needs to be wrapped in table, recommend using renderAllSections fallback
5101
+ // Table row rendering is handled by renderTableRows function
4082
5102
  "table-row": () => "",
4083
- // Table headers are usually handled in repeatedHeaders
5103
+ // Table headers are handled by renderTableRows function
4084
5104
  "table-header": () => "",
4085
5105
  // Following types are handled in dedicated functions
4086
5106
  header: () => "",
4087
5107
  footer: () => "",
4088
5108
  signature: () => ""
4089
5109
  };
5110
+ function renderPartialTable(section2, data, options, cls2, rowIndices, includeHeader) {
5111
+ if (section2.type !== "table") return "";
5112
+ const tableHtml = renderTable(section2.config, data, options, {
5113
+ rowIndices,
5114
+ includeHeader
5115
+ });
5116
+ const titleHtml = section2.title && includeHeader ? div().class(cls2(CSS_CLASSES.SECTION_TITLE)).text(section2.title).build() : "";
5117
+ return `${titleHtml}${tableHtml}`;
5118
+ }
4090
5119
  function renderContentItem(itemId, itemMap, sectionMap, data, options, cls2) {
4091
5120
  const item = itemMap.get(itemId);
4092
5121
  if (!item) return "";
@@ -4094,34 +5123,116 @@ function renderContentItem(itemId, itemMap, sectionMap, data, options, cls2) {
4094
5123
  return renderer ? renderer(item, sectionMap, data, options, cls2) : "";
4095
5124
  }
4096
5125
  function renderAllSections(ctx, cls2) {
4097
- const { schema, data, options } = ctx;
5126
+ const { schema, data, options, isFirstPage, overflowResults, overflowFieldNames, overflowTextConfig } = ctx;
4098
5127
  const parts = [];
5128
+ const overflowResultMap = /* @__PURE__ */ new Map();
5129
+ if (overflowResults) {
5130
+ for (const result of overflowResults) {
5131
+ overflowResultMap.set(result.fieldName, result);
5132
+ }
5133
+ }
4099
5134
  for (const section2 of schema.sections) {
4100
5135
  if (section2.type === "signature-area") continue;
4101
- parts.push(renderSectionWithTitle(section2, data, options, cls2));
5136
+ if (isFirstPage && overflowFieldNames && overflowFieldNames.length > 0 && isOverflowSection(section2, overflowFieldNames)) {
5137
+ parts.push(renderSectionWithOverflow(section2, data, options, cls2, overflowResultMap, overflowTextConfig));
5138
+ } else {
5139
+ parts.push(renderSectionWithTitle(section2, data, options, cls2));
5140
+ }
4102
5141
  }
4103
5142
  return parts.join("\n");
4104
5143
  }
5144
+ function renderSectionWithOverflow(section2, data, options, cls2, overflowResultMap, textConfig) {
5145
+ const titleHtml = section2.title ? div().class(cls2(CSS_CLASSES.SECTION_TITLE)).text(section2.title).build() : "";
5146
+ if (section2.type === "info-grid") {
5147
+ const config = section2.config;
5148
+ const modifiedData = { ...data };
5149
+ const mergedTextConfig = textConfig ?? DEFAULT_OVERFLOW_TEXT;
5150
+ for (const row of config.rows) {
5151
+ for (const cell of row.cells) {
5152
+ const overflowResult = overflowResultMap.get(cell.field);
5153
+ if (overflowResult) {
5154
+ const maxChars = PAGINATION_DEFAULTS.OVERFLOW_FIRST_LINE_CHARS;
5155
+ const renderedContent = renderOverflowFirstLine(
5156
+ data[cell.field],
5157
+ maxChars,
5158
+ mergedTextConfig,
5159
+ cls2
5160
+ );
5161
+ modifiedData[cell.field] = renderedContent;
5162
+ modifiedData[`__overflow_html_${cell.field}`] = true;
5163
+ }
5164
+ }
5165
+ }
5166
+ const content2 = renderSection(section2.type, section2.config, modifiedData, options);
5167
+ return `${titleHtml}${content2}`;
5168
+ }
5169
+ const content = renderSection(section2.type, section2.config, data, options);
5170
+ return `${titleHtml}${content}`;
5171
+ }
4105
5172
  function renderPageBody(ctx, sectionMap, cls2) {
4106
- const { page, data, options, measuredItems } = ctx;
5173
+ const { page, data, options, measuredItems, isFirstPage, overflowFieldNames } = ctx;
4107
5174
  const parts = [];
4108
- const repeatedHeaders = renderRepeatedHeaders(ctx, sectionMap, cls2);
4109
- if (repeatedHeaders) {
4110
- parts.push(repeatedHeaders);
4111
- }
4112
5175
  const itemMap = new Map(measuredItems.map((m) => [m.id, m]));
4113
5176
  const hasValidItems = page.items.length > 0 && page.items.some((itemId) => {
4114
5177
  const item = itemMap.get(itemId);
4115
- return item?.type === "section";
5178
+ return item?.type === "section" || item?.type === "table-header" || item?.type === "table-row";
4116
5179
  });
4117
- if (hasValidItems) {
5180
+ const hasOverflowFields = isFirstPage && overflowFieldNames && overflowFieldNames.length > 0;
5181
+ if (hasValidItems && !hasOverflowFields) {
5182
+ const tableRowsByTableId = /* @__PURE__ */ new Map();
5183
+ const renderedTables = /* @__PURE__ */ new Set();
4118
5184
  for (const itemId of page.items) {
4119
- const content = renderContentItem(itemId, itemMap, sectionMap, data, options, cls2);
4120
- if (content) {
4121
- parts.push(content);
5185
+ const item = itemMap.get(itemId);
5186
+ if (!item) continue;
5187
+ if (item.type === "table-header" && item.tableId) {
5188
+ if (!tableRowsByTableId.has(item.tableId)) {
5189
+ tableRowsByTableId.set(item.tableId, { indices: [], hasHeader: true });
5190
+ } else {
5191
+ tableRowsByTableId.get(item.tableId).hasHeader = true;
5192
+ }
5193
+ } else if (item.type === "table-row" && item.tableId && item.dataIndex !== void 0) {
5194
+ if (!tableRowsByTableId.has(item.tableId)) {
5195
+ tableRowsByTableId.set(item.tableId, { indices: [], hasHeader: false });
5196
+ }
5197
+ tableRowsByTableId.get(item.tableId).indices.push(item.dataIndex);
5198
+ }
5199
+ }
5200
+ for (const itemId of page.items) {
5201
+ const item = itemMap.get(itemId);
5202
+ if (!item) continue;
5203
+ if (item.type === "section") {
5204
+ const content = renderContentItem(itemId, itemMap, sectionMap, data, options, cls2);
5205
+ if (content) {
5206
+ parts.push(content);
5207
+ }
5208
+ } else if ((item.type === "table-header" || item.type === "table-row") && item.tableId) {
5209
+ if (!renderedTables.has(item.tableId)) {
5210
+ renderedTables.add(item.tableId);
5211
+ const tableData = tableRowsByTableId.get(item.tableId);
5212
+ const section2 = sectionMap.get(item.tableId);
5213
+ if (section2 && tableData) {
5214
+ const hasRepeatedHeader = page.repeatedHeaders.includes(`${item.tableId}-header`);
5215
+ const includeHeader = tableData.hasHeader || hasRepeatedHeader;
5216
+ const tableHtml = renderPartialTable(
5217
+ section2,
5218
+ data,
5219
+ options,
5220
+ cls2,
5221
+ tableData.indices,
5222
+ includeHeader
5223
+ );
5224
+ if (tableHtml) {
5225
+ parts.push(tableHtml);
5226
+ }
5227
+ }
5228
+ }
4122
5229
  }
4123
5230
  }
4124
5231
  } else {
5232
+ const repeatedHeaders = renderRepeatedHeaders(ctx, sectionMap, cls2);
5233
+ if (repeatedHeaders) {
5234
+ parts.push(repeatedHeaders);
5235
+ }
4125
5236
  parts.push(renderAllSections(ctx, cls2));
4126
5237
  }
4127
5238
  return main().class(cls2(CSS_CLASSES.PRINT_CONTENT)).raw(parts.join("\n")).build();
@@ -4151,6 +5262,7 @@ function buildSectionMap(schema, measuredItems) {
4151
5262
  if (section2.type === "table") {
4152
5263
  const tableConfig = section2.config;
4153
5264
  map.set(`table-${tableConfig.dataField}`, section2);
5265
+ map.set(`table-${index}`, section2);
4154
5266
  }
4155
5267
  });
4156
5268
  for (const item of measuredItems) {
@@ -4198,12 +5310,30 @@ function renderPaginatedHtml(context) {
4198
5310
  const mergedConfig = mergeConfig(config);
4199
5311
  const theme = mergeTheme(options?.theme);
4200
5312
  const cls2 = createClassNameFn2(mergedConfig.isolated);
5313
+ const renderOptions2 = mergedConfig.isolated ? { ...options, classPrefix: CSS_NAMESPACE } : options;
4201
5314
  const baseCss = mergedConfig.isolated ? generateIsolatedCss(options?.theme) : generateCss(theme);
4202
5315
  const paginationCss = generatePaginationCss(mergedConfig.isolated);
4203
5316
  const fullCss = `${baseCss}
4204
5317
  ${paginationCss}`;
4205
5318
  const sectionMap = buildSectionMap(schema, measuredItems);
4206
- const { totalPages } = pageBreakResult;
5319
+ const overflowFieldNames = getOverflowFieldNames(schema.pagination);
5320
+ const overflowConfigs = createOverflowFieldConfigs(overflowFieldNames);
5321
+ const overflowResults = overflowConfigs.length > 0 ? processOverflowFields(data, overflowConfigs) : [];
5322
+ const overflowTextConfig = mergeOverflowTextConfig(mergedConfig.overflowText);
5323
+ const overflowFieldsWithLabels = overflowResults.map((result) => {
5324
+ let label = result.fieldName;
5325
+ for (const section2 of schema.sections) {
5326
+ const foundLabel = findOverflowFieldLabel(section2, result.fieldName);
5327
+ if (foundLabel !== result.fieldName) {
5328
+ label = foundLabel;
5329
+ break;
5330
+ }
5331
+ }
5332
+ return { result, label };
5333
+ });
5334
+ const needsOverflowPage = hasAnyContinuationContent(overflowFieldsWithLabels);
5335
+ const baseTotalPages = pageBreakResult.totalPages;
5336
+ const totalPages = needsOverflowPage ? baseTotalPages + 1 : baseTotalPages;
4207
5337
  const pages = pageBreakResult.pages.map((page, i) => {
4208
5338
  const pageNumber = i + 1;
4209
5339
  const pageCtx = {
@@ -4211,14 +5341,46 @@ ${paginationCss}`;
4211
5341
  pageNumber,
4212
5342
  totalPages,
4213
5343
  isFirstPage: pageNumber === 1,
4214
- isLastPage: pageNumber === totalPages,
5344
+ isLastPage: pageNumber === totalPages && !needsOverflowPage,
4215
5345
  schema,
4216
5346
  data,
4217
- options,
5347
+ options: renderOptions2,
4218
5348
  measuredItems,
4219
- config: mergedConfig};
5349
+ config: mergedConfig,
5350
+ // Pass overflow context for first page
5351
+ overflowResults: pageNumber === 1 ? overflowResults : void 0,
5352
+ overflowFieldNames: pageNumber === 1 ? overflowFieldNames : void 0,
5353
+ overflowTextConfig
5354
+ };
4220
5355
  return renderSinglePage(pageCtx, sectionMap, cls2);
4221
5356
  });
5357
+ if (needsOverflowPage) {
5358
+ let signatureHtml;
5359
+ if (mergedConfig.showSignatureOnEachPage) {
5360
+ const signatureSection = schema.sections.find((s) => s.type === "signature-area");
5361
+ if (signatureSection) {
5362
+ signatureHtml = renderSection(signatureSection.type, signatureSection.config, data, renderOptions2);
5363
+ }
5364
+ }
5365
+ const overflowPageHtml = renderOverflowContinuationPage(
5366
+ {
5367
+ pageNumber: totalPages,
5368
+ totalPages,
5369
+ title: schema.header.title,
5370
+ hospital: schema.header.hospital,
5371
+ department: schema.header.department,
5372
+ overflowFields: overflowFieldsWithLabels,
5373
+ textConfig: overflowTextConfig,
5374
+ showSignature: mergedConfig.showSignatureOnEachPage,
5375
+ signatureHtml,
5376
+ pageNumberFormat: mergedConfig.pageNumberFormat
5377
+ },
5378
+ cls2,
5379
+ schema.pageSize.toLowerCase(),
5380
+ schema.orientation
5381
+ );
5382
+ pages.push(overflowPageHtml);
5383
+ }
4222
5384
  const pagesHtml = pages.join("\n");
4223
5385
  const bodyContent = mergedConfig.isolated ? generateIsolatedBodyContent(fullCss, pagesHtml) : pagesHtml;
4224
5386
  return generateHtmlDocument(schema.header.title, fullCss, bodyContent, mergedConfig.isolated);
@@ -4237,6 +5399,11 @@ function generatePaginationCss(isolated = false) {
4237
5399
  const pageBreakBefore = cls2(CSS_CLASSES.PAGE_BREAK_BEFORE);
4238
5400
  const pageBreakAfter = cls2(CSS_CLASSES.PAGE_BREAK_AFTER);
4239
5401
  const noPageBreak = cls2(CSS_CLASSES.NO_PAGE_BREAK);
5402
+ const overflowFirstLine = cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_FIRST_LINE);
5403
+ const overflowContinuation = cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_CONTINUATION);
5404
+ const seeNext = cls2(OVERFLOW_CSS_CLASSES.SEE_NEXT);
5405
+ const overflowLabel = cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_LABEL);
5406
+ const overflowContent = cls2(OVERFLOW_CSS_CLASSES.OVERFLOW_CONTENT);
4240
5407
  return `
4241
5408
  /* Paginated document styles */
4242
5409
  ${rootSelector} {
@@ -4276,6 +5443,32 @@ ${rootSelector} .${printPage} {
4276
5443
  flex: 1;
4277
5444
  }
4278
5445
 
5446
+ /* Overflow field styles */
5447
+ .${overflowFirstLine} {
5448
+ display: inline;
5449
+ }
5450
+
5451
+ .${overflowContinuation} {
5452
+ margin-top: 1em;
5453
+ padding: 0.5em 0;
5454
+ }
5455
+
5456
+ .${seeNext} {
5457
+ color: #dc2626;
5458
+ font-weight: 500;
5459
+ margin-left: 0.25em;
5460
+ }
5461
+
5462
+ .${overflowLabel} {
5463
+ font-weight: 500;
5464
+ margin-bottom: 0.5em;
5465
+ }
5466
+
5467
+ .${overflowContent} {
5468
+ white-space: pre-wrap;
5469
+ line-height: 1.6;
5470
+ }
5471
+
4279
5472
  /* Print styles */
4280
5473
  @media print {
4281
5474
  ${rootSelector} {
@@ -4307,6 +5500,17 @@ ${rootSelector} .${printPage} {
4307
5500
  .${signatureArea} {
4308
5501
  page-break-inside: avoid;
4309
5502
  }
5503
+
5504
+ /* Overflow field print styles */
5505
+ .${seeNext} {
5506
+ color: #dc2626;
5507
+ -webkit-print-color-adjust: exact;
5508
+ print-color-adjust: exact;
5509
+ }
5510
+
5511
+ .${overflowContent} {
5512
+ white-space: pre-wrap;
5513
+ }
4310
5514
  }
4311
5515
 
4312
5516
  /* Pagination control classes */
@@ -4346,6 +5550,290 @@ function createRenderConfigFromPaginationConfig(paginationConfig) {
4346
5550
  };
4347
5551
  }
4348
5552
 
5553
+ // src/pagination/strategies/smart/smart-pagination-strategy.ts
5554
+ var SmartPaginationStrategy = class {
5555
+ /**
5556
+ * Constructor with optional measurement strategy injection
5557
+ * @param measurementStrategy - Custom measurement strategy (defaults to DomMeasurementStrategy)
5558
+ * @requirements 1.3 - Dependency injection for measurement strategy
5559
+ */
5560
+ constructor(measurementStrategy) {
5561
+ /**
5562
+ * Strategy name identifier
5563
+ * @requirements 2.1 - Strategy name
5564
+ */
5565
+ this.name = "smart-pagination";
5566
+ this.measurementStrategy = measurementStrategy ?? new DomMeasurementStrategy();
5567
+ }
5568
+ /**
5569
+ * Check if smart pagination should be applied
5570
+ * @param schema - Print schema with pagination configuration
5571
+ * @returns Whether smart pagination is enabled
5572
+ * @requirements 2.5 - shouldApply checks smartPagination.enabled
5573
+ */
5574
+ shouldApply(schema) {
5575
+ return schema.pagination?.smartPagination?.enabled === true;
5576
+ }
5577
+ /**
5578
+ * Render content using smart pagination
5579
+ * Delegates to existing calculatePageBreaks and renderPaginatedHtml functions
5580
+ * Uses injected measurement strategy for DOM measurement
5581
+ * @param schema - Print schema with pagination configuration
5582
+ * @param data - Form data to render
5583
+ * @param options - Render options
5584
+ * @returns Rendered HTML string
5585
+ * @requirements 1.3, 2.3, 2.4, 3.1, 3.2, 4.1, 4.2, 4.3, 4.4 - Delegate to existing functions with correct measurement data
5586
+ */
5587
+ render(schema, data, options) {
5588
+ const measuredItems = options?.measuredItems ?? this.measurementStrategy.measure(
5589
+ schema,
5590
+ data,
5591
+ {
5592
+ minRowHeight: schema.pagination?.smartPagination?.minRowHeight ?? 8,
5593
+ pageHeight: this.getPageHeight(schema)
5594
+ }
5595
+ );
5596
+ const headerHeight = this.extractHeaderHeight(measuredItems);
5597
+ const footerHeight = this.extractFooterHeight(measuredItems);
5598
+ const signatureHeight = this.extractSignatureHeight(measuredItems);
5599
+ const signatureOnEachPage = schema.pagination?.display?.signatureOnEachPage ?? false;
5600
+ const effectiveFooterHeight = signatureOnEachPage ? footerHeight + signatureHeight : footerHeight;
5601
+ const lastPageExtraHeight = signatureOnEachPage ? 0 : signatureHeight;
5602
+ const contentItems = this.filterContentItems(measuredItems);
5603
+ const pageBreakResult = calculatePageBreaks(contentItems, {
5604
+ pageHeight: this.getPageHeight(schema),
5605
+ headerHeight,
5606
+ footerHeight: effectiveFooterHeight,
5607
+ repeatTableHeaders: schema.pagination?.display?.repeatTableHeaders ?? true,
5608
+ lastPageExtraHeight
5609
+ });
5610
+ const renderOptions2 = options ? {
5611
+ theme: void 0
5612
+ // Will be handled by renderPaginatedHtml
5613
+ } : void 0;
5614
+ return renderPaginatedHtml({
5615
+ schema,
5616
+ data,
5617
+ options: renderOptions2,
5618
+ pageBreakResult,
5619
+ measuredItems,
5620
+ config: {
5621
+ isolated: options?.isolated,
5622
+ showHeaderOnEachPage: schema.pagination?.display?.headerOnEachPage ?? true,
5623
+ showFooterOnEachPage: schema.pagination?.display?.footerOnEachPage ?? true,
5624
+ showSignatureOnEachPage: schema.pagination?.display?.signatureOnEachPage ?? false
5625
+ }
5626
+ });
5627
+ }
5628
+ // ==================== Private Helper Methods ====================
5629
+ /**
5630
+ * Extract header height from measured items
5631
+ * Finds the item with type 'header' and returns its height
5632
+ * @param items - All measured items
5633
+ * @returns Header height in pixels, or 0 if no header item found
5634
+ * @requirements 1.1, 1.2, 1.4 - Extract header height from measured results
5635
+ */
5636
+ extractHeaderHeight(items) {
5637
+ const headerItem = items.find((item) => item.type === "header");
5638
+ return headerItem?.height ?? 0;
5639
+ }
5640
+ /**
5641
+ * Extract footer height from measured items
5642
+ * Only includes 'footer' type items. Signature height is handled separately
5643
+ * based on signatureOnEachPage configuration.
5644
+ * @param items - All measured items
5645
+ * @returns Footer height in pixels, or 0 if no footer items found
5646
+ * @requirements 2.1, 2.2, 2.4 - Extract footer height from measured results
5647
+ */
5648
+ extractFooterHeight(items) {
5649
+ let totalHeight = 0;
5650
+ for (const item of items) {
5651
+ if (item.type === "footer") {
5652
+ totalHeight += item.height;
5653
+ }
5654
+ }
5655
+ return totalHeight;
5656
+ }
5657
+ /**
5658
+ * Extract signature height from measured items
5659
+ * @param items - All measured items
5660
+ * @returns Signature height in pixels, or 0 if no signature items found
5661
+ */
5662
+ extractSignatureHeight(items) {
5663
+ let totalHeight = 0;
5664
+ for (const item of items) {
5665
+ if (item.type === "signature") {
5666
+ totalHeight += item.height;
5667
+ }
5668
+ }
5669
+ return totalHeight;
5670
+ }
5671
+ /**
5672
+ * Filter content items for pagination calculation
5673
+ * Excludes header, footer, and signature items - only keeps section and table items
5674
+ * @param items - All measured items
5675
+ * @returns Array containing only section, table-header, and table-row items
5676
+ * @requirements 4.1, 4.2, 4.3, 4.4 - Filter non-content items from pagination
5677
+ */
5678
+ filterContentItems(items) {
5679
+ return items.filter(
5680
+ (item) => item.type === "section" || item.type === "table-header" || item.type === "table-row"
5681
+ );
5682
+ }
5683
+ /**
5684
+ * Get page height for pagination calculation
5685
+ * @param _schema - Print schema (unused in current implementation)
5686
+ * @returns Page height in pixels
5687
+ */
5688
+ getPageHeight(_schema) {
5689
+ const pageDimensions = PAGE_16K;
5690
+ const mmToPixels = (mm) => mm * 96 / 25.4;
5691
+ const availableHeight = pageDimensions.height - pageDimensions.marginTop - pageDimensions.marginBottom;
5692
+ return mmToPixels(availableHeight);
5693
+ }
5694
+ /**
5695
+ * Estimate measurable items when not provided
5696
+ * Creates basic section items for each schema section
5697
+ *
5698
+ * @deprecated Since v1.4.0. Use DomMeasurementStrategy instead.
5699
+ * This method only provides rough estimates and does not measure actual DOM heights.
5700
+ * The method is no longer used internally - SmartPaginationStrategy now uses
5701
+ * MeasurementStrategy (default: DomMeasurementStrategy) for accurate DOM-based measurement.
5702
+ * Will be removed in v2.0.0.
5703
+ *
5704
+ * @see {@link DomMeasurementStrategy} - For accurate DOM-based measurement
5705
+ * @see {@link MeasurementStrategy} - Interface for custom measurement strategies
5706
+ *
5707
+ * @param schema - Print schema
5708
+ * @returns Estimated measurable items (inaccurate)
5709
+ * @requirements 5.1, 5.2, 5.3, 5.4 - Deprecated method with annotations
5710
+ */
5711
+ estimateItems(schema) {
5712
+ const items = [];
5713
+ const minRowHeight = schema.pagination?.smartPagination?.minRowHeight ?? 8;
5714
+ const mmToPixels = (mm) => mm * 96 / 25.4;
5715
+ const estimatedSectionHeight = mmToPixels(minRowHeight * 3);
5716
+ schema.sections.forEach((section2, index) => {
5717
+ const sectionId = `section-${index}`;
5718
+ items.push({
5719
+ id: sectionId,
5720
+ type: "section",
5721
+ height: estimatedSectionHeight
5722
+ });
5723
+ if (section2.type === "table") {
5724
+ const tableConfig = section2.config;
5725
+ const tableId = `table-${tableConfig.dataField}`;
5726
+ items.push({
5727
+ id: `${tableId}-header`,
5728
+ type: "table-header",
5729
+ height: mmToPixels(minRowHeight),
5730
+ tableId
5731
+ });
5732
+ for (let i = 0; i < 5; i++) {
5733
+ items.push({
5734
+ id: `${tableId}-row-${i}`,
5735
+ type: "table-row",
5736
+ height: mmToPixels(minRowHeight),
5737
+ tableId,
5738
+ dataIndex: i
5739
+ });
5740
+ }
5741
+ }
5742
+ });
5743
+ return items;
5744
+ }
5745
+ };
5746
+
5747
+ // src/pagination/strategies/overflow/overflow-pagination-strategy.ts
5748
+ var OverflowPaginationStrategy = class {
5749
+ constructor() {
5750
+ /**
5751
+ * Strategy name identifier
5752
+ * @requirements 3.1 - Strategy name
5753
+ */
5754
+ this.name = "overflow-pagination";
5755
+ }
5756
+ /**
5757
+ * Check if overflow pagination should be applied
5758
+ * @param schema - Print schema with pagination configuration
5759
+ * @returns Whether overflow pagination is configured
5760
+ * @requirements 3.5 - shouldApply checks pagination.overflow.fields has items
5761
+ */
5762
+ shouldApply(schema) {
5763
+ const fields = schema.pagination?.overflow?.fields;
5764
+ return Array.isArray(fields) && fields.length > 0;
5765
+ }
5766
+ /**
5767
+ * Render content using overflow pagination
5768
+ * Delegates to existing renderPaginatedHtml with overflow configuration
5769
+ * @param schema - Print schema with pagination configuration
5770
+ * @param data - Form data to render
5771
+ * @param options - Render options
5772
+ * @returns Rendered HTML string
5773
+ * @requirements 3.4 - Delegate to existing renderPaginatedHtml with overflow config
5774
+ */
5775
+ render(schema, data, options) {
5776
+ const pageBreakResult = this.createSinglePageResult();
5777
+ const overflowTextConfig = this.mergeOverflowTextConfig(options?.textConfig);
5778
+ const renderOptions2 = options ? {
5779
+ theme: void 0
5780
+ // Will be handled by renderPaginatedHtml
5781
+ } : void 0;
5782
+ return renderPaginatedHtml({
5783
+ schema,
5784
+ data,
5785
+ options: renderOptions2,
5786
+ pageBreakResult,
5787
+ measuredItems: [],
5788
+ // Overflow pagination doesn't use measured items
5789
+ config: {
5790
+ isolated: options?.isolated,
5791
+ showHeaderOnEachPage: schema.pagination?.display?.headerOnEachPage ?? true,
5792
+ showFooterOnEachPage: schema.pagination?.display?.footerOnEachPage ?? true,
5793
+ showSignatureOnEachPage: schema.pagination?.display?.signatureOnEachPage ?? false,
5794
+ overflowText: overflowTextConfig
5795
+ }
5796
+ });
5797
+ }
5798
+ // ==================== Private Helper Methods ====================
5799
+ /**
5800
+ * Create single page result for overflow pagination
5801
+ * Overflow pagination uses a single logical page, with continuation pages handled separately
5802
+ * @returns Single page break result
5803
+ */
5804
+ createSinglePageResult() {
5805
+ return {
5806
+ pages: [{
5807
+ pageNumber: 1,
5808
+ isContinuation: false,
5809
+ items: [],
5810
+ // Items will be handled by renderAllSections fallback
5811
+ repeatedHeaders: []
5812
+ }],
5813
+ totalPages: 1
5814
+ };
5815
+ }
5816
+ /**
5817
+ * Merge overflow text configuration with defaults
5818
+ * @param config - Partial overflow text configuration
5819
+ * @returns Complete overflow text configuration
5820
+ */
5821
+ mergeOverflowTextConfig(config) {
5822
+ return {
5823
+ ...DEFAULT_OVERFLOW_TEXT,
5824
+ ...config
5825
+ };
5826
+ }
5827
+ };
5828
+
5829
+ // src/pagination/strategies/index.ts
5830
+ function createDefaultPaginationContext() {
5831
+ return new PaginationContext([
5832
+ new SmartPaginationStrategy(),
5833
+ new OverflowPaginationStrategy()
5834
+ ]);
5835
+ }
5836
+
4349
5837
  // src/pagination/index.ts
4350
5838
  function usePrintPagination(dimensions = PAGE_16K) {
4351
5839
  const usableHeight = calculateUsableHeight(dimensions);
@@ -4440,6 +5928,6 @@ async function mergePdfs(documents, options, pdfOptions) {
4440
5928
  return Buffer.from(mergedBuffer);
4441
5929
  }
4442
5930
 
4443
- export { AbstractPageRenderer, CSS_NAMESPACE, ContainerSection, DEFAULT_BASE_UNIT, DEFAULT_DPI, DEFAULT_PAGINATED_RENDER_CONFIG, FONT_FAMILY, FONT_STYLE, FONT_WEIGHT, FontLoadError, FormDataTraverser, FormatVisitor, FormatterFactory, HtmlBuilder, HtmlElementBuilder, ISOLATION_ROOT_CLASS, LeafSection, MM_PER_INCH, MeasureVisitor, PAGE_16K, PAGE_A4, PAGE_A5, PAGE_PRESETS, PageBuilder, PaginatedPageRenderer, SIZE_MULTIPLIERS, SectionRendererFactory, SectionTreeTraverser, SinglePageRenderer, StrategyContext, TableBuilder, UNIT_CONVERSIONS, ValidationVisitor, buildTableHeaderMap, calculatePageBreaks, calculatePageBreaksSimple, calculateUsableHeight, calculateUsableHeightMm, calculateUsableWidth, calculateUsableWidthMm, clamp, convertFromMm, convertToMm, createDefaultStrategyContext, createFormDataTraverser, createFormatVisitor, createInlineStyles, createMeasureVisitor, createOverflowFieldConfig, createOverflowFieldConfigs, createPageDimensions, createPaginatedPageRenderer, createRenderConfigFromPaginationConfig, createScaledTheme, createSectionComponent, createSectionTree, createSinglePageRenderer, createThemeWithBaseUnit, createValidationVisitor, defaultColors, defaultFonts, defaultInlineStyles, defaultMultipliers, defaultScaledConfig, defaultTheme, div2 as div, each, escapeAttr, escapeHtml, extractWatermarkOptions, findTableHeader, footer2 as footer, formatBoolean, formatDate, formatNumber, formatPadding, formatSize, formatValue, fragment, generateCss, generateIsolatedCss, generatePaginationCss, getDefaultFormatterFactory, getDefaultSectionRendererFactory, getFontCss, getFontDataUrl, getNamespacedClass, getOverflowFieldConfig, getOverflowFirstLine, getOverflowRest, getPageContentHeight, getPageDimensions, getPageStyles, getSectionRenderer, h, h12 as h1, hasAnyOverflowContent, hasOverflowContent, header2 as header, isChecked, isFontLoaded, isOverflowField, main2 as main, mergePdfs, mergeStyles, mergeTheme, mmToPt, mmToPx, namespaceClass, namespaceClasses, normalizeOpacity, p2 as p, processOverflowFields, ptToMm, pxToMm, registerSectionRenderer, renderPaginatedHtml, renderPaginatedHtmlSimple, renderSectionTree, renderToHtml, renderToIsolatedFragment, renderToIsolatedHtml, renderToPdf, renderWatermarkHtml, scaleValue, span2 as span, styleToString, table2 as table, tbody2 as tbody, td2 as td, th2 as th, thead2 as thead, tr2 as tr, usePrintPagination, validatePageBreakResult, waitForFonts, when };
5931
+ export { AbstractPageRenderer, CSS_NAMESPACE, ContainerSection, DEFAULT_BASE_UNIT, DEFAULT_DPI, DEFAULT_OVERFLOW_TEXT, DEFAULT_PAGINATED_RENDER_CONFIG, ENGLISH_OVERFLOW_TEXT, FONT_FAMILY, FONT_STYLE, FONT_WEIGHT, FontLoadError, FormDataTraverser, FormatVisitor, FormatterFactory, HtmlBuilder, HtmlElementBuilder, ISOLATION_ROOT_CLASS, LeafSection, MEASURABLE_ITEM_TYPES, MM_PER_INCH, MeasureVisitor, OVERFLOW_CSS_CLASSES, OverflowPaginationStrategy, PAGE_16K, PAGE_A4, PAGE_A5, PAGE_PRESETS, PAGINATION_DEFAULTS, PageBuilder, PaginatedPageRenderer, PaginationContext, SIZE_MULTIPLIERS, SectionRendererFactory, SectionTreeTraverser, SinglePageRenderer, SmartPaginationStrategy, StrategyContext, TableBuilder, UNIT_CONVERSIONS, ValidationVisitor, buildTableHeaderMap, calculatePageBreaks, calculatePageBreaksSimple, calculateUsableHeight, calculateUsableHeightMm, calculateUsableWidth, calculateUsableWidthMm, clamp, convertFromMm, convertToMm, createDefaultPaginationContext, createDefaultStrategyContext, createFormDataTraverser, createFormatVisitor, createInlineStyles, createMeasureVisitor, createOverflowFieldConfig, createOverflowFieldConfigs, createPageDimensions, createPaginatedPageRenderer, createRenderConfigFromPaginationConfig, createScaledTheme, createSectionComponent, createSectionTree, createSinglePageRenderer, createThemeWithBaseUnit, createValidationVisitor, defaultColors, defaultFonts, defaultInlineStyles, defaultMultipliers, defaultScaledConfig, defaultTheme, div2 as div, each, escapeAttr, escapeHtml, extractWatermarkOptions, findOverflowFieldCell, findOverflowFieldLabel, findTableHeader, footer2 as footer, formatBoolean, formatDate, formatNumber, formatPadding, formatSize, formatValue, fragment, generateCss, generateIsolatedCss, generatePaginationCss, getDefaultFormatterFactory, getDefaultSectionRendererFactory, getFontCss, getFontDataUrl, getNamespacedClass, getOverflowFieldConfig, getOverflowFieldNames, getOverflowFieldsFromConfig, getOverflowFirstLine, getOverflowRest, getPageContentHeight, getPageDimensions, getPageStyles, getSectionRenderer, h, h12 as h1, hasAnyContinuationContent, hasAnyOverflowContent, hasOverflowContent, header2 as header, isChecked, isFontLoaded, isOverflowField, isOverflowSection, main2 as main, mergeOverflowTextConfig, mergePdfs, mergeStyles, mergeTheme, mmToPt, mmToPx, namespaceClass, namespaceClasses, normalizeOpacity, p2 as p, processOverflowFields, ptToMm, pxToMm, registerSectionRenderer, renderOverflowContinuation, renderOverflowContinuationPage, renderOverflowFirstLine, renderPaginatedHtml, renderPaginatedHtmlSimple, renderSectionTree, renderToHtml, renderToIsolatedFragment, renderToIsolatedHtml, renderToPdf, renderWatermarkHtml, scaleValue, span2 as span, styleToString, table2 as table, tbody2 as tbody, td2 as td, th2 as th, thead3 as thead, tr3 as tr, usePrintPagination, validatePageBreakResult, waitForFonts, when };
4444
5932
  //# sourceMappingURL=node.js.map
4445
5933
  //# sourceMappingURL=node.js.map