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