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/README.md +243 -360
- package/README.zh-CN.md +248 -365
- package/dist/index.cjs +1553 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1073 -13
- package/dist/index.d.ts +1073 -13
- package/dist/index.js +1533 -45
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +1553 -46
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1073 -13
- package/dist/node.d.ts +1073 -13
- package/dist/node.js +1533 -45
- package/dist/node.js.map +1 -1
- package/package.json +22 -12
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)}">${
|
|
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
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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>${
|
|
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
|
|
1800
|
+
const headerHtml = includeHeader ? renderTableHeader(config, options) : "";
|
|
1575
1801
|
return `<div class="${cls("print-section", options)} ${cls("data-table", options)}">
|
|
1576
1802
|
<table>
|
|
1577
|
-
|
|
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"
|
|
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"
|
|
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(
|
|
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
|
|
2829
|
+
var thead3 = () => new HtmlElementBuilder("thead");
|
|
2599
2830
|
var tbody2 = () => new HtmlElementBuilder("tbody");
|
|
2600
|
-
var
|
|
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
|
-
|
|
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
|
|
5099
|
+
// Table row rendering is handled by renderTableRows function
|
|
4080
5100
|
"table-row": () => "",
|
|
4081
|
-
// Table headers are
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4118
|
-
if (
|
|
4119
|
-
|
|
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
|
|
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,
|
|
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
|