@qianxiang-ai/fu-report-viewer-engine 1.1.11 → 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;AAyKD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,SAAS,kBAAkB,EAAE,CA4B5F"}
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"}
@@ -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 rowCount = clamp(Math.max(1, ...expanded.map(row => row.outputRow + 1), areaEnd(document, 'eri') + 1 + expandedRowShift), 1, MAX_TRACKS);
45
- const columnCount = clamp(Math.max(1, maxCellColumn(expanded) + 1, areaEnd(document, 'eci') + 1), 1, 2048);
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
- const style = normalizeStyle(styles[integer(cell.style, -1)] ?? object(cell.style));
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 = rows.reduce((sum, track) => sum + track.size, 0);
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(rows, object(document.printConfig)),
112
- frozen: freezePoint(string(document.freeze, ''), rows, columns)
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|#39);/g, entity => ({
311
- '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#39;': "'"
312
- })[entity] ?? entity);
357
+ return value.replace(/&(?:amp|lt|gt|quot|apos|nbsp|#(?:\d+|x[0-9a-f]+));/gi, entity => {
358
+ const named = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&apos;': "'", '&nbsp;': '\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
- return [...unique.values()];
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.11",
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.11"
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",