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