reamkit 1.15.4 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +659 -7
- package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
- package/dist/esm/core/drawingml/chart-geometry.js +424 -92
- package/dist/esm/core/drawingml/chart-parser.js +301 -11
- package/dist/esm/core/drawingml/colors.d.ts +7 -7
- package/dist/esm/core/drawingml/colors.js +84 -18
- package/dist/esm/core/drawingml/preset-geometry.js +240 -6
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
- package/dist/esm/core/drawingml/theme-parser.js +92 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +353 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/ir/sheet.d.ts +61 -0
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/vector.d.ts +30 -0
- package/dist/esm/excel/activex-parser.js +1 -0
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +899 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +553 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +150 -61
- package/dist/esm/excel/xlsx-reader.js +242 -11
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +29 -6
- package/dist/esm/layout/page-doc.d.ts +87 -1
- package/dist/esm/layout/styled-layout.d.ts +46 -2
- package/dist/esm/layout/styled-layout.js +2345 -274
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +355 -49
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +31 -2
- package/dist/esm/pdf/vector-graphics.js +103 -3
- package/dist/esm/pptx/pptx-reader.js +2 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +98 -24
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +538 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +86 -5
- package/dist/esm/word/drawing-parser.js +1590 -54
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +108 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +32 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +9 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -1,10 +1,67 @@
|
|
|
1
1
|
import { eighthPtToPt, halfPtToPt, pt, twipsToPt } from "../core/ir/units.js";
|
|
2
2
|
import { FEATURES } from "../core/ir/features.js";
|
|
3
|
+
import { applyNumberFormat, generalToWidth, numberFormatColorHex } from "../core/number-format.js";
|
|
3
4
|
import { parseAreaRef, parseTitleRowRange } from "./defined-name-ref.js";
|
|
4
|
-
import { applyNumberFormat } from "./number-format.js";
|
|
5
5
|
import "./index.js";
|
|
6
|
-
import { bandedTables, computeColumnBands } from "./column-bands.js";
|
|
6
|
+
import { bandedTables, computeColumnBands, withHeadingBand } from "./column-bands.js";
|
|
7
7
|
import { buildConditionalFormatter } from "./conditional-format.js";
|
|
8
|
+
/** The point size {@link TWIPS_PER_EXCEL_CHAR} was measured at (Excel's default). */
|
|
9
|
+
var DEFAULT_FONT_PT = 11;
|
|
10
|
+
/**
|
|
11
|
+
* How far past the content a value-less but PAINTED cell may push the used
|
|
12
|
+
* range. A legend or a colour key sits a handful of columns off the data; a
|
|
13
|
+
* whole-row style reaches column XFD. The distance is what tells them apart.
|
|
14
|
+
*/
|
|
15
|
+
var PAINT_REACH_COLUMNS = 64;
|
|
16
|
+
var PAINT_REACH_ROWS = 512;
|
|
17
|
+
/** One screen pixel at 96 DPI, in twips — the unit Excel's width formula works in. */
|
|
18
|
+
var TWIPS_PER_PIXEL = 15;
|
|
19
|
+
/**
|
|
20
|
+
* §18.3.1.13 — a `<col width>` in twips.
|
|
21
|
+
*
|
|
22
|
+
* The padding above is the formula for a column at least one character wide.
|
|
23
|
+
* Excel documents a SEPARATE one below that: `px = Trunc(width × MDW + 0.5)`,
|
|
24
|
+
* with no padding at all. It matters because the padding is a constant: on a
|
|
25
|
+
* form drawn over a fine grid — tdf118668.xlsx rules 168 columns of 0.855
|
|
26
|
+
* characters — 3.75pt of padding nearly doubles a 4.5pt column, and the sheet
|
|
27
|
+
* came out 1384pt wide against the reference's 754, so it paginated across two
|
|
28
|
+
* pages where every reader prints one.
|
|
29
|
+
*
|
|
30
|
+
* @param chars The declared width in characters.
|
|
31
|
+
* @param charTwips The Maximum Digit Width, in twips.
|
|
32
|
+
* @returns The rendered column width in twips.
|
|
33
|
+
*/
|
|
34
|
+
function columnTwips(chars, charTwips) {
|
|
35
|
+
return columnTwipsOf(chars, charTwips);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* §18.3.1.81 — the DEFAULT column, in twips, from whichever of the two the
|
|
39
|
+
* sheet declares.
|
|
40
|
+
*
|
|
41
|
+
* The two are not the same number. `defaultColWidth` "includes margin padding
|
|
42
|
+
* and extra padding for gridlines"; `baseColWidth` is the bare character count
|
|
43
|
+
* and explicitly excludes them, so deriving a default from it adds the padding
|
|
44
|
+
* once to reach a defaultColWidth and once more to render it. 47668.xlsx says
|
|
45
|
+
* `baseColWidth="10"` and caches its picture's extent at 9753600 EMU = 768pt
|
|
46
|
+
* over 12 columns plus 48pt — 60pt, or 80px, a column. Padding it once gives
|
|
47
|
+
* 75px and squeezed that picture by 6%. LibreOffice measures ~79px here, so
|
|
48
|
+
* both references agree against us.
|
|
49
|
+
*
|
|
50
|
+
* @param worksheet The sheet.
|
|
51
|
+
* @param charTwips The Maximum Digit Width, in twips.
|
|
52
|
+
* @param fallbackChars The width to use when the sheet declares neither.
|
|
53
|
+
* @returns The default column width in twips.
|
|
54
|
+
*/
|
|
55
|
+
function defaultColumnTwips(worksheet, charTwips, fallbackChars) {
|
|
56
|
+
if (worksheet.defaultColWidthChars !== void 0) return columnTwipsOf(worksheet.defaultColWidthChars, charTwips);
|
|
57
|
+
if (worksheet.baseColWidthChars !== void 0) return columnTwipsOf(worksheet.baseColWidthChars, charTwips) + 150;
|
|
58
|
+
return columnTwipsOf(fallbackChars, charTwips);
|
|
59
|
+
}
|
|
60
|
+
function columnTwipsOf(chars, charTwips) {
|
|
61
|
+
const mdwPx = charTwips / TWIPS_PER_PIXEL;
|
|
62
|
+
if (chars >= 1 && mdwPx > 0) return Math.trunc((256 * chars + Math.trunc(128 / mdwPx)) / 256 * mdwPx) * TWIPS_PER_PIXEL;
|
|
63
|
+
return Math.trunc(chars * charTwips / TWIPS_PER_PIXEL + .5) * TWIPS_PER_PIXEL;
|
|
64
|
+
}
|
|
8
65
|
/**
|
|
9
66
|
* Excel insets a cell's text by ~2 px each side (1.5 pt at 96 DPI). The layout
|
|
10
67
|
* engine's default is a word processor's 108 twips (5.4 pt), which is nearly
|
|
@@ -12,12 +69,106 @@ import { buildConditionalFormatter } from "./conditional-format.js";
|
|
|
12
69
|
*/
|
|
13
70
|
var EXCEL_CELL_INSET_PT = 1.5;
|
|
14
71
|
/**
|
|
72
|
+
* The width behind {@link DEFAULT_COL_TWIPS}, for a non-Calibri unit.
|
|
73
|
+
*
|
|
74
|
+
* In the STORED unit, which is what {@link columnTwips} reads: Excel's default
|
|
75
|
+
* column is "8.43 characters" in its own interface and `width="9.140625"` in
|
|
76
|
+
* every file that writes it out, because the stored form carries the padding.
|
|
77
|
+
*/
|
|
78
|
+
var DEFAULT_COL_CHARS = 9.140625;
|
|
79
|
+
/**
|
|
80
|
+
* The column-width unit in twips: the Maximum Digit Width of the font the
|
|
81
|
+
* document is rendered in (§18.3.1.13), or Excel's own 7 px when the caller has
|
|
82
|
+
* not measured one.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* The digit advance, as a fraction of the em, of the faces a spreadsheet names
|
|
86
|
+
* for its normal style.
|
|
87
|
+
*
|
|
88
|
+
* Read off each face's `hmtx` for `0`–`9`; the metric-compatible substitutes
|
|
89
|
+
* are listed beside their originals because a file naming one is measured in
|
|
90
|
+
* the other's unit by every reader that has to substitute.
|
|
91
|
+
*
|
|
92
|
+
* Every entry here is measured. The CJK defaults are deliberately absent: the
|
|
93
|
+
* evidence says MS Pゴシック 11 is 8px — Japanese Excel's default column is
|
|
94
|
+
* 72px, and §18.3.1.13's forward formula turns that into exactly the
|
|
95
|
+
* `width="9"` 54524.xlsx writes for its default band — but I have no copy of
|
|
96
|
+
* the face to measure, and fitting 0.55 to it moved 50299.xlsx from 16 pages
|
|
97
|
+
* to 22 against LibreOffice's 17. A face we cannot measure keeps the old 7px.
|
|
98
|
+
*/
|
|
99
|
+
var DIGIT_EM = [
|
|
100
|
+
[/^(calibri|carlito)$/i, 1063 / 2048],
|
|
101
|
+
[/^(arial|helvetica|liberation sans|arimo|arial unicode ms)$/i, 1139 / 2048],
|
|
102
|
+
[/^(times new roman|liberation serif|tinos)$/i, 1024 / 2048],
|
|
103
|
+
[/^(tahoma)$/i, 1118 / 2048],
|
|
104
|
+
[/^(verdana|dejavu sans)$/i, 1303 / 2048]
|
|
105
|
+
];
|
|
106
|
+
/**
|
|
107
|
+
* §18.3.1.13 — the Maximum Digit Width, in twips, of the normal style's font.
|
|
108
|
+
*
|
|
109
|
+
* "The number of characters of the maximum digit width of the numbers 0, 1,
|
|
110
|
+
* 2, …, 9 **as rendered in the normal style's font**" — the unit is bound to
|
|
111
|
+
* that face at that size, and Excel takes it in whole screen pixels. This used
|
|
112
|
+
* to test only whether the file NAMED a font and hand every named one Excel's
|
|
113
|
+
* 7px: right for the Calibri 11 and Arial 10 the note below cites, wrong for
|
|
114
|
+
* anything else. 55850.xlsx and 55927.xlsx are Arial ELEVEN, whose digit is
|
|
115
|
+
* 8px, so their columns came out 14% narrow and both files lost the last
|
|
116
|
+
* character of every date. A face we do not know keeps the old 7px rather than
|
|
117
|
+
* a guess.
|
|
118
|
+
*
|
|
119
|
+
* @param name The normal style's font name.
|
|
120
|
+
* @param sizePt Its size in points; 11 when the file omits one.
|
|
121
|
+
* @returns The Maximum Digit Width in twips.
|
|
122
|
+
*/
|
|
123
|
+
function maximumDigitTwips(name, sizePt) {
|
|
124
|
+
const size = sizePt !== void 0 && Number.isFinite(sizePt) && sizePt > 0 ? sizePt : 11;
|
|
125
|
+
const em = DIGIT_EM.find(([re]) => re.test(name.trim()))?.[1];
|
|
126
|
+
if (em === void 0) return 105;
|
|
127
|
+
const px = Math.trunc(em * size * (96 / 72));
|
|
128
|
+
return px > 0 ? px * TWIPS_PER_PIXEL : 105;
|
|
129
|
+
}
|
|
130
|
+
function digitTwips(digitWidthPt) {
|
|
131
|
+
if (digitWidthPt === void 0 || !Number.isFinite(digitWidthPt) || digitWidthPt <= 0) return 105;
|
|
132
|
+
return digitWidthPt * TWIPS_PER_POINT;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
15
135
|
* ECMA-376 §18.3.1.81 — the row height Excel uses when a sheet declares no
|
|
16
136
|
* `<sheetFormatPr defaultRowHeight>`: 15pt, the line height of its default
|
|
17
137
|
* theme font (Calibri 11). Independent of whatever font we end up rendering
|
|
18
138
|
* with — the height belongs to the document, not to the typesetter.
|
|
19
139
|
*/
|
|
20
140
|
var EXCEL_DEFAULT_ROW_HEIGHT_PT = 15;
|
|
141
|
+
/**
|
|
142
|
+
* The same height for a workbook whose Normal style names another size —
|
|
143
|
+
* Excel's own table, which is not proportional because the height is a line
|
|
144
|
+
* height and a font's leading is not proportional to its body.
|
|
145
|
+
*
|
|
146
|
+
* A workbook written before Calibri says Arial 10 and no `defaultRowHeight`,
|
|
147
|
+
* and every one of its rows is 12.75pt: tdf100709.xlsx fits twenty of them on
|
|
148
|
+
* the page where our 15pt fitted fourteen and pushed the rest onto a second.
|
|
149
|
+
*/
|
|
150
|
+
var EXCEL_ROW_HEIGHT_BY_FONT_PT = new Map([
|
|
151
|
+
[8, 11],
|
|
152
|
+
[9, 12],
|
|
153
|
+
[10, 12.75],
|
|
154
|
+
[11, 15],
|
|
155
|
+
[12, 15.75],
|
|
156
|
+
[14, 18]
|
|
157
|
+
]);
|
|
158
|
+
/**
|
|
159
|
+
* The row height a sheet that declares none inherits from the workbook's
|
|
160
|
+
* Normal font. Sizes outside Excel's table scale from its 10pt entry and snap
|
|
161
|
+
* up to the 0.75pt grid row heights are stored on (one pixel at 96 dpi).
|
|
162
|
+
*
|
|
163
|
+
* @param fontPt The Normal style's font size, or undefined if unknown.
|
|
164
|
+
* @returns The default row height in points.
|
|
165
|
+
*/
|
|
166
|
+
function defaultRowHeightPtFor(fontPt) {
|
|
167
|
+
if (fontPt === void 0 || !Number.isFinite(fontPt) || fontPt <= 0) return EXCEL_DEFAULT_ROW_HEIGHT_PT;
|
|
168
|
+
const known = EXCEL_ROW_HEIGHT_BY_FONT_PT.get(fontPt);
|
|
169
|
+
if (known !== void 0) return known;
|
|
170
|
+
return Math.ceil(fontPt * 12.75 / 10 / .75) * .75;
|
|
171
|
+
}
|
|
21
172
|
var TWIPS_PER_POINT = 20;
|
|
22
173
|
var TWIPS_PER_INCH = 1440;
|
|
23
174
|
var PAPER_SIZES_TWIPS = new Map([
|
|
@@ -29,7 +180,7 @@ var PAPER_SIZES_TWIPS = new Map([
|
|
|
29
180
|
[11, [8392, 11906]],
|
|
30
181
|
[70, [5953, 8392]]
|
|
31
182
|
]);
|
|
32
|
-
var DEFAULT_PAPER_TWIPS = [
|
|
183
|
+
var DEFAULT_PAPER_TWIPS = [11900, 16840];
|
|
33
184
|
/**
|
|
34
185
|
* Build the page section (paper size + margins) from a worksheet's `<pageSetup>`
|
|
35
186
|
* / `<pageMargins>`.
|
|
@@ -102,6 +253,57 @@ function resolvePrintTitleRows(definedNames, sheetIdx) {
|
|
|
102
253
|
for (const dn of definedNames) if (dn.name === PRINT_TITLES_NAME && dn.localSheetId === sheetIdx) return parseTitleRowRange(dn.value);
|
|
103
254
|
}
|
|
104
255
|
var MIN_PRINT_SCALE = .1;
|
|
256
|
+
/**
|
|
257
|
+
* The width one page has for content: the sheet's paper (declared or the A4
|
|
258
|
+
* default) less its left and right margins, in points. The same measure the
|
|
259
|
+
* column bands are packed into — a drawing layer banded on any other width
|
|
260
|
+
* would not line up with the grid beside it.
|
|
261
|
+
*/
|
|
262
|
+
function printableWidthPt(worksheet) {
|
|
263
|
+
return sheetContentWidthTwips(worksheet) / TWIPS_PER_POINT;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Whether a sheet paginates ACROSS its columns.
|
|
267
|
+
*
|
|
268
|
+
* Fit-to-ONE-page does not: the whole grid is squeezed onto a single page
|
|
269
|
+
* width, and Excel lets that override even a manual column break. An explicit
|
|
270
|
+
* `<pageSetup scale>` is a different thing entirely — §18.3.1.63 makes it a
|
|
271
|
+
* plain zoom, and Excel paginates AFTER applying it. Treating the two alike
|
|
272
|
+
* left 47737.xlsx (`scale="63"`, still 583pt wide) as one 583pt table on a
|
|
273
|
+
* 487pt page: its last column fell off the paper and its "Heading" with it.
|
|
274
|
+
*
|
|
275
|
+
* @param worksheet The sheet.
|
|
276
|
+
* @param fitWide `fitToWidth`, or 1 when the sheet is not fit-to-page.
|
|
277
|
+
* @returns True when the grid should be split into column bands.
|
|
278
|
+
*/
|
|
279
|
+
function bandsAcross(worksheet, fitWide) {
|
|
280
|
+
return !worksheet.fitToPage || fitWide > 1;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The manual breaks (§18.3.1.74/§18.3.1.14) that still apply on one axis.
|
|
284
|
+
*
|
|
285
|
+
* Fit-to-page and a manual break are two answers to the same question, and
|
|
286
|
+
* Excel takes the scaling one: turn on "fit to N pages" and the breaks drawn
|
|
287
|
+
* into the sheet stop dividing it. Honouring both asks for the impossible —
|
|
288
|
+
* a break costs a page at EVERY scale, so `fitToHeight="1"` beside one of them
|
|
289
|
+
* can never be satisfied, and the fit search would shrink toward its floor
|
|
290
|
+
* chasing a page count it will never reach. sheet-fit-breaks.xlsx says fit on
|
|
291
|
+
* one page and breaks after row 20; the reference prints all forty rows on one
|
|
292
|
+
* page and we printed two.
|
|
293
|
+
*
|
|
294
|
+
* An explicit `0` on the axis means "as many pages as it takes" — nothing is
|
|
295
|
+
* being fitted there, so the breaks on it stand.
|
|
296
|
+
*
|
|
297
|
+
* @param worksheet The sheet, for its `fitToPage` flag and page setup.
|
|
298
|
+
* @param axis Which axis's breaks are wanted.
|
|
299
|
+
* @returns The break indices to honour; empty when fit-to-page overrides them.
|
|
300
|
+
*/
|
|
301
|
+
function honouredBreaks(worksheet, axis) {
|
|
302
|
+
const breaks = (axis === "rows" ? worksheet.rowBreaks : worksheet.colBreaks) ?? [];
|
|
303
|
+
if (!worksheet.fitToPage) return breaks;
|
|
304
|
+
const setup = worksheet.pageSetup;
|
|
305
|
+
return ((axis === "rows" ? setup?.fitToHeight : setup?.fitToWidth) ?? 1) >= 1 ? [] : breaks;
|
|
306
|
+
}
|
|
105
307
|
function sheetContentWidthTwips(worksheet) {
|
|
106
308
|
const pageSize = pageSizeFromSetup(worksheet.pageSetup);
|
|
107
309
|
const pageWidthTwips = pageSize ? Math.round(pageSize.width * 20) : DEFAULT_PAPER_TWIPS[0];
|
|
@@ -110,6 +312,17 @@ function sheetContentWidthTwips(worksheet) {
|
|
|
110
312
|
const right = Math.round(margins.right * 20);
|
|
111
313
|
return Math.max(TWIPS_PER_INCH / 2, pageWidthTwips - left - right);
|
|
112
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* The printable HEIGHT of one page of this sheet, in points — its paper height
|
|
317
|
+
* less its own top and bottom margins. The vertical twin of
|
|
318
|
+
* {@link printableWidthPt}.
|
|
319
|
+
*
|
|
320
|
+
* @param worksheet The parsed worksheet (paper + margins).
|
|
321
|
+
* @returns The height a page of this sheet has for content, in points.
|
|
322
|
+
*/
|
|
323
|
+
function printableHeightPt(worksheet) {
|
|
324
|
+
return sheetContentHeightTwips(worksheet) / TWIPS_PER_POINT;
|
|
325
|
+
}
|
|
113
326
|
function sheetContentHeightTwips(worksheet) {
|
|
114
327
|
const pageSize = pageSizeFromSetup(worksheet.pageSetup);
|
|
115
328
|
const pageHeightTwips = pageSize ? Math.round(pageSize.height * 20) : DEFAULT_PAPER_TWIPS[1];
|
|
@@ -118,27 +331,73 @@ function sheetContentHeightTwips(worksheet) {
|
|
|
118
331
|
const bottom = Math.round(margins.bottom * 20);
|
|
119
332
|
return Math.max(TWIPS_PER_INCH / 2, pageHeightTwips - top - bottom);
|
|
120
333
|
}
|
|
121
|
-
|
|
334
|
+
/**
|
|
335
|
+
* How many pages the grid ACTUALLY paginates into at a given scale — the same
|
|
336
|
+
* greedy fill the layout performs, run over one axis.
|
|
337
|
+
*
|
|
338
|
+
* A row does not split, so the closed form `contentHeight × fitToHeight / total`
|
|
339
|
+
* is a lower bound and not an answer: whatever height the last row that will not
|
|
340
|
+
* fit leaves unused is a page's worth of slack the formula never accounts for,
|
|
341
|
+
* and print titles make it worse by consuming their height again on every page.
|
|
342
|
+
*/
|
|
343
|
+
function pageCountAtScale(extents, forcedBreaks, repeatTwips, limitTwips, scale) {
|
|
344
|
+
let pages = 1;
|
|
345
|
+
let used = 0;
|
|
346
|
+
for (let i = 0; i < extents.length; i++) {
|
|
347
|
+
const extent = extents[i] * scale;
|
|
348
|
+
if (used > 0 && (forcedBreaks.has(i) || used + extent > limitTwips)) {
|
|
349
|
+
pages++;
|
|
350
|
+
used = repeatTwips * scale;
|
|
351
|
+
}
|
|
352
|
+
used += extent;
|
|
353
|
+
}
|
|
354
|
+
return pages;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The largest whole percentage at or below `closed` that paginates into at most
|
|
358
|
+
* `maxPages` pages. Whole percentages because that is the unit Excel searches in
|
|
359
|
+
* and stores the answer in (`<pageSetup scale="51">`), so the two agree exactly.
|
|
360
|
+
* Monotone — shrinking never adds a page — so a binary search is enough.
|
|
361
|
+
*/
|
|
362
|
+
function searchFitScale(closed, maxPages, pageCount) {
|
|
363
|
+
const floor = Math.round(MIN_PRINT_SCALE * 100);
|
|
364
|
+
let hi = Math.min(100, Math.floor(closed * 100));
|
|
365
|
+
if (hi <= floor) return Math.max(MIN_PRINT_SCALE, closed);
|
|
366
|
+
if (pageCount(hi / 100) <= maxPages) return hi / 100;
|
|
367
|
+
let lo = floor;
|
|
368
|
+
if (pageCount(lo / 100) > maxPages) return closed;
|
|
369
|
+
while (lo < hi) {
|
|
370
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
371
|
+
if (pageCount(mid / 100) <= maxPages) lo = mid;
|
|
372
|
+
else hi = mid - 1;
|
|
373
|
+
}
|
|
374
|
+
return lo / 100;
|
|
375
|
+
}
|
|
376
|
+
function computePrintScale(worksheet, totalGridTwips, contentWidthTwips, totalGridHeightTwips, contentHeightTwips, geometry) {
|
|
122
377
|
const setup = worksheet.pageSetup;
|
|
123
378
|
let s = 1;
|
|
124
379
|
if (worksheet.fitToPage) {
|
|
125
380
|
const fitW = setup?.fitToWidth ?? 1;
|
|
381
|
+
const fitH = setup?.fitToHeight ?? 1;
|
|
126
382
|
let sW = 1;
|
|
127
383
|
let sH = 1;
|
|
128
384
|
if (fitW >= 1 && totalGridTwips > 0) sW = contentWidthTwips * fitW / totalGridTwips;
|
|
129
|
-
|
|
130
|
-
if (
|
|
385
|
+
if (fitH >= 1 && totalGridHeightTwips > 0) sH = contentHeightTwips * fitH / totalGridHeightTwips;
|
|
386
|
+
if (fitW >= 1 && geometry.colTwips.length > 0) sW = searchFitScale(sW, fitW, (scale) => pageCountAtScale(geometry.colTwips, geometry.colBreaks, 0, contentWidthTwips, scale));
|
|
387
|
+
if (fitH >= 1 && geometry.rowTwips.length > 0) sH = searchFitScale(sH, fitH, (scale) => pageCountAtScale(geometry.rowTwips, geometry.rowBreaks, geometry.titleTwips, contentHeightTwips, scale));
|
|
131
388
|
s = Math.min(sW, sH);
|
|
132
389
|
} else if (setup?.scale !== void 0 && setup.scale > 0) s = setup.scale / 100;
|
|
133
390
|
return Math.min(1, Math.max(MIN_PRINT_SCALE, s));
|
|
134
391
|
}
|
|
135
392
|
function scaleRunFont(props, scale) {
|
|
136
|
-
const
|
|
393
|
+
const base = props.fontSizePt ?? DEFAULT_FONT_PT;
|
|
137
394
|
return {
|
|
138
395
|
...props,
|
|
139
|
-
fontSizePt:
|
|
396
|
+
fontSizePt: pt(Math.max(1, base * scale))
|
|
140
397
|
};
|
|
141
398
|
}
|
|
399
|
+
var PRINT_GRIDLINE_HEX = "C0C0C0";
|
|
400
|
+
var CF_NUMBER_FORMAT_ID = 1000001;
|
|
142
401
|
var MAX_SHEET_TEXT_CHARS = 1e6;
|
|
143
402
|
/**
|
|
144
403
|
* Project one worksheet's grid into Flow body elements — a single {@link Table}
|
|
@@ -158,6 +417,16 @@ var MAX_SHEET_TEXT_CHARS = 1e6;
|
|
|
158
417
|
*/
|
|
159
418
|
function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
160
419
|
if (worksheet.maxRow < 0 || worksheet.maxColumn < 0) return [];
|
|
420
|
+
const charTwipsUnit = styles.fonts[0]?.name === void 0 ? digitTwips(print.digitWidthPt) : maximumDigitTwips(styles.fonts[0].name, styles.fonts[0].sizePt);
|
|
421
|
+
const textTwipsUnit = 105;
|
|
422
|
+
const rowStyleAt = /* @__PURE__ */ new Map();
|
|
423
|
+
for (const r of worksheet.rowStyles ?? []) rowStyleAt.set(r.row, r.styleIndex);
|
|
424
|
+
const columnStyles = worksheet.columnStyles ?? [];
|
|
425
|
+
const defaultStyleAt = (row, col, cellXfs) => {
|
|
426
|
+
const fromRow = rowStyleAt.get(row);
|
|
427
|
+
if (fromRow !== void 0) return cellXfs[fromRow];
|
|
428
|
+
for (const cs of columnStyles) if (col + 1 >= cs.min && col + 1 <= cs.max) return cellXfs[cs.styleIndex];
|
|
429
|
+
};
|
|
161
430
|
let usedRow = -1;
|
|
162
431
|
let usedCol = -1;
|
|
163
432
|
const contentAt = /* @__PURE__ */ new Set();
|
|
@@ -177,7 +446,61 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
177
446
|
if (host.startRow > usedRow) usedRow = host.startRow;
|
|
178
447
|
if (host.startColumn > usedCol) usedCol = host.startColumn;
|
|
179
448
|
}
|
|
180
|
-
if (usedRow
|
|
449
|
+
if (usedRow >= 0 && usedCol >= 0 && print.drawingExtentPt) {
|
|
450
|
+
const wantTwips = Math.round(print.drawingExtentPt.widthPt * TWIPS_PER_POINT);
|
|
451
|
+
const defTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
|
|
452
|
+
const widthAt = (abs) => {
|
|
453
|
+
for (const col of worksheet.columns) {
|
|
454
|
+
if (abs < col.min - 1 || abs > col.max - 1) continue;
|
|
455
|
+
return col.hidden ? 0 : columnTwips(col.widthChars, charTwipsUnit);
|
|
456
|
+
}
|
|
457
|
+
return defTwips;
|
|
458
|
+
};
|
|
459
|
+
let acc = 0;
|
|
460
|
+
for (let col = 0; col < 16384 && acc < wantTwips; col++) {
|
|
461
|
+
acc += widthAt(col);
|
|
462
|
+
if (col > usedCol) usedCol = col;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (usedRow >= 0 && usedCol >= 0) {
|
|
466
|
+
const colReach = usedCol + PAINT_REACH_COLUMNS;
|
|
467
|
+
const rowReach = usedRow + PAINT_REACH_ROWS;
|
|
468
|
+
for (const c of worksheet.cells) {
|
|
469
|
+
if (c.column > colReach || c.row > rowReach) continue;
|
|
470
|
+
if (c.column <= usedCol && c.row <= usedRow) continue;
|
|
471
|
+
if (!cellPaintsSomething(c, styles)) continue;
|
|
472
|
+
if (c.row > usedRow) usedRow = c.row;
|
|
473
|
+
if (c.column > usedCol) usedCol = c.column;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (usedRow < 0 && usedCol < 0) {
|
|
477
|
+
const pageTwips = sheetContentWidthTwips(worksheet);
|
|
478
|
+
const defTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
|
|
479
|
+
let acc = 0;
|
|
480
|
+
let colLimit = 0;
|
|
481
|
+
while (colLimit < PAINT_REACH_COLUMNS && acc < pageTwips) {
|
|
482
|
+
const col = worksheet.columns.find((cc) => colLimit >= cc.min - 1 && colLimit <= cc.max - 1);
|
|
483
|
+
acc += col ? col.hidden ? 0 : columnTwips(col.widthChars, charTwipsUnit) : defTwips;
|
|
484
|
+
colLimit++;
|
|
485
|
+
}
|
|
486
|
+
for (const c of worksheet.cells) {
|
|
487
|
+
if (c.column >= colLimit || c.row >= PAINT_REACH_ROWS) continue;
|
|
488
|
+
if (!cellPaintsVisibly(c, styles)) continue;
|
|
489
|
+
if (c.row > usedRow) usedRow = c.row;
|
|
490
|
+
if (c.column > usedCol) usedCol = c.column;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (usedRow < 0 || usedCol < 0) {
|
|
494
|
+
if (print.scaleSink) print.scaleSink.value = computePrintScale(worksheet, Math.round((print.drawingExtentPt?.widthPt ?? 0) * TWIPS_PER_POINT), sheetContentWidthTwips(worksheet), Math.round((print.drawingExtentPt?.heightPt ?? 0) * TWIPS_PER_POINT), sheetContentHeightTwips(worksheet), {
|
|
495
|
+
rowTwips: [],
|
|
496
|
+
rowBreaks: /* @__PURE__ */ new Set(),
|
|
497
|
+
titleTwips: 0,
|
|
498
|
+
colTwips: [],
|
|
499
|
+
colBreaks: /* @__PURE__ */ new Set()
|
|
500
|
+
});
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
usedCol += overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit);
|
|
181
504
|
let rowStart = 0;
|
|
182
505
|
let colStart = 0;
|
|
183
506
|
let rowEnd = usedRow;
|
|
@@ -190,6 +513,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
190
513
|
}
|
|
191
514
|
if (rowEnd < rowStart || colEnd < colStart) return [];
|
|
192
515
|
const MAX_GRID_COLS = 16384;
|
|
516
|
+
const SHEET_MAX_ROWS = 1048576;
|
|
193
517
|
const MAX_GRID_ROWS = 5e4;
|
|
194
518
|
const MAX_GRID_CELLS = 1e6;
|
|
195
519
|
const wantRows = rowEnd - rowStart + 1;
|
|
@@ -198,21 +522,22 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
198
522
|
let rowCount = Math.max(1, Math.min(wantRows, MAX_GRID_ROWS, Math.floor(MAX_GRID_CELLS / colCount)));
|
|
199
523
|
if (rowCount < wantRows) rowCount = Math.max(1, lastContentRow(worksheet, rowStart, rowCount, colStart, colCount) + 1);
|
|
200
524
|
if (colCount < wantCols) colCount = Math.max(1, lastContentColumn(worksheet, rowStart, rowCount, colStart, colCount) + 1);
|
|
525
|
+
const reason = (want, limit, unit, last) => want > limit ? `the sheet declares cells past ${unit} ${last}, the last SpreadsheetML defines` : "memory guard";
|
|
201
526
|
if (rowCount < wantRows) print.losses?.push({
|
|
202
527
|
severity: "dropped",
|
|
203
528
|
feature: FEATURES.tables,
|
|
204
|
-
detail: `grid clipped to the first ${rowCount} rows of ${wantRows} in the used range (
|
|
529
|
+
detail: `grid clipped to the first ${rowCount} rows of ${wantRows} in the used range (${reason(rowStart + wantRows, SHEET_MAX_ROWS, "row", String(SHEET_MAX_ROWS))})`,
|
|
205
530
|
...print.sheetName ? { where: `sheet "${print.sheetName}"` } : {}
|
|
206
531
|
});
|
|
207
532
|
if (colCount < wantCols) print.losses?.push({
|
|
208
533
|
severity: "dropped",
|
|
209
534
|
feature: FEATURES.tables,
|
|
210
|
-
detail: `grid clipped to the first ${colCount} columns of ${wantCols} in the used range (
|
|
535
|
+
detail: `grid clipped to the first ${colCount} columns of ${wantCols} in the used range (${reason(colStart + wantCols, MAX_GRID_COLS, "column", "XFD")})`,
|
|
211
536
|
...print.sheetName ? { where: `sheet "${print.sheetName}"` } : {}
|
|
212
537
|
});
|
|
213
538
|
const rowWindowEnd = rowStart + rowCount - 1;
|
|
214
539
|
const colWindowEnd = colStart + colCount - 1;
|
|
215
|
-
const defaultRowTwips = Math.round((worksheet.defaultRowHeightPt ??
|
|
540
|
+
const defaultRowTwips = Math.round((worksheet.defaultRowHeightPt ?? defaultRowHeightPtFor(print.defaultFontPt)) * TWIPS_PER_POINT);
|
|
216
541
|
const rowHeightMap = /* @__PURE__ */ new Map();
|
|
217
542
|
for (let r = 0; r < rowCount; r++) rowHeightMap.set(r, {
|
|
218
543
|
heightTwips: defaultRowTwips,
|
|
@@ -224,7 +549,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
224
549
|
const heightTwips = Math.round(h.heightPt * TWIPS_PER_POINT);
|
|
225
550
|
rowHeightMap.set(local, {
|
|
226
551
|
heightTwips,
|
|
227
|
-
heightRule: "atLeast"
|
|
552
|
+
heightRule: h.customHeight ? "exact" : "atLeast"
|
|
228
553
|
});
|
|
229
554
|
}
|
|
230
555
|
const cellMatrix = Array.from({ length: rowCount }, () => new Array(colCount));
|
|
@@ -243,22 +568,54 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
243
568
|
const cEnd = Math.min(m.endColumn, colWindowEnd);
|
|
244
569
|
for (let r = Math.max(m.startRow, rowStart); r <= rEnd; r++) for (let c = Math.max(m.startColumn, colStart); c <= cEnd; c++) if (!(r === m.startRow && c === m.startColumn)) insideMerge.add(key(r, c));
|
|
245
570
|
}
|
|
246
|
-
const
|
|
247
|
-
const defaultColTwips = defaultColChars !== void 0 ? Math.round(defaultColChars * 105 + 75) : 960;
|
|
571
|
+
const defaultColTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
|
|
248
572
|
const columnWidths = new Array(colCount).fill(defaultColTwips);
|
|
573
|
+
const hiddenCols = /* @__PURE__ */ new Set();
|
|
249
574
|
for (const col of worksheet.columns) {
|
|
250
|
-
const twips =
|
|
575
|
+
const twips = columnTwips(col.widthChars, charTwipsUnit);
|
|
251
576
|
for (let abs = col.min - 1; abs <= col.max - 1; abs++) {
|
|
252
577
|
const i = abs - colStart;
|
|
253
|
-
if (i
|
|
578
|
+
if (i < 0 || i >= colCount) continue;
|
|
579
|
+
columnWidths[i] = twips;
|
|
580
|
+
if (col.hidden) hiddenCols.add(i);
|
|
254
581
|
}
|
|
255
582
|
}
|
|
256
|
-
const
|
|
583
|
+
const hiddenRows = /* @__PURE__ */ new Set();
|
|
584
|
+
for (const h of worksheet.rowHeights) if (h.hidden) hiddenRows.add(h.row - rowStart);
|
|
585
|
+
const visibleCols = [];
|
|
586
|
+
for (let c = 0; c < colCount; c++) if (!hiddenCols.has(c)) visibleCols.push(c);
|
|
587
|
+
const visibleWidths = visibleCols.map((c) => columnWidths[c]);
|
|
588
|
+
const totalGridTwips = visibleWidths.reduce((sum, w) => sum + w, 0);
|
|
257
589
|
let totalGridHeightTwips = 0;
|
|
258
|
-
|
|
259
|
-
const
|
|
590
|
+
const fitRowTwips = [];
|
|
591
|
+
const fitRowBreaks = /* @__PURE__ */ new Set();
|
|
592
|
+
const manualRowBreaks = new Set(honouredBreaks(worksheet, "rows"));
|
|
593
|
+
let fitTitleTwips = 0;
|
|
594
|
+
for (let r = 0; r < rowCount; r++) {
|
|
595
|
+
if (hiddenRows.has(r)) continue;
|
|
596
|
+
const twips = rowHeightMap.get(r)?.heightTwips ?? 300;
|
|
597
|
+
totalGridHeightTwips += twips;
|
|
598
|
+
const abs = r + rowStart;
|
|
599
|
+
if (manualRowBreaks.has(abs)) fitRowBreaks.add(fitRowTwips.length);
|
|
600
|
+
if (print.titleRows && abs >= print.titleRows.startRow && abs <= print.titleRows.endRow) fitTitleTwips += twips;
|
|
601
|
+
fitRowTwips.push(twips);
|
|
602
|
+
}
|
|
603
|
+
const manualColBreaks = new Set(honouredBreaks(worksheet, "cols"));
|
|
604
|
+
const fitColBreaks = /* @__PURE__ */ new Set();
|
|
605
|
+
visibleCols.forEach((c, i) => {
|
|
606
|
+
if (manualColBreaks.has(c + colStart)) fitColBreaks.add(i);
|
|
607
|
+
});
|
|
608
|
+
const drawing = print.drawingExtentPt;
|
|
609
|
+
const printScale = computePrintScale(worksheet, Math.max(totalGridTwips, Math.round((drawing?.widthPt ?? 0) * TWIPS_PER_POINT)), sheetContentWidthTwips(worksheet), Math.max(totalGridHeightTwips, Math.round((drawing?.heightPt ?? 0) * TWIPS_PER_POINT)), sheetContentHeightTwips(worksheet), {
|
|
610
|
+
rowTwips: fitRowTwips,
|
|
611
|
+
rowBreaks: fitRowBreaks,
|
|
612
|
+
titleTwips: fitTitleTwips,
|
|
613
|
+
colTwips: visibleWidths,
|
|
614
|
+
colBreaks: fitColBreaks
|
|
615
|
+
});
|
|
260
616
|
const scaled = printScale < .999;
|
|
261
|
-
|
|
617
|
+
if (print.scaleSink) print.scaleSink.value = printScale;
|
|
618
|
+
const breakRows = new Set(honouredBreaks(worksheet, "rows"));
|
|
262
619
|
let textBudget = MAX_SHEET_TEXT_CHARS;
|
|
263
620
|
let textBudgetReported = false;
|
|
264
621
|
const runPropsByXf = /* @__PURE__ */ new Map();
|
|
@@ -284,22 +641,57 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
284
641
|
const sparklineByCell = buildSparklineLookup(worksheet, print);
|
|
285
642
|
const tableFormatByCell = buildTableFormatLookup(worksheet);
|
|
286
643
|
const dropdownRanges = listDropdownRanges(worksheet.dataValidations);
|
|
644
|
+
const bandEndOfCol = /* @__PURE__ */ new Map();
|
|
645
|
+
{
|
|
646
|
+
const widthsForBands = scaledColumnWidths(visibleWidths, printScale, scaled);
|
|
647
|
+
const total = widthsForBands.reduce((sum, w) => sum + w, 0);
|
|
648
|
+
const breaks = /* @__PURE__ */ new Set();
|
|
649
|
+
for (const brk of worksheet.colBreaks ?? []) {
|
|
650
|
+
const local = brk - colStart;
|
|
651
|
+
if (local > 0 && local < colCount) breaks.add(local);
|
|
652
|
+
}
|
|
653
|
+
const wide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
|
|
654
|
+
const width = sheetContentWidthTwips(worksheet);
|
|
655
|
+
if (colCount > 1 && bandsAcross(worksheet, wide) && (total > width || breaks.size > 0)) for (const band of computeColumnBands(widthsForBands, width, breaks)) for (let i = band.start; i <= band.end; i++) bandEndOfCol.set(visibleCols[i] ?? i, visibleCols[band.end] ?? band.end);
|
|
656
|
+
}
|
|
287
657
|
const rows = [];
|
|
658
|
+
const rowNumbers = [];
|
|
659
|
+
let titleRowIndex = -1;
|
|
288
660
|
for (let r = 0; r < rowCount; r++) {
|
|
661
|
+
if (hiddenRows.has(r)) continue;
|
|
289
662
|
const absR = r + rowStart;
|
|
290
663
|
const cells = [];
|
|
291
664
|
const overflowed = /* @__PURE__ */ new Set();
|
|
665
|
+
const cellColumns = [];
|
|
666
|
+
const leftOverflow = [];
|
|
292
667
|
for (let c = 0; c < colCount; c++) {
|
|
293
|
-
if (overflowed.has(c)) continue;
|
|
668
|
+
if (hiddenCols.has(c) || overflowed.has(c)) continue;
|
|
294
669
|
const absC = c + colStart;
|
|
295
670
|
const merge = mergeOrigins.get(key(absR, absC));
|
|
296
671
|
if (insideMerge.has(key(absR, absC)) && !merge) {
|
|
297
672
|
const verticalParent = verticalMergeParent(worksheet.merges, absR, absC);
|
|
298
|
-
if (verticalParent && absC === verticalParent.startColumn)
|
|
673
|
+
if (verticalParent && absC === verticalParent.startColumn) {
|
|
674
|
+
const originCell = cellMatrix[verticalParent.startRow - rowStart]?.[verticalParent.startColumn - colStart];
|
|
675
|
+
const originXf = originCell ? styles.cellXfs[originCell.styleIndex ?? 0] : void 0;
|
|
676
|
+
const own = cellMatrix[r]?.[c];
|
|
677
|
+
const ownXf = own ? styles.cellXfs[own.styleIndex ?? 0] : void 0;
|
|
678
|
+
cells.push(makeVerticalContinuation(verticalParent, colWindowEnd, absR, rowWindowEnd, {
|
|
679
|
+
shading: originXf ? shadingFromXf(originXf, styles) : void 0,
|
|
680
|
+
borders: (ownXf ? bordersFromXf(ownXf, styles) : void 0) ?? (originXf ? bordersFromXf(originXf, styles) : void 0)
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
299
683
|
continue;
|
|
300
684
|
}
|
|
301
685
|
const ws = cellMatrix[r]?.[c];
|
|
302
686
|
let text = ws ? resolveCellText(ws, sharedStrings, styles, date1904) : "";
|
|
687
|
+
if (ws?.type === "n" && ws.rawValue.length > 0 && (styles.cellXfs[ws.styleIndex ?? 0]?.numFmtId ?? 0) === 0) {
|
|
688
|
+
const unit = charTwips(styles.cellXfs[ws.styleIndex ?? 0], styles, textTwipsUnit);
|
|
689
|
+
const digit = charWidthUnits("0");
|
|
690
|
+
if (unit > 0 && digit > 0) {
|
|
691
|
+
const room = columnWidths[c] / unit;
|
|
692
|
+
text = generalToWidth(ws.rawValue, (candidate) => estimateChars(candidate) / digit <= room);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
303
695
|
const cfText = text.length > 0 ? text : void 0;
|
|
304
696
|
if (text.length > textBudget) {
|
|
305
697
|
text = text.slice(0, Math.max(0, textBudget));
|
|
@@ -314,8 +706,15 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
314
706
|
}
|
|
315
707
|
}
|
|
316
708
|
textBudget -= text.length;
|
|
317
|
-
const xf = ws
|
|
709
|
+
const xf = ws ? styles.cellXfs[ws.styleIndex ?? 0] : defaultStyleAt(absR, absC, styles.cellXfs);
|
|
318
710
|
let runProps = cellRunProps(xf);
|
|
711
|
+
if (ws?.type === "n" && xf?.numFmtId) {
|
|
712
|
+
const fmtColor = numberFormatColorHex(ws.rawValue, xf.numFmtId, styles.numFmts);
|
|
713
|
+
if (fmtColor !== void 0 && fmtColor !== runProps.colorHex) runProps = {
|
|
714
|
+
...runProps,
|
|
715
|
+
colorHex: fmtColor
|
|
716
|
+
};
|
|
717
|
+
}
|
|
319
718
|
const alignment = alignmentFromXf(xf, ws?.type);
|
|
320
719
|
let shading = xf ? shadingFromXf(xf, styles) : void 0;
|
|
321
720
|
const tableFmt = tableFormatByCell.get(key(absR, absC));
|
|
@@ -324,46 +723,74 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
324
723
|
...runProps,
|
|
325
724
|
colorHex: tableFmt.fontColorHex
|
|
326
725
|
};
|
|
327
|
-
|
|
726
|
+
let borders = xf ? scaleBorders(bordersFromXf(xf, styles), printScale) : void 0;
|
|
328
727
|
let dataBar;
|
|
329
728
|
let icon;
|
|
330
729
|
const sparkline = sparklineByCell.get(key(absR, absC));
|
|
331
730
|
if (cfFormatter && ws) {
|
|
332
|
-
const
|
|
731
|
+
const blank = ws.rawValue.length === 0 && ws.inlineText === void 0;
|
|
732
|
+
const over = cfFormatter(absR, absC, ws.type === "n" && Number.isFinite(Number(ws.rawValue)) ? Number(ws.rawValue) : void 0, cfText, blank);
|
|
333
733
|
if (over) {
|
|
334
734
|
if (over.fillHex) shading = { colorHex: over.fillHex };
|
|
335
735
|
if (over.dataBar) dataBar = over.dataBar;
|
|
736
|
+
if (over.numberFormat !== void 0 && ws.type === "n" && ws.rawValue.length > 0) text = applyNumberFormat(ws.rawValue, CF_NUMBER_FORMAT_ID, new Map([[CF_NUMBER_FORMAT_ID, over.numberFormat]]), date1904);
|
|
737
|
+
if (over.hideValue) text = "";
|
|
336
738
|
if (over.icon) icon = over.icon;
|
|
739
|
+
if (over.border) borders = {
|
|
740
|
+
...borders,
|
|
741
|
+
...mapXlsxBorder(over.border)
|
|
742
|
+
};
|
|
337
743
|
runProps = applyCfOverride(runProps, over);
|
|
338
744
|
}
|
|
339
745
|
}
|
|
340
746
|
let overflowSpan = 1;
|
|
747
|
+
let paintColumns = 1;
|
|
748
|
+
let overflowRightRule;
|
|
341
749
|
const wrapText = xf?.alignment?.wrapText === true;
|
|
342
750
|
const rotation = xf?.alignment?.textRotation;
|
|
343
751
|
const rotated = rotation !== void 0 && rotation !== 0 && text.length > 0;
|
|
344
752
|
const shrinkToFit = xf?.alignment?.shrinkToFit === true && !wrapText && !rotated;
|
|
345
|
-
if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && alignment === "left") {
|
|
753
|
+
if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && ws && (ws.type === "s" || ws.type === "str" || ws.type === "inlineStr") && (alignment === "left" || alignment === "center") && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) {
|
|
346
754
|
let availTwips = columnWidths[c];
|
|
347
755
|
let cc = c + 1;
|
|
348
|
-
const neighbourIsFree = (col) => !cellHasContent(cellMatrix[r]?.[col]) && !cellPaintsSomething(cellMatrix[r]?.[col], styles) && !sparklineByCell.has(key(absR, col + colStart)) && !(dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, col + colStart));
|
|
349
|
-
|
|
350
|
-
|
|
756
|
+
const neighbourIsFree = (col) => !cellHasContent(cellMatrix[r]?.[col]) && !insideMerge.has(key(absR, col + colStart)) && !mergeOrigins.has(key(absR, col + colStart)) && (!cellPaintsSomething(cellMatrix[r]?.[col], styles) || spanPreservesPaint(cellMatrix[r]?.[col], styles, shading, borders)) && !sparklineByCell.has(key(absR, col + colStart)) && !(dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, col + colStart));
|
|
757
|
+
const bandEnd = bandEndOfCol.get(c) ?? colCount - 1;
|
|
758
|
+
while (cc <= bandEnd && (hiddenCols.has(cc) || neighbourIsFree(cc))) {
|
|
759
|
+
if (!hiddenCols.has(cc)) availTwips += columnWidths[cc];
|
|
351
760
|
cc++;
|
|
352
761
|
}
|
|
353
|
-
if (cc
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
762
|
+
if (cc <= bandEnd && !cellHasContent(cellMatrix[r]?.[cc]) && !sparklineByCell.has(key(absR, cc + colStart)) && !(dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, cc + colStart))) {
|
|
763
|
+
const rule = absorbableRightRule(cellMatrix[r]?.[cc], styles, shading, borders);
|
|
764
|
+
if (rule) {
|
|
765
|
+
availTwips += columnWidths[cc];
|
|
766
|
+
overflowRightRule = rule;
|
|
767
|
+
cc++;
|
|
768
|
+
}
|
|
356
769
|
}
|
|
770
|
+
text = clipToWidth(text, availTwips * 1.1, charTwips(xf, styles, textTwipsUnit));
|
|
357
771
|
if (cc > c + 1) {
|
|
358
|
-
overflowSpan =
|
|
772
|
+
overflowSpan = 0;
|
|
773
|
+
for (let k = c; k < cc; k++) if (!hiddenCols.has(k)) overflowSpan++;
|
|
359
774
|
for (let k = c + 1; k < cc; k++) overflowed.add(k);
|
|
775
|
+
paintColumns = 1;
|
|
776
|
+
for (let k = c + 1; k < cc; k++) {
|
|
777
|
+
if (hiddenCols.has(k)) continue;
|
|
778
|
+
if (!cellPaintsSomething(cellMatrix[r]?.[k], styles)) break;
|
|
779
|
+
paintColumns++;
|
|
780
|
+
}
|
|
360
781
|
}
|
|
782
|
+
} else if (text.length > 0 && merge && !wrapText && !rotated && !shrinkToFit) {
|
|
783
|
+
const lastCol = Math.min(merge.endColumn - colStart, bandEndOfCol.get(c) ?? colCount - 1);
|
|
784
|
+
let availTwips = 0;
|
|
785
|
+
for (let k = c; k <= lastCol; k++) if (!hiddenCols.has(k)) availTwips += columnWidths[k] ?? 0;
|
|
786
|
+
text = clipToWidth(text, availTwips * 1.1, charTwips(xf, styles, textTwipsUnit));
|
|
361
787
|
}
|
|
362
788
|
if (shrinkToFit && ws && text.length > 0) {
|
|
363
|
-
const
|
|
364
|
-
|
|
789
|
+
const drawnTwips = estimateChars(text) / charWidthUnits("0") * charTwips(xf, styles, textTwipsUnit);
|
|
790
|
+
const room = columnWidths[c];
|
|
791
|
+
if (drawnTwips > room) {
|
|
365
792
|
const baseHalfPt = (styles.fonts[xf.fontId]?.sizePt ?? 11) * 2;
|
|
366
|
-
const scaledHalfPt = Math.max(2, Math.
|
|
793
|
+
const scaledHalfPt = Math.max(2, Math.floor(baseHalfPt * room / drawnTwips));
|
|
367
794
|
runProps = {
|
|
368
795
|
...runProps,
|
|
369
796
|
fontSizePt: halfPtToPt(scaledHalfPt)
|
|
@@ -373,15 +800,28 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
373
800
|
const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
|
|
374
801
|
const href = print.hyperlinks && print.hyperlinks.length > 0 ? hyperlinkUrlAt(print.hyperlinks, absR, absC) : void 0;
|
|
375
802
|
const visibleEndCol = merge ? Math.min(merge.endColumn, colWindowEnd) : 0;
|
|
803
|
+
let mergeSpan = 0;
|
|
804
|
+
if (merge && visibleEndCol > merge.startColumn) {
|
|
805
|
+
for (let a = merge.startColumn; a <= visibleEndCol; a++) if (!hiddenCols.has(a - colStart)) mergeSpan++;
|
|
806
|
+
}
|
|
376
807
|
const properties = {
|
|
377
|
-
...
|
|
808
|
+
...mergeSpan > 1 ? { colSpan: mergeSpan } : overflowSpan > 1 ? {
|
|
809
|
+
colSpan: overflowSpan,
|
|
810
|
+
paintColumns
|
|
811
|
+
} : {},
|
|
378
812
|
...merge && Math.min(merge.endRow, rowWindowEnd) > merge.startRow ? { merge: "start" } : {},
|
|
379
813
|
...shading ? { shading } : {},
|
|
380
814
|
...dataBar ? { dataBar } : {},
|
|
381
815
|
...icon ? { icon } : {},
|
|
382
816
|
...sparkline ? { sparkline } : {},
|
|
383
|
-
...borders ? { borders
|
|
384
|
-
|
|
817
|
+
...borders || overflowRightRule ? { borders: {
|
|
818
|
+
...borders,
|
|
819
|
+
...overflowRightRule ? { right: overflowRightRule } : {}
|
|
820
|
+
} } : {},
|
|
821
|
+
...dropdown ? { dropdown: true } : {},
|
|
822
|
+
...!wrapText && !rotated ? { noWrap: true } : {},
|
|
823
|
+
...!wrapText && !rotated && !shrinkToFit && !merge && ws?.type === "n" && (xf?.numFmtId ?? 0) !== 0 && text.length > 0 && tooWideToShow(text, charTwips(xf, styles, textTwipsUnit), columnWidths[c]) ? { hashOnOverflow: true } : {},
|
|
824
|
+
verticalAlign: verticalAlignOf(xf)
|
|
385
825
|
};
|
|
386
826
|
const indentLevels = xf?.alignment?.indent ?? 0;
|
|
387
827
|
const baseParaProps = cellParaProps(alignment);
|
|
@@ -399,29 +839,49 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
399
839
|
properties: runProps,
|
|
400
840
|
...href ? { href } : {}
|
|
401
841
|
}] : [];
|
|
402
|
-
const content = rotated ? stackedVerticalContent(text, runProps, href) : cellRuns.length === 0 ? [] :
|
|
842
|
+
const content = rotated ? stackedVerticalContent(text, runProps, href) : cellRuns.length === 0 ? [] : splitCellLines(cellRuns, wrapText).map((runs) => ({
|
|
403
843
|
kind: "paragraph",
|
|
404
844
|
paragraph: {
|
|
405
845
|
properties: paragraphProps,
|
|
406
|
-
runs
|
|
846
|
+
runs
|
|
407
847
|
}
|
|
408
|
-
}
|
|
848
|
+
}));
|
|
849
|
+
if (text.length > 0 && !merge && !wrapText && !rotated && !shrinkToFit && c > 0 && alignment === "right" && estimateChars(text) * charTwips(xf, styles, textTwipsUnit) > columnWidths[c]) leftOverflow.push({
|
|
850
|
+
index: cells.length,
|
|
851
|
+
column: c,
|
|
852
|
+
needTwips: estimateChars(text) * charTwips(xf, styles, textTwipsUnit) - columnWidths[c],
|
|
853
|
+
shading,
|
|
854
|
+
borders
|
|
855
|
+
});
|
|
856
|
+
cellColumns.push(c);
|
|
409
857
|
cells.push({
|
|
410
858
|
properties,
|
|
411
859
|
content
|
|
412
860
|
});
|
|
413
861
|
}
|
|
862
|
+
absorbLeftwards(cells, cellColumns, leftOverflow, styles, (col) => {
|
|
863
|
+
if (insideMerge.has(key(absR, col)) || mergeOrigins.has(key(absR, col))) return void 0;
|
|
864
|
+
if (sparklineByCell.has(key(absR, col + colStart))) return void 0;
|
|
865
|
+
if (dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, col + colStart)) return;
|
|
866
|
+
if (cellHasContent(cellMatrix[r]?.[col])) return void 0;
|
|
867
|
+
return {
|
|
868
|
+
cell: cellMatrix[r]?.[col],
|
|
869
|
+
widthTwips: columnWidths[col]
|
|
870
|
+
};
|
|
871
|
+
});
|
|
414
872
|
const baseRowProps = rowHeightMap.get(r);
|
|
415
873
|
const rowHeightTwips = scaled && baseRowProps !== void 0 ? Math.max(1, Math.round(baseRowProps.heightTwips * printScale)) : baseRowProps?.heightTwips;
|
|
416
874
|
const isTitleRow = print.titleRows !== void 0 && absR >= print.titleRows.startRow && absR <= print.titleRows.endRow;
|
|
417
875
|
const rowProps = {
|
|
418
876
|
...rowHeightTwips !== void 0 ? {
|
|
419
877
|
height: twipsToPt(rowHeightTwips),
|
|
420
|
-
heightRule: "atLeast"
|
|
878
|
+
heightRule: baseRowProps?.heightRule ?? "atLeast"
|
|
421
879
|
} : {},
|
|
422
880
|
...isTitleRow ? { isHeader: true } : {},
|
|
423
881
|
...breakRows.has(absR) ? { pageBreakBefore: true } : {}
|
|
424
882
|
};
|
|
883
|
+
if (isTitleRow && titleRowIndex < 0) titleRowIndex = rows.length;
|
|
884
|
+
rowNumbers.push(absR + 1);
|
|
425
885
|
rows.push({
|
|
426
886
|
properties: rowProps,
|
|
427
887
|
cells
|
|
@@ -429,13 +889,14 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
429
889
|
}
|
|
430
890
|
const thin = {
|
|
431
891
|
style: "single",
|
|
432
|
-
width: eighthPtToPt(
|
|
892
|
+
width: eighthPtToPt(2),
|
|
893
|
+
colorHex: PRINT_GRIDLINE_HEX
|
|
433
894
|
};
|
|
434
895
|
const centered = worksheet.printOptions?.horizontalCentered === true;
|
|
435
896
|
const tableProperties = {
|
|
436
897
|
defaultCellMargins: {
|
|
437
|
-
left: pt(EXCEL_CELL_INSET_PT),
|
|
438
|
-
right: pt(EXCEL_CELL_INSET_PT),
|
|
898
|
+
left: pt(EXCEL_CELL_INSET_PT * printScale),
|
|
899
|
+
right: pt(EXCEL_CELL_INSET_PT * printScale),
|
|
439
900
|
top: pt(0),
|
|
440
901
|
bottom: pt(0)
|
|
441
902
|
},
|
|
@@ -457,16 +918,42 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
457
918
|
if (local > 0 && local < colCount) colBreaksLocal.add(local);
|
|
458
919
|
}
|
|
459
920
|
const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
|
|
460
|
-
const bandWidths =
|
|
921
|
+
const bandWidths = scaledColumnWidths(visibleWidths, printScale, scaled);
|
|
461
922
|
const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
|
|
462
|
-
if (colCount > 1 && (
|
|
923
|
+
if (colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
|
|
463
924
|
const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
|
|
464
|
-
if (bands.length > 1)
|
|
925
|
+
if (bands.length > 1) {
|
|
926
|
+
if (print.bandSink) print.bandSink.lefts = bands.map((band) => twipsToPt(bandWidths.slice(0, band.start).reduce((sum, w) => sum + w, 0)));
|
|
927
|
+
return bandedTables(rows, bandWidths, bands, tableProperties, titleRowIndex, Math.round((print.drawingExtentPt?.widthPt ?? 0) * TWIPS_PER_POINT * printScale), worksheet.printOptions?.headings ? {
|
|
928
|
+
colStart,
|
|
929
|
+
rowNumbers
|
|
930
|
+
} : void 0);
|
|
931
|
+
}
|
|
465
932
|
}
|
|
466
933
|
const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
|
|
467
934
|
rows: worksheet.pane.frozenRows,
|
|
468
935
|
cols: worksheet.pane.frozenCols
|
|
469
936
|
} : void 0;
|
|
937
|
+
if (titleRowIndex > 0 && titleRowIndex < rows.length) {
|
|
938
|
+
const titleStart = titleRowIndex;
|
|
939
|
+
const grid = bandWidths.map((w) => twipsToPt(w));
|
|
940
|
+
return [{
|
|
941
|
+
kind: "table",
|
|
942
|
+
table: {
|
|
943
|
+
properties: tableProperties,
|
|
944
|
+
grid,
|
|
945
|
+
rows: rows.slice(0, titleStart)
|
|
946
|
+
}
|
|
947
|
+
}, {
|
|
948
|
+
kind: "table",
|
|
949
|
+
table: {
|
|
950
|
+
properties: tableProperties,
|
|
951
|
+
grid,
|
|
952
|
+
rows: rows.slice(titleStart)
|
|
953
|
+
}
|
|
954
|
+
}];
|
|
955
|
+
}
|
|
956
|
+
const headed = worksheet.printOptions?.headings ? withHeadingBand(rows, bandWidths, colStart, rowNumbers) : void 0;
|
|
470
957
|
return [{
|
|
471
958
|
kind: "table",
|
|
472
959
|
table: {
|
|
@@ -474,20 +961,22 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
474
961
|
...tableProperties,
|
|
475
962
|
frozen
|
|
476
963
|
} : tableProperties,
|
|
477
|
-
grid: bandWidths.map((w) => twipsToPt(w)),
|
|
478
|
-
rows
|
|
964
|
+
grid: (headed?.widths ?? bandWidths).map((w) => twipsToPt(w)),
|
|
965
|
+
rows: headed?.rows ?? rows
|
|
479
966
|
}
|
|
480
967
|
}];
|
|
481
968
|
}
|
|
482
969
|
function verticalMergeParent(merges, row, column) {
|
|
483
970
|
for (const m of merges) if (column >= m.startColumn && column <= m.endColumn && row > m.startRow && row <= m.endRow && m.endRow > m.startRow) return m;
|
|
484
971
|
}
|
|
485
|
-
function makeVerticalContinuation(merge, colWindowEnd, absR, rowWindowEnd) {
|
|
972
|
+
function makeVerticalContinuation(merge, colWindowEnd, absR, rowWindowEnd, paint) {
|
|
486
973
|
const span = Math.min(merge.endColumn, colWindowEnd) - merge.startColumn + 1;
|
|
487
974
|
return {
|
|
488
975
|
properties: {
|
|
489
976
|
merge: absR < Math.min(merge.endRow, rowWindowEnd) ? "middle" : "end",
|
|
490
|
-
...span > 1 ? { colSpan: span } : {}
|
|
977
|
+
...span > 1 ? { colSpan: span } : {},
|
|
978
|
+
...paint.shading ? { shading: paint.shading } : {},
|
|
979
|
+
...paint.borders ? { borders: paint.borders } : {}
|
|
491
980
|
},
|
|
492
981
|
content: [{
|
|
493
982
|
kind: "paragraph",
|
|
@@ -518,16 +1007,17 @@ function resolveCellText(cell, sharedStrings, styles, date1904) {
|
|
|
518
1007
|
}
|
|
519
1008
|
if (cell.type === "b") return cell.rawValue === "1" ? "TRUE" : "FALSE";
|
|
520
1009
|
if (cell.type === "str" || cell.type === "e" || cell.type === "d") return cell.rawValue;
|
|
521
|
-
const numFmtId =
|
|
1010
|
+
const numFmtId = styles.cellXfs[cell.styleIndex ?? 0]?.numFmtId ?? 0;
|
|
522
1011
|
return applyNumberFormat(cell.rawValue, numFmtId, styles.numFmts, date1904);
|
|
523
1012
|
}
|
|
524
1013
|
function runPropsFromXf(xf, styles) {
|
|
525
|
-
if (!xf.applyFont && xf.fontId === 0) return {};
|
|
526
1014
|
const font = styles.fonts[xf.fontId];
|
|
527
1015
|
if (!font) return {};
|
|
528
1016
|
const props = {};
|
|
529
1017
|
if (font.bold) props.bold = true;
|
|
530
1018
|
if (font.italic) props.italic = true;
|
|
1019
|
+
if (font.underline) props.underline = "single";
|
|
1020
|
+
if (font.strike) props.strike = true;
|
|
531
1021
|
if (font.sizePt !== void 0) props.fontSizePt = halfPtToPt(Math.round(font.sizePt * 2));
|
|
532
1022
|
if (font.colorHex) props.colorHex = font.colorHex;
|
|
533
1023
|
return props;
|
|
@@ -549,9 +1039,10 @@ function stackedVerticalContent(text, runProps, href) {
|
|
|
549
1039
|
}
|
|
550
1040
|
function richRunProps(base, rr) {
|
|
551
1041
|
const out = { ...base };
|
|
552
|
-
if (rr.bold) out.bold =
|
|
553
|
-
if (rr.italic) out.italic =
|
|
1042
|
+
if (rr.bold !== void 0) out.bold = rr.bold;
|
|
1043
|
+
if (rr.italic !== void 0) out.italic = rr.italic;
|
|
554
1044
|
if (rr.underline) out.underline = "single";
|
|
1045
|
+
if (rr.strike !== void 0) out.strike = rr.strike;
|
|
555
1046
|
if (rr.colorHex) out.colorHex = rr.colorHex;
|
|
556
1047
|
if (rr.sizePt !== void 0) out.fontSizePt = halfPtToPt(Math.round(rr.sizePt * 2));
|
|
557
1048
|
if (rr.vertAlign) out.verticalAlign = rr.vertAlign;
|
|
@@ -562,7 +1053,8 @@ function applyCfOverride(base, o) {
|
|
|
562
1053
|
...base,
|
|
563
1054
|
...o.fontColorHex ? { colorHex: o.fontColorHex } : {},
|
|
564
1055
|
...o.bold !== void 0 ? { bold: o.bold } : {},
|
|
565
|
-
...o.italic !== void 0 ? { italic: o.italic } : {}
|
|
1056
|
+
...o.italic !== void 0 ? { italic: o.italic } : {},
|
|
1057
|
+
...o.strike !== void 0 ? { strike: o.strike } : {}
|
|
566
1058
|
};
|
|
567
1059
|
}
|
|
568
1060
|
/**
|
|
@@ -668,6 +1160,42 @@ function bordersFromXf(xf, styles) {
|
|
|
668
1160
|
}
|
|
669
1161
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
670
1162
|
}
|
|
1163
|
+
/** An {@link XlsxBorder}'s four edges as {@link CellBorders} — a dxf's, or a cell's. */
|
|
1164
|
+
function mapXlsxBorder(border) {
|
|
1165
|
+
const out = {};
|
|
1166
|
+
const top = mapBorderEdge(border.top);
|
|
1167
|
+
const right = mapBorderEdge(border.right);
|
|
1168
|
+
const bottom = mapBorderEdge(border.bottom);
|
|
1169
|
+
const left = mapBorderEdge(border.left);
|
|
1170
|
+
if (top) out.top = top;
|
|
1171
|
+
if (right) out.right = right;
|
|
1172
|
+
if (bottom) out.bottom = bottom;
|
|
1173
|
+
if (left) out.left = left;
|
|
1174
|
+
return out;
|
|
1175
|
+
}
|
|
1176
|
+
/** Every edge of a cell's box, at the print scale. */
|
|
1177
|
+
function scaleBorders(borders, scale) {
|
|
1178
|
+
if (!borders || scale >= .999) return borders;
|
|
1179
|
+
const edge = (b) => b === void 0 || b.width === void 0 ? b : {
|
|
1180
|
+
...b,
|
|
1181
|
+
width: pt(b.width * scale)
|
|
1182
|
+
};
|
|
1183
|
+
const out = {};
|
|
1184
|
+
for (const side of [
|
|
1185
|
+
"top",
|
|
1186
|
+
"right",
|
|
1187
|
+
"bottom",
|
|
1188
|
+
"left",
|
|
1189
|
+
"insideH",
|
|
1190
|
+
"insideV"
|
|
1191
|
+
]) {
|
|
1192
|
+
const e = edge(borders[side]);
|
|
1193
|
+
if (e) out[side] = e;
|
|
1194
|
+
}
|
|
1195
|
+
if (borders.diagonalUp !== void 0) out.diagonalUp = borders.diagonalUp;
|
|
1196
|
+
if (borders.diagonalDown !== void 0) out.diagonalDown = borders.diagonalDown;
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
671
1199
|
function mapBorderEdge(edge) {
|
|
672
1200
|
if (!edge || !edge.style || edge.style === "none") return void 0;
|
|
673
1201
|
const { style, sizeEighthPt } = mapBorderStyle(edge.style);
|
|
@@ -681,41 +1209,56 @@ function mapBorderStyle(style) {
|
|
|
681
1209
|
switch (style) {
|
|
682
1210
|
case "hair": return {
|
|
683
1211
|
style: "single",
|
|
684
|
-
sizeEighthPt:
|
|
1212
|
+
sizeEighthPt: 3
|
|
685
1213
|
};
|
|
686
1214
|
case "thin": return {
|
|
687
1215
|
style: "single",
|
|
688
|
-
sizeEighthPt:
|
|
1216
|
+
sizeEighthPt: 6
|
|
1217
|
+
};
|
|
1218
|
+
case "medium": return {
|
|
1219
|
+
style: "single",
|
|
1220
|
+
sizeEighthPt: 12
|
|
1221
|
+
};
|
|
1222
|
+
case "mediumDashed": return {
|
|
1223
|
+
style: "dashed",
|
|
1224
|
+
sizeEighthPt: 12
|
|
689
1225
|
};
|
|
690
|
-
case "medium":
|
|
691
|
-
case "mediumDashed":
|
|
692
1226
|
case "mediumDashDot":
|
|
1227
|
+
case "slantDashDot": return {
|
|
1228
|
+
style: "dashDot",
|
|
1229
|
+
sizeEighthPt: 12
|
|
1230
|
+
};
|
|
693
1231
|
case "mediumDashDotDot": return {
|
|
694
|
-
style: "
|
|
695
|
-
sizeEighthPt:
|
|
1232
|
+
style: "dashDotDot",
|
|
1233
|
+
sizeEighthPt: 12
|
|
696
1234
|
};
|
|
697
1235
|
case "thick": return {
|
|
698
1236
|
style: "thick",
|
|
699
|
-
sizeEighthPt:
|
|
1237
|
+
sizeEighthPt: 18
|
|
700
1238
|
};
|
|
701
|
-
case "dashed":
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1239
|
+
case "dashed": return {
|
|
1240
|
+
style: "dashSmallGap",
|
|
1241
|
+
sizeEighthPt: 6
|
|
1242
|
+
};
|
|
1243
|
+
case "dashDot": return {
|
|
1244
|
+
style: "dashDot",
|
|
1245
|
+
sizeEighthPt: 6
|
|
1246
|
+
};
|
|
1247
|
+
case "dashDotDot": return {
|
|
1248
|
+
style: "dashDotDot",
|
|
1249
|
+
sizeEighthPt: 6
|
|
707
1250
|
};
|
|
708
1251
|
case "dotted": return {
|
|
709
1252
|
style: "dotted",
|
|
710
|
-
sizeEighthPt:
|
|
1253
|
+
sizeEighthPt: 6
|
|
711
1254
|
};
|
|
712
1255
|
case "double": return {
|
|
713
1256
|
style: "double",
|
|
714
|
-
sizeEighthPt:
|
|
1257
|
+
sizeEighthPt: 18
|
|
715
1258
|
};
|
|
716
1259
|
default: return {
|
|
717
1260
|
style: "single",
|
|
718
|
-
sizeEighthPt:
|
|
1261
|
+
sizeEighthPt: 6
|
|
719
1262
|
};
|
|
720
1263
|
}
|
|
721
1264
|
}
|
|
@@ -890,12 +1433,290 @@ function lastContentColumn(worksheet, rowStart, rowCount, colStart, colCount) {
|
|
|
890
1433
|
* Such a cell cannot be swallowed by a neighbour's overflowing text: the span
|
|
891
1434
|
* that gives the text its width would take the paint with it.
|
|
892
1435
|
*/
|
|
1436
|
+
/**
|
|
1437
|
+
* Whether a cell puts anything VISIBLE on an otherwise-empty page.
|
|
1438
|
+
*
|
|
1439
|
+
* The difference from {@link cellPaintsSomething} is white: a solid fill in the
|
|
1440
|
+
* paper's own colour paints nothing a reader can see, and a sheet of them is a
|
|
1441
|
+
* blank sheet. bnc762542.xlsx is 176 such cells — `fgColor indexed="9"`, the
|
|
1442
|
+
* legacy palette's white — with every word it prints living in a callout, and
|
|
1443
|
+
* both references print the callout on a bare page. 48779.xlsx is one cell of
|
|
1444
|
+
* solid red, and both print the swatch.
|
|
1445
|
+
*
|
|
1446
|
+
* @param cell The cell, or undefined.
|
|
1447
|
+
* @param styles The workbook's style tables.
|
|
1448
|
+
* @returns True when the cell draws something the eye can find.
|
|
1449
|
+
*/
|
|
1450
|
+
function cellPaintsVisibly(cell, styles) {
|
|
1451
|
+
if (!cell || cell.styleIndex === void 0) return false;
|
|
1452
|
+
const xf = styles.cellXfs[cell.styleIndex];
|
|
1453
|
+
if (!xf) return false;
|
|
1454
|
+
if (bordersFromXf(xf, styles) !== void 0) return true;
|
|
1455
|
+
const shading = shadingFromXf(xf, styles);
|
|
1456
|
+
return shading !== void 0 && shading.colorHex.toUpperCase() !== "FFFFFF";
|
|
1457
|
+
}
|
|
893
1458
|
function cellPaintsSomething(cell, styles) {
|
|
894
1459
|
if (!cell || cell.styleIndex === void 0) return false;
|
|
895
1460
|
const xf = styles.cellXfs[cell.styleIndex];
|
|
896
1461
|
if (!xf) return false;
|
|
897
1462
|
return shadingFromXf(xf, styles) !== void 0 || bordersFromXf(xf, styles) !== void 0;
|
|
898
1463
|
}
|
|
1464
|
+
/**
|
|
1465
|
+
* How many empty columns past the used range the last column's text needs to
|
|
1466
|
+
* run into, bounded by the printable width (Excel stops at the page edge too).
|
|
1467
|
+
*
|
|
1468
|
+
* Only the last used column can want them — anywhere else the grid already has
|
|
1469
|
+
* neighbours. Zero for the overwhelming majority of sheets, which keeps their
|
|
1470
|
+
* projection exactly as it was.
|
|
1471
|
+
*/
|
|
1472
|
+
function overflowColumnsPastUsedRange(worksheet, usedCol, sharedStrings, styles, date1904, charTwipsUnit) {
|
|
1473
|
+
const defaultTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
|
|
1474
|
+
const widthOf = (abs) => {
|
|
1475
|
+
for (const col of worksheet.columns) {
|
|
1476
|
+
if (abs < col.min - 1 || abs > col.max - 1) continue;
|
|
1477
|
+
return col.hidden ? 0 : columnTwips(col.widthChars, charTwipsUnit);
|
|
1478
|
+
}
|
|
1479
|
+
return defaultTwips;
|
|
1480
|
+
};
|
|
1481
|
+
const lastOfRow = /* @__PURE__ */ new Map();
|
|
1482
|
+
for (const cell of worksheet.cells) {
|
|
1483
|
+
if (!cellHasContent(cell)) continue;
|
|
1484
|
+
const prev = lastOfRow.get(cell.row);
|
|
1485
|
+
if (!prev || cell.column > prev.column) lastOfRow.set(cell.row, cell);
|
|
1486
|
+
}
|
|
1487
|
+
let needTwips = 0;
|
|
1488
|
+
for (const cell of lastOfRow.values()) {
|
|
1489
|
+
if (!(cell.type === "s" || cell.type === "str" || cell.type === "inlineStr")) continue;
|
|
1490
|
+
const xf = styles.cellXfs[cell.styleIndex ?? 0];
|
|
1491
|
+
const align = xf?.alignment;
|
|
1492
|
+
if (align?.wrapText || align?.shrinkToFit || align?.textRotation) continue;
|
|
1493
|
+
if (alignmentFromXf(xf, cell.type) !== "left") continue;
|
|
1494
|
+
let room = 0;
|
|
1495
|
+
for (let abs = cell.column; abs <= usedCol; abs++) room += widthOf(abs);
|
|
1496
|
+
const text = resolveCellText(cell, sharedStrings, styles, date1904);
|
|
1497
|
+
needTwips = Math.max(needTwips, estimateChars(text) * charTwips(xf, styles, 105) - room);
|
|
1498
|
+
}
|
|
1499
|
+
if (needTwips <= 0) return 0;
|
|
1500
|
+
let gridTwips = 0;
|
|
1501
|
+
for (let abs = 0; abs <= usedCol; abs++) gridTwips += widthOf(abs);
|
|
1502
|
+
const limit = sheetContentWidthTwips(worksheet);
|
|
1503
|
+
const MAX_OVERFLOW_COLS = 256;
|
|
1504
|
+
let extra = 0;
|
|
1505
|
+
while (needTwips > 0 && extra < MAX_OVERFLOW_COLS) {
|
|
1506
|
+
const w = widthOf(usedCol + extra + 1);
|
|
1507
|
+
if (w <= 0 || gridTwips + w > limit) break;
|
|
1508
|
+
gridTwips += w;
|
|
1509
|
+
needTwips -= w;
|
|
1510
|
+
extra++;
|
|
1511
|
+
}
|
|
1512
|
+
return extra;
|
|
1513
|
+
}
|
|
1514
|
+
/** Column widths as the bands see them: shrunk by the print scale, floor-rounded. */
|
|
1515
|
+
function scaledColumnWidths(widths, printScale, scaled) {
|
|
1516
|
+
return scaled ? widths.map((w) => Math.max(1, Math.floor(w * printScale))) : [...widths];
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Break a cell's runs at the hard line breaks inside them, or — for a cell that
|
|
1520
|
+
* does not wrap, and so shows one line — drop those breaks.
|
|
1521
|
+
*
|
|
1522
|
+
* Dropped, not turned into a space. A line feed is a control character with no
|
|
1523
|
+
* width of its own: switch wrapping off in Excel and the two lines run
|
|
1524
|
+
* together, which is the complaint behind every "my Alt+Enter disappeared"
|
|
1525
|
+
* question. preserve_space.xlsx holds a bold "123", a break and a plain "456",
|
|
1526
|
+
* and the reference prints "123456" where we printed "123 456" — a space the
|
|
1527
|
+
* document does not contain.
|
|
1528
|
+
*/
|
|
1529
|
+
function splitCellLines(runs, wrapText) {
|
|
1530
|
+
if (!runs.some((r) => /[\r\n]/.test(r.text))) return [[...runs]];
|
|
1531
|
+
if (!wrapText) return [runs.map((r) => ({
|
|
1532
|
+
...r,
|
|
1533
|
+
text: r.text.replace(/[\r\n]+/g, "")
|
|
1534
|
+
}))];
|
|
1535
|
+
const lines = [[]];
|
|
1536
|
+
for (const run of runs) {
|
|
1537
|
+
const parts = run.text.split(/\r\n|\r|\n/);
|
|
1538
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1539
|
+
if (i > 0) lines.push([]);
|
|
1540
|
+
const text = parts[i];
|
|
1541
|
+
if (text.length > 0) lines[lines.length - 1].push({
|
|
1542
|
+
...run,
|
|
1543
|
+
text
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return lines.some((l) => l.length > 0) ? lines : [];
|
|
1548
|
+
}
|
|
1549
|
+
/** §18.8.1 `<alignment vertical>`, defaulting to Excel's bottom. */
|
|
1550
|
+
function verticalAlignOf(xf) {
|
|
1551
|
+
const v = xf?.alignment?.vertical;
|
|
1552
|
+
if (v === "top") return "top";
|
|
1553
|
+
if (v === "center") return "center";
|
|
1554
|
+
if (v === "justify" || v === "distributed") return "top";
|
|
1555
|
+
return "bottom";
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* The width of one character of a cell's own font, in twips.
|
|
1559
|
+
*
|
|
1560
|
+
* {@link TWIPS_PER_EXCEL_CHAR} is the Maximum Digit Width of the DEFAULT 11pt
|
|
1561
|
+
* font, and column widths are measured in exactly those units — but the text
|
|
1562
|
+
* inside a cell is not necessarily written in it. Measuring a 7pt annotation by
|
|
1563
|
+
* the 11pt yardstick makes it read as half again as wide as it is, and the
|
|
1564
|
+
* overflow clip then cuts it in the middle of a word: tdf171828.xlsx lost the
|
|
1565
|
+
* "eff. Zins =" half of a label that fits its band perfectly well.
|
|
1566
|
+
*/
|
|
1567
|
+
function charTwips(xf, styles, unit) {
|
|
1568
|
+
const sizePt = (xf ? styles.fonts[xf.fontId]?.sizePt : void 0) ?? DEFAULT_FONT_PT;
|
|
1569
|
+
if (!Number.isFinite(sizePt) || sizePt <= 0) return unit;
|
|
1570
|
+
return unit * sizePt / DEFAULT_FONT_PT;
|
|
1571
|
+
}
|
|
1572
|
+
var NARROW_CHARS = /* @__PURE__ */ new Set(" .,:;'\"!|il");
|
|
1573
|
+
var SEMI_NARROW_CHARS = /* @__PURE__ */ new Set("ftr()[]{}-/\\");
|
|
1574
|
+
var WIDE_CHARS = /* @__PURE__ */ new Set("MWmw@%");
|
|
1575
|
+
function isFullWidth(code) {
|
|
1576
|
+
return code >= 4352 && code <= 4447 || code >= 11904 && code <= 12350 || code >= 12353 && code <= 13311 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 40960 && code <= 42191 || code >= 43360 && code <= 43391 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072 && code <= 262141;
|
|
1577
|
+
}
|
|
1578
|
+
function charWidthUnits(ch) {
|
|
1579
|
+
if (NARROW_CHARS.has(ch)) return .53;
|
|
1580
|
+
if (SEMI_NARROW_CHARS.has(ch)) return .71;
|
|
1581
|
+
if (WIDE_CHARS.has(ch)) return 1.77;
|
|
1582
|
+
if (ch >= "a" && ch <= "z") return 1.12;
|
|
1583
|
+
if (ch >= "A" && ch <= "Z") return 1.36;
|
|
1584
|
+
const code = ch.codePointAt(0);
|
|
1585
|
+
if (code !== void 0 && isFullWidth(code)) return 2;
|
|
1586
|
+
return 1.18;
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Whether a number is too wide for its column to show it at all — the test
|
|
1590
|
+
* behind {@link CellProperties.hashOnOverflow}.
|
|
1591
|
+
*
|
|
1592
|
+
* Two corrections separate this from the clipping estimate, and both are about
|
|
1593
|
+
* how destructive the answer is. Filling a cell with `#` erases the value, so
|
|
1594
|
+
* the test has to be sure.
|
|
1595
|
+
*
|
|
1596
|
+
* `charWidthUnits` measures in the font we DRAW in, expressed in Excel's unit —
|
|
1597
|
+
* right for deciding what fits on the page, wrong for deciding what the
|
|
1598
|
+
* document itself considers too wide, since §18.3.1.13 defines that unit AS the
|
|
1599
|
+
* digit's width. Dividing by our own digit puts the text back in the document's
|
|
1600
|
+
* terms: a face 18 % wider than the workbook's would otherwise hash a column of
|
|
1601
|
+
* dates that every other reader shows (forum-mso-de-104083.xlsx).
|
|
1602
|
+
*
|
|
1603
|
+
* And then a tenth of headroom, because the estimate is an estimate. A value
|
|
1604
|
+
* that overruns by one percent is a value we should draw and let the cell clip;
|
|
1605
|
+
* only one that plainly cannot fit earns the hashes.
|
|
1606
|
+
*/
|
|
1607
|
+
function tooWideToShow(text, perCharTwips, columnTwips) {
|
|
1608
|
+
const digit = charWidthUnits("0");
|
|
1609
|
+
if (!(digit > 0) || !(perCharTwips > 0)) return false;
|
|
1610
|
+
return estimateChars(text) / digit * perCharTwips > columnTwips * 1.1;
|
|
1611
|
+
}
|
|
1612
|
+
/** Width of `text` in column-width units, estimated character by character. */
|
|
1613
|
+
function estimateChars(text) {
|
|
1614
|
+
let n = 0;
|
|
1615
|
+
for (const ch of text) n += charWidthUnits(ch);
|
|
1616
|
+
return n;
|
|
1617
|
+
}
|
|
1618
|
+
/** The longest prefix of `text` that fits `availTwips`, by the same estimate. */
|
|
1619
|
+
function clipToWidth(text, availTwips, perChar) {
|
|
1620
|
+
if (perChar <= 0) return text;
|
|
1621
|
+
const budget = availTwips / perChar;
|
|
1622
|
+
let used = 0;
|
|
1623
|
+
let i = 0;
|
|
1624
|
+
for (const ch of text) {
|
|
1625
|
+
const w = charWidthUnits(ch);
|
|
1626
|
+
if (used + w > budget) break;
|
|
1627
|
+
used += w;
|
|
1628
|
+
i += ch.length;
|
|
1629
|
+
}
|
|
1630
|
+
return i >= text.length ? text : text.slice(0, Math.max(1, i));
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Widen each right-aligned cell that overflows leftwards over the free columns
|
|
1634
|
+
* before it, deleting the cells those columns produced.
|
|
1635
|
+
*
|
|
1636
|
+
* The mirror of the rightward overflow the row loop does inline, but it cannot
|
|
1637
|
+
* be done inline: the space a right-aligned cell needs lies behind it, in cells
|
|
1638
|
+
* already emitted. Same freeness rule as rightwards — nothing in the column,
|
|
1639
|
+
* and nothing painted there that the span would erase.
|
|
1640
|
+
*/
|
|
1641
|
+
function absorbLeftwards(cells, cellColumns, candidates, styles, neighbourAt) {
|
|
1642
|
+
for (let k = candidates.length - 1; k >= 0; k--) {
|
|
1643
|
+
const cand = candidates[k];
|
|
1644
|
+
let need = cand.needTwips;
|
|
1645
|
+
let first = cand.index;
|
|
1646
|
+
while (first > 0 && need > 0) {
|
|
1647
|
+
const prev = first - 1;
|
|
1648
|
+
const col = cellColumns[prev];
|
|
1649
|
+
if ((cells[prev].properties.colSpan ?? 1) !== 1) break;
|
|
1650
|
+
if (cellColumns[first] !== col + 1) break;
|
|
1651
|
+
const free = neighbourAt(col);
|
|
1652
|
+
if (!free) break;
|
|
1653
|
+
if (!spanPreservesPaint(free.cell, styles, cand.shading, cand.borders)) break;
|
|
1654
|
+
need -= free.widthTwips;
|
|
1655
|
+
first = prev;
|
|
1656
|
+
}
|
|
1657
|
+
if (first === cand.index) continue;
|
|
1658
|
+
const span = cand.index - first + 1;
|
|
1659
|
+
const cell = cells[cand.index];
|
|
1660
|
+
cells.splice(first, span, {
|
|
1661
|
+
...cell,
|
|
1662
|
+
properties: {
|
|
1663
|
+
...cell.properties,
|
|
1664
|
+
colSpan: (cell.properties.colSpan ?? 1) + span - 1
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
cellColumns.splice(first, span, cellColumns[first]);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Whether spanning a cell's overflowing text across `neighbour` would leave the
|
|
1672
|
+
* page looking the same as drawing over it would.
|
|
1673
|
+
*
|
|
1674
|
+
* A span paints the ORIGIN's fill and the ORIGIN's outer borders across the
|
|
1675
|
+
* whole run, so it is lossless exactly when the neighbour's fill already
|
|
1676
|
+
* matches, the neighbour carries no vertical rule of its own to erase, and its
|
|
1677
|
+
* horizontal edges are the ones the origin will redraw anyway. A block of empty
|
|
1678
|
+
* cells continuing a filled, top-ruled band — which is what a decorated
|
|
1679
|
+
* neighbour almost always is — passes; a differently filled or boxed cell does
|
|
1680
|
+
* not, and the text is clipped short of it instead.
|
|
1681
|
+
*/
|
|
1682
|
+
function spanPreservesPaint(neighbour, styles, shading, borders) {
|
|
1683
|
+
if (!neighbour || neighbour.styleIndex === void 0) return true;
|
|
1684
|
+
const xf = styles.cellXfs[neighbour.styleIndex];
|
|
1685
|
+
if (!xf) return true;
|
|
1686
|
+
if (shadingFromXf(xf, styles)?.colorHex !== shading?.colorHex) return false;
|
|
1687
|
+
const nb = bordersFromXf(xf, styles);
|
|
1688
|
+
if (!nb) return true;
|
|
1689
|
+
if (nb.left || nb.right || nb.insideV || nb.diagonalUp || nb.diagonalDown) return false;
|
|
1690
|
+
return sameBorder(nb.top, borders?.top) && sameBorder(nb.bottom, borders?.bottom);
|
|
1691
|
+
}
|
|
1692
|
+
function sameBorder(a, b) {
|
|
1693
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1694
|
+
return a.style === b.style && a.width === b.width && a.colorHex === b.colorHex;
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* The right rule a neighbour carries when that rule is the ONLY thing standing
|
|
1698
|
+
* between it and the overflowing text — in which case the span can simply adopt
|
|
1699
|
+
* it as its own right border and nothing is lost.
|
|
1700
|
+
*
|
|
1701
|
+
* {@link spanPreservesPaint} refuses any vertical rule, which is right for a
|
|
1702
|
+
* rule INSIDE the run (a span erases it) but wrong for the one that closes it:
|
|
1703
|
+
* the last cell's right edge is the boundary of the band the origin sits in,
|
|
1704
|
+
* and the span's own right border lands in exactly the same place. Refusing it
|
|
1705
|
+
* cut five labels short on every page of tdf171828.xlsx — "ohne Sondertilgung"
|
|
1706
|
+
* as "ohne Sondertil" — where the reference runs each of them to that very
|
|
1707
|
+
* edge.
|
|
1708
|
+
*/
|
|
1709
|
+
function absorbableRightRule(neighbour, styles, shading, borders) {
|
|
1710
|
+
if (!neighbour || neighbour.styleIndex === void 0) return void 0;
|
|
1711
|
+
const xf = styles.cellXfs[neighbour.styleIndex];
|
|
1712
|
+
if (!xf) return void 0;
|
|
1713
|
+
if (shadingFromXf(xf, styles)?.colorHex !== shading?.colorHex) return void 0;
|
|
1714
|
+
const nb = bordersFromXf(xf, styles);
|
|
1715
|
+
if (!nb?.right) return void 0;
|
|
1716
|
+
if (nb.left || nb.insideV || nb.diagonalUp || nb.diagonalDown) return void 0;
|
|
1717
|
+
if (!sameBorder(nb.top, borders?.top) || !sameBorder(nb.bottom, borders?.bottom)) return;
|
|
1718
|
+
return nb.right;
|
|
1719
|
+
}
|
|
899
1720
|
var SLICER_WIDTH_PT = 108;
|
|
900
1721
|
var SLICER_ROW_PT = 16;
|
|
901
1722
|
var SLICER_UNSELECTED_HEX = "F2F2F2";
|
|
@@ -910,7 +1731,8 @@ function slicerTable(slicer) {
|
|
|
910
1731
|
const colWidthPt = SLICER_WIDTH_PT / cols;
|
|
911
1732
|
const thin = {
|
|
912
1733
|
style: "single",
|
|
913
|
-
width: eighthPtToPt(
|
|
1734
|
+
width: eighthPtToPt(2),
|
|
1735
|
+
colorHex: PRINT_GRIDLINE_HEX
|
|
914
1736
|
};
|
|
915
1737
|
const rowProps = {
|
|
916
1738
|
height: pt(SLICER_ROW_PT),
|
|
@@ -979,4 +1801,4 @@ function slicerParagraph(text, runProps) {
|
|
|
979
1801
|
};
|
|
980
1802
|
}
|
|
981
1803
|
//#endregion
|
|
982
|
-
export { resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
|
|
1804
|
+
export { cellPaintsVisibly, columnTwips, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
|