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