reamkit 1.15.3 → 1.16.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/converter/facade.d.ts +5 -0
- 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 +187 -1
- package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
- package/dist/esm/core/drawingml/chart-geometry.js +371 -88
- package/dist/esm/core/drawingml/chart-parser.js +262 -11
- package/dist/esm/core/drawingml/colors.d.ts +6 -6
- package/dist/esm/core/drawingml/colors.js +58 -18
- package/dist/esm/core/drawingml/preset-geometry.js +7 -6
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
- package/dist/esm/core/drawingml/theme-parser.js +74 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.js +172 -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/sheet.d.ts +67 -3
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- 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/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/vector.d.ts +13 -0
- package/dist/esm/excel/activex-parser.d.ts +9 -0
- package/dist/esm/excel/activex-parser.js +16 -1
- 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 +902 -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 +551 -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 +149 -28
- package/dist/esm/excel/xlsx-reader.js +271 -33
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +11 -2
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -0
- package/dist/esm/layout/styled-layout.js +493 -64
- 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/styled-page-emitter.js +164 -32
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +3 -1
- package/dist/esm/pdf/vector-graphics.js +19 -2
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.js +12 -1
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.js +1 -0
- package/dist/esm/word/drawing-parser.d.ts +34 -1
- package/dist/esm/word/drawing-parser.js +52 -2
- package/dist/esm/word/numbering-parser.js +1 -0
- package/dist/esm/word/settings-parser.js +1 -0
- package/dist/esm/word/styles-parser.js +1 -0
- 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,13 +417,26 @@ 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;
|
|
432
|
+
const contentAt = /* @__PURE__ */ new Set();
|
|
163
433
|
for (const c of worksheet.cells) if (c.rawValue !== "" || c.inlineText !== void 0) {
|
|
434
|
+
contentAt.add(key(c.row, c.column));
|
|
164
435
|
if (c.row > usedRow) usedRow = c.row;
|
|
165
436
|
if (c.column > usedCol) usedCol = c.column;
|
|
166
437
|
}
|
|
167
438
|
for (const m of worksheet.merges) {
|
|
439
|
+
if (!contentAt.has(key(m.startRow, m.startColumn))) continue;
|
|
168
440
|
if (m.endRow > usedRow) usedRow = m.endRow;
|
|
169
441
|
if (m.endColumn > usedCol) usedCol = m.endColumn;
|
|
170
442
|
}
|
|
@@ -174,7 +446,61 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
174
446
|
if (host.startRow > usedRow) usedRow = host.startRow;
|
|
175
447
|
if (host.startColumn > usedCol) usedCol = host.startColumn;
|
|
176
448
|
}
|
|
177
|
-
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);
|
|
178
504
|
let rowStart = 0;
|
|
179
505
|
let colStart = 0;
|
|
180
506
|
let rowEnd = usedRow;
|
|
@@ -186,7 +512,8 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
186
512
|
colEnd = Math.min(usedCol, print.printArea.endColumn);
|
|
187
513
|
}
|
|
188
514
|
if (rowEnd < rowStart || colEnd < colStart) return [];
|
|
189
|
-
const MAX_GRID_COLS =
|
|
515
|
+
const MAX_GRID_COLS = 16384;
|
|
516
|
+
const SHEET_MAX_ROWS = 1048576;
|
|
190
517
|
const MAX_GRID_ROWS = 5e4;
|
|
191
518
|
const MAX_GRID_CELLS = 1e6;
|
|
192
519
|
const wantRows = rowEnd - rowStart + 1;
|
|
@@ -195,21 +522,22 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
195
522
|
let rowCount = Math.max(1, Math.min(wantRows, MAX_GRID_ROWS, Math.floor(MAX_GRID_CELLS / colCount)));
|
|
196
523
|
if (rowCount < wantRows) rowCount = Math.max(1, lastContentRow(worksheet, rowStart, rowCount, colStart, colCount) + 1);
|
|
197
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";
|
|
198
526
|
if (rowCount < wantRows) print.losses?.push({
|
|
199
527
|
severity: "dropped",
|
|
200
528
|
feature: FEATURES.tables,
|
|
201
|
-
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))})`,
|
|
202
530
|
...print.sheetName ? { where: `sheet "${print.sheetName}"` } : {}
|
|
203
531
|
});
|
|
204
532
|
if (colCount < wantCols) print.losses?.push({
|
|
205
533
|
severity: "dropped",
|
|
206
534
|
feature: FEATURES.tables,
|
|
207
|
-
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")})`,
|
|
208
536
|
...print.sheetName ? { where: `sheet "${print.sheetName}"` } : {}
|
|
209
537
|
});
|
|
210
538
|
const rowWindowEnd = rowStart + rowCount - 1;
|
|
211
539
|
const colWindowEnd = colStart + colCount - 1;
|
|
212
|
-
const defaultRowTwips = Math.round((worksheet.defaultRowHeightPt ??
|
|
540
|
+
const defaultRowTwips = Math.round((worksheet.defaultRowHeightPt ?? defaultRowHeightPtFor(print.defaultFontPt)) * TWIPS_PER_POINT);
|
|
213
541
|
const rowHeightMap = /* @__PURE__ */ new Map();
|
|
214
542
|
for (let r = 0; r < rowCount; r++) rowHeightMap.set(r, {
|
|
215
543
|
heightTwips: defaultRowTwips,
|
|
@@ -221,7 +549,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
221
549
|
const heightTwips = Math.round(h.heightPt * TWIPS_PER_POINT);
|
|
222
550
|
rowHeightMap.set(local, {
|
|
223
551
|
heightTwips,
|
|
224
|
-
heightRule: "atLeast"
|
|
552
|
+
heightRule: h.customHeight ? "exact" : "atLeast"
|
|
225
553
|
});
|
|
226
554
|
}
|
|
227
555
|
const cellMatrix = Array.from({ length: rowCount }, () => new Array(colCount));
|
|
@@ -240,22 +568,54 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
240
568
|
const cEnd = Math.min(m.endColumn, colWindowEnd);
|
|
241
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));
|
|
242
570
|
}
|
|
243
|
-
const
|
|
244
|
-
const defaultColTwips = defaultColChars !== void 0 ? Math.round(defaultColChars * 105 + 75) : 960;
|
|
571
|
+
const defaultColTwips = defaultColumnTwips(worksheet, charTwipsUnit, DEFAULT_COL_CHARS);
|
|
245
572
|
const columnWidths = new Array(colCount).fill(defaultColTwips);
|
|
573
|
+
const hiddenCols = /* @__PURE__ */ new Set();
|
|
246
574
|
for (const col of worksheet.columns) {
|
|
247
|
-
const twips =
|
|
575
|
+
const twips = columnTwips(col.widthChars, charTwipsUnit);
|
|
248
576
|
for (let abs = col.min - 1; abs <= col.max - 1; abs++) {
|
|
249
577
|
const i = abs - colStart;
|
|
250
|
-
if (i
|
|
578
|
+
if (i < 0 || i >= colCount) continue;
|
|
579
|
+
columnWidths[i] = twips;
|
|
580
|
+
if (col.hidden) hiddenCols.add(i);
|
|
251
581
|
}
|
|
252
582
|
}
|
|
253
|
-
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);
|
|
254
589
|
let totalGridHeightTwips = 0;
|
|
255
|
-
|
|
256
|
-
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
|
+
});
|
|
257
616
|
const scaled = printScale < .999;
|
|
258
|
-
|
|
617
|
+
if (print.scaleSink) print.scaleSink.value = printScale;
|
|
618
|
+
const breakRows = new Set(honouredBreaks(worksheet, "rows"));
|
|
259
619
|
let textBudget = MAX_SHEET_TEXT_CHARS;
|
|
260
620
|
let textBudgetReported = false;
|
|
261
621
|
const runPropsByXf = /* @__PURE__ */ new Map();
|
|
@@ -281,22 +641,57 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
281
641
|
const sparklineByCell = buildSparklineLookup(worksheet, print);
|
|
282
642
|
const tableFormatByCell = buildTableFormatLookup(worksheet);
|
|
283
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
|
+
}
|
|
284
657
|
const rows = [];
|
|
658
|
+
const rowNumbers = [];
|
|
659
|
+
let titleRowIndex = -1;
|
|
285
660
|
for (let r = 0; r < rowCount; r++) {
|
|
661
|
+
if (hiddenRows.has(r)) continue;
|
|
286
662
|
const absR = r + rowStart;
|
|
287
663
|
const cells = [];
|
|
288
664
|
const overflowed = /* @__PURE__ */ new Set();
|
|
665
|
+
const cellColumns = [];
|
|
666
|
+
const leftOverflow = [];
|
|
289
667
|
for (let c = 0; c < colCount; c++) {
|
|
290
|
-
if (overflowed.has(c)) continue;
|
|
668
|
+
if (hiddenCols.has(c) || overflowed.has(c)) continue;
|
|
291
669
|
const absC = c + colStart;
|
|
292
670
|
const merge = mergeOrigins.get(key(absR, absC));
|
|
293
671
|
if (insideMerge.has(key(absR, absC)) && !merge) {
|
|
294
672
|
const verticalParent = verticalMergeParent(worksheet.merges, absR, absC);
|
|
295
|
-
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
|
+
}
|
|
296
683
|
continue;
|
|
297
684
|
}
|
|
298
685
|
const ws = cellMatrix[r]?.[c];
|
|
299
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
|
+
}
|
|
300
695
|
const cfText = text.length > 0 ? text : void 0;
|
|
301
696
|
if (text.length > textBudget) {
|
|
302
697
|
text = text.slice(0, Math.max(0, textBudget));
|
|
@@ -311,8 +706,15 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
311
706
|
}
|
|
312
707
|
}
|
|
313
708
|
textBudget -= text.length;
|
|
314
|
-
const xf = ws
|
|
709
|
+
const xf = ws ? styles.cellXfs[ws.styleIndex ?? 0] : defaultStyleAt(absR, absC, styles.cellXfs);
|
|
315
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
|
+
}
|
|
316
718
|
const alignment = alignmentFromXf(xf, ws?.type);
|
|
317
719
|
let shading = xf ? shadingFromXf(xf, styles) : void 0;
|
|
318
720
|
const tableFmt = tableFormatByCell.get(key(absR, absC));
|
|
@@ -321,46 +723,74 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
321
723
|
...runProps,
|
|
322
724
|
colorHex: tableFmt.fontColorHex
|
|
323
725
|
};
|
|
324
|
-
|
|
726
|
+
let borders = xf ? scaleBorders(bordersFromXf(xf, styles), printScale) : void 0;
|
|
325
727
|
let dataBar;
|
|
326
728
|
let icon;
|
|
327
729
|
const sparkline = sparklineByCell.get(key(absR, absC));
|
|
328
730
|
if (cfFormatter && ws) {
|
|
329
|
-
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);
|
|
330
733
|
if (over) {
|
|
331
734
|
if (over.fillHex) shading = { colorHex: over.fillHex };
|
|
332
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 = "";
|
|
333
738
|
if (over.icon) icon = over.icon;
|
|
739
|
+
if (over.border) borders = {
|
|
740
|
+
...borders,
|
|
741
|
+
...mapXlsxBorder(over.border)
|
|
742
|
+
};
|
|
334
743
|
runProps = applyCfOverride(runProps, over);
|
|
335
744
|
}
|
|
336
745
|
}
|
|
337
746
|
let overflowSpan = 1;
|
|
747
|
+
let paintColumns = 1;
|
|
748
|
+
let overflowRightRule;
|
|
338
749
|
const wrapText = xf?.alignment?.wrapText === true;
|
|
339
750
|
const rotation = xf?.alignment?.textRotation;
|
|
340
751
|
const rotated = rotation !== void 0 && rotation !== 0 && text.length > 0;
|
|
341
752
|
const shrinkToFit = xf?.alignment?.shrinkToFit === true && !wrapText && !rotated;
|
|
342
|
-
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]) {
|
|
343
754
|
let availTwips = columnWidths[c];
|
|
344
755
|
let cc = c + 1;
|
|
345
|
-
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));
|
|
346
|
-
|
|
347
|
-
|
|
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];
|
|
348
760
|
cc++;
|
|
349
761
|
}
|
|
350
|
-
if (cc
|
|
351
|
-
const
|
|
352
|
-
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
|
+
}
|
|
353
769
|
}
|
|
770
|
+
text = clipToWidth(text, availTwips * 1.1, charTwips(xf, styles, textTwipsUnit));
|
|
354
771
|
if (cc > c + 1) {
|
|
355
|
-
overflowSpan =
|
|
772
|
+
overflowSpan = 0;
|
|
773
|
+
for (let k = c; k < cc; k++) if (!hiddenCols.has(k)) overflowSpan++;
|
|
356
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
|
+
}
|
|
357
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));
|
|
358
787
|
}
|
|
359
788
|
if (shrinkToFit && ws && text.length > 0) {
|
|
360
|
-
const
|
|
361
|
-
|
|
789
|
+
const drawnTwips = estimateChars(text) / charWidthUnits("0") * charTwips(xf, styles, textTwipsUnit);
|
|
790
|
+
const room = columnWidths[c];
|
|
791
|
+
if (drawnTwips > room) {
|
|
362
792
|
const baseHalfPt = (styles.fonts[xf.fontId]?.sizePt ?? 11) * 2;
|
|
363
|
-
const scaledHalfPt = Math.max(2, Math.
|
|
793
|
+
const scaledHalfPt = Math.max(2, Math.floor(baseHalfPt * room / drawnTwips));
|
|
364
794
|
runProps = {
|
|
365
795
|
...runProps,
|
|
366
796
|
fontSizePt: halfPtToPt(scaledHalfPt)
|
|
@@ -370,15 +800,28 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
370
800
|
const dropdown = dropdownRanges.length > 0 && rangesCover(dropdownRanges, absR, absC);
|
|
371
801
|
const href = print.hyperlinks && print.hyperlinks.length > 0 ? hyperlinkUrlAt(print.hyperlinks, absR, absC) : void 0;
|
|
372
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
|
+
}
|
|
373
807
|
const properties = {
|
|
374
|
-
...
|
|
808
|
+
...mergeSpan > 1 ? { colSpan: mergeSpan } : overflowSpan > 1 ? {
|
|
809
|
+
colSpan: overflowSpan,
|
|
810
|
+
paintColumns
|
|
811
|
+
} : {},
|
|
375
812
|
...merge && Math.min(merge.endRow, rowWindowEnd) > merge.startRow ? { merge: "start" } : {},
|
|
376
813
|
...shading ? { shading } : {},
|
|
377
814
|
...dataBar ? { dataBar } : {},
|
|
378
815
|
...icon ? { icon } : {},
|
|
379
816
|
...sparkline ? { sparkline } : {},
|
|
380
|
-
...borders ? { borders
|
|
381
|
-
|
|
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)
|
|
382
825
|
};
|
|
383
826
|
const indentLevels = xf?.alignment?.indent ?? 0;
|
|
384
827
|
const baseParaProps = cellParaProps(alignment);
|
|
@@ -396,29 +839,49 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
396
839
|
properties: runProps,
|
|
397
840
|
...href ? { href } : {}
|
|
398
841
|
}] : [];
|
|
399
|
-
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) => ({
|
|
400
843
|
kind: "paragraph",
|
|
401
844
|
paragraph: {
|
|
402
845
|
properties: paragraphProps,
|
|
403
|
-
runs
|
|
846
|
+
runs
|
|
404
847
|
}
|
|
405
|
-
}
|
|
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);
|
|
406
857
|
cells.push({
|
|
407
858
|
properties,
|
|
408
859
|
content
|
|
409
860
|
});
|
|
410
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
|
+
});
|
|
411
872
|
const baseRowProps = rowHeightMap.get(r);
|
|
412
873
|
const rowHeightTwips = scaled && baseRowProps !== void 0 ? Math.max(1, Math.round(baseRowProps.heightTwips * printScale)) : baseRowProps?.heightTwips;
|
|
413
874
|
const isTitleRow = print.titleRows !== void 0 && absR >= print.titleRows.startRow && absR <= print.titleRows.endRow;
|
|
414
875
|
const rowProps = {
|
|
415
876
|
...rowHeightTwips !== void 0 ? {
|
|
416
877
|
height: twipsToPt(rowHeightTwips),
|
|
417
|
-
heightRule: "atLeast"
|
|
878
|
+
heightRule: baseRowProps?.heightRule ?? "atLeast"
|
|
418
879
|
} : {},
|
|
419
880
|
...isTitleRow ? { isHeader: true } : {},
|
|
420
881
|
...breakRows.has(absR) ? { pageBreakBefore: true } : {}
|
|
421
882
|
};
|
|
883
|
+
if (isTitleRow && titleRowIndex < 0) titleRowIndex = rows.length;
|
|
884
|
+
rowNumbers.push(absR + 1);
|
|
422
885
|
rows.push({
|
|
423
886
|
properties: rowProps,
|
|
424
887
|
cells
|
|
@@ -426,13 +889,14 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
426
889
|
}
|
|
427
890
|
const thin = {
|
|
428
891
|
style: "single",
|
|
429
|
-
width: eighthPtToPt(
|
|
892
|
+
width: eighthPtToPt(2),
|
|
893
|
+
colorHex: PRINT_GRIDLINE_HEX
|
|
430
894
|
};
|
|
431
895
|
const centered = worksheet.printOptions?.horizontalCentered === true;
|
|
432
896
|
const tableProperties = {
|
|
433
897
|
defaultCellMargins: {
|
|
434
|
-
left: pt(EXCEL_CELL_INSET_PT),
|
|
435
|
-
right: pt(EXCEL_CELL_INSET_PT),
|
|
898
|
+
left: pt(EXCEL_CELL_INSET_PT * printScale),
|
|
899
|
+
right: pt(EXCEL_CELL_INSET_PT * printScale),
|
|
436
900
|
top: pt(0),
|
|
437
901
|
bottom: pt(0)
|
|
438
902
|
},
|
|
@@ -454,16 +918,42 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
454
918
|
if (local > 0 && local < colCount) colBreaksLocal.add(local);
|
|
455
919
|
}
|
|
456
920
|
const fitWide = worksheet.fitToPage ? worksheet.pageSetup?.fitToWidth ?? 1 : 1;
|
|
457
|
-
const bandWidths =
|
|
921
|
+
const bandWidths = scaledColumnWidths(visibleWidths, printScale, scaled);
|
|
458
922
|
const bandTotal = bandWidths.reduce((sum, w) => sum + w, 0);
|
|
459
|
-
if (colCount > 1 && (
|
|
923
|
+
if (colCount > 1 && bandsAcross(worksheet, fitWide) && (bandTotal > contentWidthTwips || colBreaksLocal.size > 0)) {
|
|
460
924
|
const bands = computeColumnBands(bandWidths, contentWidthTwips, colBreaksLocal);
|
|
461
|
-
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
|
+
}
|
|
462
932
|
}
|
|
463
933
|
const frozen = worksheet.pane && (worksheet.pane.frozenRows > 0 || worksheet.pane.frozenCols > 0) ? {
|
|
464
934
|
rows: worksheet.pane.frozenRows,
|
|
465
935
|
cols: worksheet.pane.frozenCols
|
|
466
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;
|
|
467
957
|
return [{
|
|
468
958
|
kind: "table",
|
|
469
959
|
table: {
|
|
@@ -471,20 +961,22 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
|
|
|
471
961
|
...tableProperties,
|
|
472
962
|
frozen
|
|
473
963
|
} : tableProperties,
|
|
474
|
-
grid: bandWidths.map((w) => twipsToPt(w)),
|
|
475
|
-
rows
|
|
964
|
+
grid: (headed?.widths ?? bandWidths).map((w) => twipsToPt(w)),
|
|
965
|
+
rows: headed?.rows ?? rows
|
|
476
966
|
}
|
|
477
967
|
}];
|
|
478
968
|
}
|
|
479
969
|
function verticalMergeParent(merges, row, column) {
|
|
480
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;
|
|
481
971
|
}
|
|
482
|
-
function makeVerticalContinuation(merge, colWindowEnd, absR, rowWindowEnd) {
|
|
972
|
+
function makeVerticalContinuation(merge, colWindowEnd, absR, rowWindowEnd, paint) {
|
|
483
973
|
const span = Math.min(merge.endColumn, colWindowEnd) - merge.startColumn + 1;
|
|
484
974
|
return {
|
|
485
975
|
properties: {
|
|
486
976
|
merge: absR < Math.min(merge.endRow, rowWindowEnd) ? "middle" : "end",
|
|
487
|
-
...span > 1 ? { colSpan: span } : {}
|
|
977
|
+
...span > 1 ? { colSpan: span } : {},
|
|
978
|
+
...paint.shading ? { shading: paint.shading } : {},
|
|
979
|
+
...paint.borders ? { borders: paint.borders } : {}
|
|
488
980
|
},
|
|
489
981
|
content: [{
|
|
490
982
|
kind: "paragraph",
|
|
@@ -515,16 +1007,17 @@ function resolveCellText(cell, sharedStrings, styles, date1904) {
|
|
|
515
1007
|
}
|
|
516
1008
|
if (cell.type === "b") return cell.rawValue === "1" ? "TRUE" : "FALSE";
|
|
517
1009
|
if (cell.type === "str" || cell.type === "e" || cell.type === "d") return cell.rawValue;
|
|
518
|
-
const numFmtId =
|
|
1010
|
+
const numFmtId = styles.cellXfs[cell.styleIndex ?? 0]?.numFmtId ?? 0;
|
|
519
1011
|
return applyNumberFormat(cell.rawValue, numFmtId, styles.numFmts, date1904);
|
|
520
1012
|
}
|
|
521
1013
|
function runPropsFromXf(xf, styles) {
|
|
522
|
-
if (!xf.applyFont && xf.fontId === 0) return {};
|
|
523
1014
|
const font = styles.fonts[xf.fontId];
|
|
524
1015
|
if (!font) return {};
|
|
525
1016
|
const props = {};
|
|
526
1017
|
if (font.bold) props.bold = true;
|
|
527
1018
|
if (font.italic) props.italic = true;
|
|
1019
|
+
if (font.underline) props.underline = "single";
|
|
1020
|
+
if (font.strike) props.strike = true;
|
|
528
1021
|
if (font.sizePt !== void 0) props.fontSizePt = halfPtToPt(Math.round(font.sizePt * 2));
|
|
529
1022
|
if (font.colorHex) props.colorHex = font.colorHex;
|
|
530
1023
|
return props;
|
|
@@ -546,9 +1039,10 @@ function stackedVerticalContent(text, runProps, href) {
|
|
|
546
1039
|
}
|
|
547
1040
|
function richRunProps(base, rr) {
|
|
548
1041
|
const out = { ...base };
|
|
549
|
-
if (rr.bold) out.bold =
|
|
550
|
-
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;
|
|
551
1044
|
if (rr.underline) out.underline = "single";
|
|
1045
|
+
if (rr.strike !== void 0) out.strike = rr.strike;
|
|
552
1046
|
if (rr.colorHex) out.colorHex = rr.colorHex;
|
|
553
1047
|
if (rr.sizePt !== void 0) out.fontSizePt = halfPtToPt(Math.round(rr.sizePt * 2));
|
|
554
1048
|
if (rr.vertAlign) out.verticalAlign = rr.vertAlign;
|
|
@@ -559,7 +1053,8 @@ function applyCfOverride(base, o) {
|
|
|
559
1053
|
...base,
|
|
560
1054
|
...o.fontColorHex ? { colorHex: o.fontColorHex } : {},
|
|
561
1055
|
...o.bold !== void 0 ? { bold: o.bold } : {},
|
|
562
|
-
...o.italic !== void 0 ? { italic: o.italic } : {}
|
|
1056
|
+
...o.italic !== void 0 ? { italic: o.italic } : {},
|
|
1057
|
+
...o.strike !== void 0 ? { strike: o.strike } : {}
|
|
563
1058
|
};
|
|
564
1059
|
}
|
|
565
1060
|
/**
|
|
@@ -665,6 +1160,42 @@ function bordersFromXf(xf, styles) {
|
|
|
665
1160
|
}
|
|
666
1161
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
667
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
|
+
}
|
|
668
1199
|
function mapBorderEdge(edge) {
|
|
669
1200
|
if (!edge || !edge.style || edge.style === "none") return void 0;
|
|
670
1201
|
const { style, sizeEighthPt } = mapBorderStyle(edge.style);
|
|
@@ -678,41 +1209,56 @@ function mapBorderStyle(style) {
|
|
|
678
1209
|
switch (style) {
|
|
679
1210
|
case "hair": return {
|
|
680
1211
|
style: "single",
|
|
681
|
-
sizeEighthPt:
|
|
1212
|
+
sizeEighthPt: 3
|
|
682
1213
|
};
|
|
683
1214
|
case "thin": return {
|
|
684
1215
|
style: "single",
|
|
685
|
-
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
|
|
686
1225
|
};
|
|
687
|
-
case "medium":
|
|
688
|
-
case "mediumDashed":
|
|
689
1226
|
case "mediumDashDot":
|
|
1227
|
+
case "slantDashDot": return {
|
|
1228
|
+
style: "dashDot",
|
|
1229
|
+
sizeEighthPt: 12
|
|
1230
|
+
};
|
|
690
1231
|
case "mediumDashDotDot": return {
|
|
691
|
-
style: "
|
|
692
|
-
sizeEighthPt:
|
|
1232
|
+
style: "dashDotDot",
|
|
1233
|
+
sizeEighthPt: 12
|
|
693
1234
|
};
|
|
694
1235
|
case "thick": return {
|
|
695
1236
|
style: "thick",
|
|
696
|
-
sizeEighthPt:
|
|
1237
|
+
sizeEighthPt: 18
|
|
697
1238
|
};
|
|
698
|
-
case "dashed":
|
|
699
|
-
case "dashDot":
|
|
700
|
-
case "dashDotDot":
|
|
701
|
-
case "slantDashDot": return {
|
|
1239
|
+
case "dashed": return {
|
|
702
1240
|
style: "dashed",
|
|
703
|
-
sizeEighthPt:
|
|
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
|
|
704
1250
|
};
|
|
705
1251
|
case "dotted": return {
|
|
706
1252
|
style: "dotted",
|
|
707
|
-
sizeEighthPt:
|
|
1253
|
+
sizeEighthPt: 6
|
|
708
1254
|
};
|
|
709
1255
|
case "double": return {
|
|
710
1256
|
style: "double",
|
|
711
|
-
sizeEighthPt:
|
|
1257
|
+
sizeEighthPt: 18
|
|
712
1258
|
};
|
|
713
1259
|
default: return {
|
|
714
1260
|
style: "single",
|
|
715
|
-
sizeEighthPt:
|
|
1261
|
+
sizeEighthPt: 6
|
|
716
1262
|
};
|
|
717
1263
|
}
|
|
718
1264
|
}
|
|
@@ -887,12 +1433,290 @@ function lastContentColumn(worksheet, rowStart, rowCount, colStart, colCount) {
|
|
|
887
1433
|
* Such a cell cannot be swallowed by a neighbour's overflowing text: the span
|
|
888
1434
|
* that gives the text its width would take the paint with it.
|
|
889
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
|
+
}
|
|
890
1458
|
function cellPaintsSomething(cell, styles) {
|
|
891
1459
|
if (!cell || cell.styleIndex === void 0) return false;
|
|
892
1460
|
const xf = styles.cellXfs[cell.styleIndex];
|
|
893
1461
|
if (!xf) return false;
|
|
894
1462
|
return shadingFromXf(xf, styles) !== void 0 || bordersFromXf(xf, styles) !== void 0;
|
|
895
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
|
+
}
|
|
896
1720
|
var SLICER_WIDTH_PT = 108;
|
|
897
1721
|
var SLICER_ROW_PT = 16;
|
|
898
1722
|
var SLICER_UNSELECTED_HEX = "F2F2F2";
|
|
@@ -907,7 +1731,8 @@ function slicerTable(slicer) {
|
|
|
907
1731
|
const colWidthPt = SLICER_WIDTH_PT / cols;
|
|
908
1732
|
const thin = {
|
|
909
1733
|
style: "single",
|
|
910
|
-
width: eighthPtToPt(
|
|
1734
|
+
width: eighthPtToPt(2),
|
|
1735
|
+
colorHex: PRINT_GRIDLINE_HEX
|
|
911
1736
|
};
|
|
912
1737
|
const rowProps = {
|
|
913
1738
|
height: pt(SLICER_ROW_PT),
|
|
@@ -976,4 +1801,4 @@ function slicerParagraph(text, runProps) {
|
|
|
976
1801
|
};
|
|
977
1802
|
}
|
|
978
1803
|
//#endregion
|
|
979
|
-
export { resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
|
|
1804
|
+
export { cellPaintsVisibly, columnTwips, printableHeightPt, printableWidthPt, resolveCellText, resolvePrintArea, resolvePrintTitleRows, sectionFromWorksheet, slicerTable, worksheetToBody };
|