@qianxiang-ai/fu-report-viewer-engine 1.1.10 → 1.1.12
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-converter.d.ts","sourceRoot":"","sources":["../src/legacy-converter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,KAAK,EAMV,uBAAuB,EAKvB,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAA;AA4BnB,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,uBAA4B,GACpC,oBAAoB,CAgBtB;
|
|
1
|
+
{"version":3,"file":"legacy-converter.d.ts","sourceRoot":"","sources":["../src/legacy-converter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAC1E,OAAO,KAAK,EAMV,uBAAuB,EAKvB,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAA;AA4BnB,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE,uBAA4B,GACpC,oBAAoB,CAgBtB;AA0ND,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,SAAS,kBAAkB,EAAE,CA4B5F"}
|
package/dist/legacy-converter.js
CHANGED
|
@@ -41,8 +41,19 @@ function convertSheet(source, options) {
|
|
|
41
41
|
const defaultColumnWidth = clamp(number(options.defaultColumnWidth, 100), 24, 1000);
|
|
42
42
|
const expanded = expandRows(object(document.rows), options.datasets ?? {}, defaultRowHeight);
|
|
43
43
|
const expandedRowShift = Math.max(0, ...expanded.map(row => row.outputRow - row.sourceRow));
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
const sourceToOutput = new Map();
|
|
45
|
+
expanded.forEach(row => {
|
|
46
|
+
if (!sourceToOutput.has(row.sourceRow))
|
|
47
|
+
sourceToOutput.set(row.sourceRow, row.outputRow);
|
|
48
|
+
});
|
|
49
|
+
const merges = mergeRanges(document, expanded, sourceToOutput);
|
|
50
|
+
const layerSources = arrays(document.imgList, document.images, document.chartList, document.charts, document.barcodeList, document.qrcodeList, document.richTextList);
|
|
51
|
+
const rowCount = clamp(Math.max(1, ...expanded.map(row => row.outputRow + 1), areaEnd(document, 'eri') + 1 + expandedRowShift, ...merges.map(merge => merge.row + merge.rowspan), ...layerSources.map(raw => {
|
|
52
|
+
const item = object(raw);
|
|
53
|
+
const row = integer(item.row, 0);
|
|
54
|
+
return (sourceToOutput.get(row) ?? row) + clamp(integer(item.rowspan, 1), 1, 10_000);
|
|
55
|
+
})), 1, MAX_TRACKS);
|
|
56
|
+
const columnCount = clamp(Math.max(1, maxCellColumn(expanded) + 1, areaEnd(document, 'eci') + 1, ...merges.map(merge => merge.column + merge.colspan)), 1, 2048);
|
|
46
57
|
const hidden = object(document.hidden);
|
|
47
58
|
const hiddenRows = integerSet(hidden.rows);
|
|
48
59
|
const hiddenColumns = integerSet(hidden.cols);
|
|
@@ -52,15 +63,13 @@ function convertSheet(source, options) {
|
|
|
52
63
|
}, hiddenRows);
|
|
53
64
|
const columnsSource = object(document.cols);
|
|
54
65
|
const columns = tracks(columnCount, defaultColumnWidth, index => number(object(columnsSource[String(index)]).width, defaultColumnWidth), hiddenColumns);
|
|
55
|
-
const sourceToOutput = new Map();
|
|
56
|
-
expanded.forEach(row => {
|
|
57
|
-
if (!sourceToOutput.has(row.sourceRow))
|
|
58
|
-
sourceToOutput.set(row.sourceRow, row.outputRow);
|
|
59
|
-
});
|
|
60
|
-
const merges = mergeRanges(document, expanded, sourceToOutput);
|
|
61
66
|
const covered = coveredCells(merges);
|
|
62
67
|
const styles = Array.isArray(document.styles) ? document.styles.map(object) : [];
|
|
63
68
|
const cells = [];
|
|
69
|
+
let lastContentRow = Math.max(0, ...merges.filter(merge => {
|
|
70
|
+
const bounds = cellBounds(rows, columns, merge.row, merge.column, merge.rowspan, merge.colspan);
|
|
71
|
+
return bounds.width > 0 && bounds.height > 0;
|
|
72
|
+
}).map(merge => merge.row + merge.rowspan - 1));
|
|
64
73
|
for (const row of expanded) {
|
|
65
74
|
for (const [columnKey, rawCell] of Object.entries(row.cells)) {
|
|
66
75
|
const column = integer(columnKey, -1);
|
|
@@ -71,9 +80,14 @@ function convertSheet(source, options) {
|
|
|
71
80
|
const rowspan = merge?.rowspan ?? 1;
|
|
72
81
|
const colspan = merge?.colspan ?? 1;
|
|
73
82
|
const bounds = cellBounds(rows, columns, row.outputRow, column, rowspan, colspan);
|
|
74
|
-
|
|
83
|
+
if (bounds.width <= 0 || bounds.height <= 0)
|
|
84
|
+
continue;
|
|
85
|
+
const sourceStyle = styles[integer(cell.style, -1)] ?? object(cell.style);
|
|
86
|
+
const style = normalizeStyle(sourceStyle);
|
|
75
87
|
const padding = normalizePadding(cell.padding ?? styles[integer(cell.style, -1)]?.padding);
|
|
76
88
|
const value = resolveCellValue(cell, row, options.datasets ?? {});
|
|
89
|
+
if (hasPrintContent(cell, value.text, sourceStyle, value.richText))
|
|
90
|
+
lastContentRow = Math.max(lastContentRow, row.outputRow + rowspan - 1);
|
|
77
91
|
if (cell.virtual && value.text.trim() === '')
|
|
78
92
|
continue;
|
|
79
93
|
cells.push({
|
|
@@ -94,24 +108,57 @@ function convertSheet(source, options) {
|
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
collapseDynamicGroups(cells, rows);
|
|
97
|
-
const visibleCells = cells.filter(cell => cell.rowspan > 0).map(({ dynamicGroup: _ignored, ...cell }) => cell);
|
|
98
111
|
const layers = convertLayers(document, rows, columns, sourceToOutput);
|
|
112
|
+
// 按展开后的正文和实际图层范围裁尾,原始模板及正文中间的空行保持原位。
|
|
113
|
+
const layerBottom = Math.max(0, ...layers.map(layer => layer.bounds.y + layer.bounds.height));
|
|
114
|
+
if (layerBottom > 0) {
|
|
115
|
+
const end = rows.findIndex(track => track.offset + track.size >= layerBottom);
|
|
116
|
+
lastContentRow = Math.max(lastContentRow, end < 0 ? rows.length - 1 : end);
|
|
117
|
+
}
|
|
118
|
+
const printRows = rows.slice(0, lastContentRow + 1);
|
|
119
|
+
const visibleCells = cells.filter(cell => cell.rowspan > 0 && cell.row < printRows.length)
|
|
120
|
+
.map(({ dynamicGroup: _ignored, ...cell }) => cell);
|
|
99
121
|
const width = columns.reduce((sum, track) => sum + track.size, 0);
|
|
100
|
-
const height =
|
|
122
|
+
const height = Math.max(layerBottom, printRows.reduce((sum, track) => sum + track.size, 0));
|
|
101
123
|
return {
|
|
102
124
|
id: source.id,
|
|
103
125
|
name: source.name,
|
|
104
126
|
order: source.order,
|
|
105
127
|
width,
|
|
106
128
|
height,
|
|
107
|
-
rows,
|
|
129
|
+
rows: printRows,
|
|
108
130
|
columns,
|
|
109
131
|
cells: visibleCells,
|
|
110
132
|
layers,
|
|
111
|
-
pages: paginate(
|
|
112
|
-
frozen: freezePoint(string(document.freeze, ''),
|
|
133
|
+
pages: paginate(printRows, object(document.printConfig), height),
|
|
134
|
+
frozen: freezePoint(string(document.freeze, ''), printRows, columns)
|
|
113
135
|
};
|
|
114
136
|
}
|
|
137
|
+
/** 字体或白底样式不独立占页;显式版式占位和可见样式仍须保留。 */
|
|
138
|
+
function hasPrintContent(cell, text, style, richText) {
|
|
139
|
+
if ((richText === undefined ? text.trim() : hasRichPrintContent(richText)) || cell.merge != null
|
|
140
|
+
|| [cell.completeBlankStatus, cell.fixedHead, cell.fixedTail].some(printFlagEnabled))
|
|
141
|
+
return true;
|
|
142
|
+
if (['virtual', 'rendered', 'lineStart', 'fillForm'].some(key => {
|
|
143
|
+
if (key === 'rendered' && richText !== undefined)
|
|
144
|
+
return false;
|
|
145
|
+
const value = cell[key];
|
|
146
|
+
return value != null && value !== false && value !== 0 && String(value).trim() !== '';
|
|
147
|
+
}))
|
|
148
|
+
return true;
|
|
149
|
+
return hasPrintBackground(style.bgcolor ?? style.background) || Object.keys(object(style.border)).length > 0;
|
|
150
|
+
}
|
|
151
|
+
function printFlagEnabled(value) {
|
|
152
|
+
return value === true || value === 1 || value === 'true' || value === '1';
|
|
153
|
+
}
|
|
154
|
+
function hasRichPrintContent(nodes = []) {
|
|
155
|
+
return nodes.some(node => node.type === 'text' ? node.text.trim() !== ''
|
|
156
|
+
: node.type !== 'lineBreak' && (hasRichPrintContent(node.children) || hasPrintBackground(node.style?.background ?? node.style?.backgroundColor)));
|
|
157
|
+
}
|
|
158
|
+
function hasPrintBackground(value) {
|
|
159
|
+
const background = string(value, '').replace(/\s/g, '').toLowerCase();
|
|
160
|
+
return !['', '#fff', '#ffffff', 'white', 'transparent', 'rgb(255,255,255)', 'rgba(255,255,255,1)'].includes(background);
|
|
161
|
+
}
|
|
115
162
|
function expandRows(rows, datasets, defaultHeight) {
|
|
116
163
|
const sourceIndexes = Object.keys(rows).map(value => integer(value, -1))
|
|
117
164
|
.filter(value => value >= 0).sort((a, b) => a - b);
|
|
@@ -307,9 +354,14 @@ function safeRichTextStyleValue(key, value) {
|
|
|
307
354
|
return /^(?:-?\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw)?|auto)(?:\s+(?:-?\d+(?:\.\d+)?(?:px|em|rem|%|vh|vw)?|auto)){0,3}$/.test(value);
|
|
308
355
|
}
|
|
309
356
|
function decodeHtmlEntities(value) {
|
|
310
|
-
return value.replace(/&(?:amp|lt|gt|quot|#
|
|
311
|
-
'&': '&', '<': '<', '>': '>', '"': '"', '
|
|
312
|
-
|
|
357
|
+
return value.replace(/&(?:amp|lt|gt|quot|apos|nbsp|#(?:\d+|x[0-9a-f]+));/gi, entity => {
|
|
358
|
+
const named = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'", ' ': '\u00a0' };
|
|
359
|
+
if (named[entity.toLowerCase()] !== undefined)
|
|
360
|
+
return named[entity.toLowerCase()];
|
|
361
|
+
const hex = entity.slice(0, 3).toLowerCase() === '&#x';
|
|
362
|
+
const point = Number.parseInt(entity.slice(hex ? 3 : 2, -1), hex ? 16 : 10);
|
|
363
|
+
return point > 0 && point <= 0x10ffff && !(point >= 0xd800 && point <= 0xdfff) ? String.fromCodePoint(point) : entity;
|
|
364
|
+
});
|
|
313
365
|
}
|
|
314
366
|
function mergeRanges(document, rows, sourceToOutput) {
|
|
315
367
|
const result = [];
|
|
@@ -334,7 +386,14 @@ function mergeRanges(document, rows, sourceToOutput) {
|
|
|
334
386
|
}
|
|
335
387
|
}
|
|
336
388
|
const unique = new Map(result.map(item => [`${item.row}:${item.column}`, item]));
|
|
337
|
-
|
|
389
|
+
const visible = [];
|
|
390
|
+
// 覆盖格的历史合并不是可见锚点,不能扩展打印范围或遮挡后续单元格。
|
|
391
|
+
for (const merge of [...unique.values()].sort((a, b) => a.row - b.row || a.column - b.column)) {
|
|
392
|
+
if (!visible.some(parent => parent.row <= merge.row && parent.row + parent.rowspan > merge.row
|
|
393
|
+
&& parent.column <= merge.column && parent.column + parent.colspan > merge.column))
|
|
394
|
+
visible.push(merge);
|
|
395
|
+
}
|
|
396
|
+
return visible;
|
|
338
397
|
}
|
|
339
398
|
function coveredCells(merges) {
|
|
340
399
|
const covered = new Set();
|
|
@@ -462,7 +521,7 @@ function normalizeChartType(value) {
|
|
|
462
521
|
return value;
|
|
463
522
|
return 'bar';
|
|
464
523
|
}
|
|
465
|
-
function paginate(rows, config) {
|
|
524
|
+
function paginate(rows, config, contentHeight) {
|
|
466
525
|
const paperHeightMm = string(config.layout, 'portrait') === 'landscape'
|
|
467
526
|
? number(config.width, 297) : number(config.height, 297);
|
|
468
527
|
const margin = clamp(number(config.marginY, 10), 0, paperHeightMm / 3);
|
|
@@ -478,6 +537,12 @@ function paginate(rows, config) {
|
|
|
478
537
|
firstRow = lastRow + 1;
|
|
479
538
|
top = rows[firstRow]?.offset ?? rows.reduce((sum, row) => sum + row.size, 0);
|
|
480
539
|
}
|
|
540
|
+
// 悬浮图层可以超过最后一行;继续分页但不向表格模型插入不存在的数据行。
|
|
541
|
+
while (pages.length && pages[pages.length - 1].top + pageHeight < contentHeight) {
|
|
542
|
+
const previous = pages[pages.length - 1];
|
|
543
|
+
pages.push({ index: pages.length, top: previous.top + pageHeight, height: pageHeight,
|
|
544
|
+
firstRow: rows.length - 1, lastRow: rows.length - 1 });
|
|
545
|
+
}
|
|
481
546
|
return pages;
|
|
482
547
|
}
|
|
483
548
|
function freezePoint(value, rows, columns) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qianxiang-ai/fu-report-viewer-engine",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"author": "位志国 <ijunfu@aliyun.com>",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"registry": "https://registry.npmjs.org/"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@qianxiang-ai/fu-report-contracts": "1.1.
|
|
23
|
+
"@qianxiang-ai/fu-report-contracts": "1.1.12"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json",
|