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.
Files changed (95) hide show
  1. package/dist/esm/core/converter/facade.d.ts +5 -0
  2. package/dist/esm/core/converter/ream.d.ts +6 -0
  3. package/dist/esm/core/converter/ream.js +34 -8
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +187 -1
  6. package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
  7. package/dist/esm/core/drawingml/chart-geometry.js +371 -88
  8. package/dist/esm/core/drawingml/chart-parser.js +262 -11
  9. package/dist/esm/core/drawingml/colors.d.ts +6 -6
  10. package/dist/esm/core/drawingml/colors.js +58 -18
  11. package/dist/esm/core/drawingml/preset-geometry.js +7 -6
  12. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +74 -1
  15. package/dist/esm/core/font/ttf-subset.js +26 -1
  16. package/dist/esm/core/images.js +172 -43
  17. package/dist/esm/core/indexed-colors.d.ts +11 -0
  18. package/dist/esm/core/indexed-colors.js +81 -0
  19. package/dist/esm/core/ir/sheet.d.ts +67 -3
  20. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  21. package/dist/esm/core/number-format.js +800 -0
  22. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  23. package/dist/esm/core/opc/alternate-content.js +72 -0
  24. package/dist/esm/core/opc/core-properties.js +1 -0
  25. package/dist/esm/core/opc/package.d.ts +14 -1
  26. package/dist/esm/core/opc/package.js +43 -4
  27. package/dist/esm/core/opc/relationships.js +1 -0
  28. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  29. package/dist/esm/core/opc/xml-entities.js +91 -0
  30. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  31. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  32. package/dist/esm/core/vector.d.ts +13 -0
  33. package/dist/esm/excel/activex-parser.d.ts +9 -0
  34. package/dist/esm/excel/activex-parser.js +16 -1
  35. package/dist/esm/excel/column-bands.d.ts +26 -1
  36. package/dist/esm/excel/column-bands.js +156 -20
  37. package/dist/esm/excel/comments-parser.js +5 -3
  38. package/dist/esm/excel/conditional-format.d.ts +25 -2
  39. package/dist/esm/excel/conditional-format.js +63 -19
  40. package/dist/esm/excel/escaped-text.d.ts +9 -0
  41. package/dist/esm/excel/escaped-text.js +20 -0
  42. package/dist/esm/excel/form-control-parser.js +1 -0
  43. package/dist/esm/excel/formula/dates.js +1 -1
  44. package/dist/esm/excel/header-footer.d.ts +9 -1
  45. package/dist/esm/excel/header-footer.js +162 -14
  46. package/dist/esm/excel/index.d.ts +1 -1
  47. package/dist/esm/excel/index.js +2 -2
  48. package/dist/esm/excel/pivot-table-parser.js +1 -0
  49. package/dist/esm/excel/print-model.d.ts +75 -0
  50. package/dist/esm/excel/print-model.js +902 -77
  51. package/dist/esm/excel/printer-settings.d.ts +16 -0
  52. package/dist/esm/excel/printer-settings.js +36 -0
  53. package/dist/esm/excel/rich-value.d.ts +13 -0
  54. package/dist/esm/excel/rich-value.js +120 -0
  55. package/dist/esm/excel/shared-strings-parser.js +11 -7
  56. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  57. package/dist/esm/excel/sheet-drawing.js +26 -3
  58. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  59. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  60. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  61. package/dist/esm/excel/sheet-to-flow.js +551 -36
  62. package/dist/esm/excel/slicer-parser.js +1 -0
  63. package/dist/esm/excel/styles-parser.d.ts +30 -2
  64. package/dist/esm/excel/styles-parser.js +177 -40
  65. package/dist/esm/excel/table-parser.js +1 -0
  66. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  67. package/dist/esm/excel/vml-drawing.js +158 -0
  68. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  69. package/dist/esm/excel/workbook-parser.js +5 -1
  70. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  71. package/dist/esm/excel/worksheet-parser.js +149 -28
  72. package/dist/esm/excel/xlsx-reader.js +271 -33
  73. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  74. package/dist/esm/html/html-writer.js +11 -2
  75. package/dist/esm/layout/page-doc.d.ts +26 -0
  76. package/dist/esm/layout/styled-layout.d.ts +2 -0
  77. package/dist/esm/layout/styled-layout.js +493 -64
  78. package/dist/esm/pdf/cid-font.js +20 -9
  79. package/dist/esm/pdf/objects.d.ts +13 -0
  80. package/dist/esm/pdf/objects.js +18 -1
  81. package/dist/esm/pdf/styled-page-emitter.js +164 -32
  82. package/dist/esm/pdf/text-page-renderer.js +1 -0
  83. package/dist/esm/pdf/vector-graphics.d.ts +3 -1
  84. package/dist/esm/pdf/vector-graphics.js +19 -2
  85. package/dist/esm/pptx/pptx-reader.js +1 -0
  86. package/dist/esm/pptx/slide-parser.js +12 -1
  87. package/dist/esm/svg/svg-writer.js +23 -4
  88. package/dist/esm/word/document-parser.js +1 -0
  89. package/dist/esm/word/drawing-parser.d.ts +34 -1
  90. package/dist/esm/word/drawing-parser.js +52 -2
  91. package/dist/esm/word/numbering-parser.js +1 -0
  92. package/dist/esm/word/settings-parser.js +1 -0
  93. package/dist/esm/word/styles-parser.js +1 -0
  94. package/package.json +7 -3
  95. package/dist/esm/excel/number-format.js +0 -476
@@ -1,11 +1,39 @@
1
- import { XlsxStyles } from '../core/spreadsheet-model/index.js';
1
+ import { Dxf, XlsxStyles } from '../core/spreadsheet-model/index.js';
2
+ import { INDEXED_COLORS } from '../core/indexed-colors.js';
3
+ export { INDEXED_COLORS };
2
4
  /** The empty style table, returned for a workbook with no (or a malformed) `styles.xml`. */
3
5
  export declare const EMPTY_XLSX_STYLES: XlsxStyles;
6
+ /** A workbook theme's colour scheme, slot name → RRGGBB (see `parseTheme`). */
7
+ export type ThemePalette = ReadonlyMap<string, string>;
8
+ /**
9
+ * Everything a `<color>` in this workbook can point AT: the theme's scheme
10
+ * (§18.8.3 `theme`) and the legacy palette (§18.8.27 `indexed`), which the
11
+ * workbook may replace with one of its own.
12
+ */
13
+ export interface WorkbookColors {
14
+ readonly theme?: ThemePalette;
15
+ readonly indexed: ReadonlyArray<string>;
16
+ }
4
17
  /**
5
18
  * Parse `xl/styles.xml` (§18.8) into the {@link XlsxStyles} table the renderer
6
19
  * consumes: custom number formats, fonts, fills, borders, the cell formats
7
20
  * (`cellXfs` — each indexing font/fill/border/numFmt + optional alignment) and the
8
21
  * differential formats (`dxfs`) conditional formatting applies. Returns
9
22
  * {@link EMPTY_XLSX_STYLES} when the root is absent or malformed.
23
+ *
24
+ * @param theme The workbook's theme palette, resolving `<color theme="N">`.
25
+ * Without it those colours are dropped, and a sheet styled from the
26
+ * theme loses every fill and font colour it has.
27
+ */
28
+ export declare function parseXlsxStyles(data: Uint8Array, theme?: ThemePalette): XlsxStyles;
29
+ /**
30
+ * Read one §18.8.14 `<dxf>` — the differential format a conditional-format rule
31
+ * applies on match. Exported because the 2009 extension writes its dxf INSIDE
32
+ * the rule (`<x14:dxf>`) rather than into the workbook's `<dxfs>` table, and
33
+ * the sheet reader has to resolve it there.
34
+ *
35
+ * @param item The `<dxf>` node, as parsed.
36
+ * @param colors The workbook palette, resolving theme and indexed colours.
37
+ * @returns The format; empty when the node states nothing.
10
38
  */
11
- export declare function parseXlsxStyles(data: Uint8Array): XlsxStyles;
39
+ export declare function parseDxf(item: unknown, colors: WorkbookColors): Dxf;
@@ -1,7 +1,12 @@
1
+ import { INDEXED_COLORS } from "../core/indexed-colors.js";
2
+ import { applyColorMods } from "../core/drawingml/colors.js";
3
+ import { resolveInternalEntities } from "../core/opc/xml-entities.js";
4
+ import { resolveAlternateContent } from "../core/opc/alternate-content.js";
1
5
  import { XMLParser } from "fast-xml-parser";
2
6
  //#region src/excel/styles-parser.ts
3
7
  var decoder = new TextDecoder("utf-8");
4
8
  var parser = new XMLParser({
9
+ htmlEntities: true,
5
10
  ignoreAttributes: false,
6
11
  attributeNamePrefix: "@_",
7
12
  textNodeName: "#text",
@@ -24,38 +29,56 @@ var EMPTY_XLSX_STYLES = {
24
29
  * (`cellXfs` — each indexing font/fill/border/numFmt + optional alignment) and the
25
30
  * differential formats (`dxfs`) conditional formatting applies. Returns
26
31
  * {@link EMPTY_XLSX_STYLES} when the root is absent or malformed.
32
+ *
33
+ * @param theme The workbook's theme palette, resolving `<color theme="N">`.
34
+ * Without it those colours are dropped, and a sheet styled from the
35
+ * theme loses every fill and font colour it has.
27
36
  */
28
- function parseXlsxStyles(data) {
29
- const xml = decoder.decode(data);
37
+ function parseXlsxStyles(data, theme) {
38
+ const xml = resolveAlternateContent(resolveInternalEntities(decoder.decode(data)));
30
39
  const root = asObject(parser.parse(xml)["styleSheet"]);
31
40
  if (!root) return EMPTY_XLSX_STYLES;
32
- const dxfs = parseDxfs(root);
41
+ const colors = {
42
+ ...theme ? { theme } : {},
43
+ indexed: parseIndexedColors(root)
44
+ };
45
+ const dxfs = parseDxfs(root, colors);
33
46
  return {
34
47
  numFmts: parseNumFmts(root),
35
- fonts: parseFonts(root),
36
- fills: parseFills(root),
37
- borders: parseBorders(root),
48
+ fonts: parseFonts(root, colors),
49
+ fills: parseFills(root, colors),
50
+ borders: parseBorders(root, colors),
38
51
  cellXfs: parseCellXfs(root),
39
52
  ...dxfs.length > 0 ? { dxfs } : {}
40
53
  };
41
54
  }
42
- function parseDxfs(root) {
55
+ function parseDxfs(root, colors) {
43
56
  const node = asObject(root["dxfs"]);
44
57
  if (!node) return [];
45
- const out = [];
46
- for (const item of asArray(node["dxf"])) {
58
+ return asArray(node["dxf"]).map((item) => parseDxf(item, colors));
59
+ }
60
+ /**
61
+ * Read one §18.8.14 `<dxf>` — the differential format a conditional-format rule
62
+ * applies on match. Exported because the 2009 extension writes its dxf INSIDE
63
+ * the rule (`<x14:dxf>`) rather than into the workbook's `<dxfs>` table, and
64
+ * the sheet reader has to resolve it there.
65
+ *
66
+ * @param item The `<dxf>` node, as parsed.
67
+ * @param colors The workbook palette, resolving theme and indexed colours.
68
+ * @returns The format; empty when the node states nothing.
69
+ */
70
+ function parseDxf(item, colors) {
71
+ {
47
72
  const obj = asObject(item);
48
- if (!obj) {
49
- out.push({});
50
- continue;
51
- }
73
+ if (!obj) return {};
52
74
  const dxf = {};
53
75
  const fontObj = asObject(obj["font"]);
54
76
  if (fontObj) {
55
77
  const font = {};
56
78
  if (hasChild(fontObj, "b")) font.bold = childToggle(fontObj, "b");
57
79
  if (hasChild(fontObj, "i")) font.italic = childToggle(fontObj, "i");
58
- const colorHex = colorOf(asObject(fontObj["color"]));
80
+ if (hasChild(fontObj, "strike")) font.strike = childToggle(fontObj, "strike");
81
+ const colorHex = colorOf(asObject(fontObj["color"]), colors);
59
82
  if (colorHex) font.colorHex = colorHex;
60
83
  if (Object.keys(font).length > 0) dxf.font = font;
61
84
  }
@@ -64,15 +87,31 @@ function parseDxfs(root) {
64
87
  const fill = {};
65
88
  const pt = strAttr(pf, "patternType");
66
89
  if (pt) fill.patternType = pt;
67
- const fg = colorOf(asObject(pf["fgColor"]));
68
- const bg = colorOf(asObject(pf["bgColor"]));
90
+ const fg = colorOf(asObject(pf["fgColor"]), colors);
91
+ const bg = colorOf(asObject(pf["bgColor"]), colors);
69
92
  if (fg) fill.fgColorHex = fg;
70
93
  if (bg) fill.bgColorHex = bg;
71
94
  if (Object.keys(fill).length > 0) dxf.fill = fill;
72
95
  }
73
- out.push(dxf);
96
+ const numFmtObj = asObject(obj["numFmt"]);
97
+ const code = numFmtObj ? strAttr(numFmtObj, "formatCode") : void 0;
98
+ if (code !== void 0 && code.length > 0) dxf.numberFormat = code;
99
+ const borderObj = asObject(obj["border"]);
100
+ if (borderObj) {
101
+ const border = {};
102
+ for (const side of [
103
+ "top",
104
+ "right",
105
+ "bottom",
106
+ "left"
107
+ ]) {
108
+ const edge = parseBorderEdge(asObject(borderObj[side]), colors);
109
+ if (edge) border[side] = edge;
110
+ }
111
+ if (Object.keys(border).length > 0) dxf.border = border;
112
+ }
113
+ return dxf;
74
114
  }
75
- return out;
76
115
  }
77
116
  var VALID_BORDER_STYLES = new Set([
78
117
  "none",
@@ -90,7 +129,7 @@ var VALID_BORDER_STYLES = new Set([
90
129
  "mediumDashDotDot",
91
130
  "slantDashDot"
92
131
  ]);
93
- function parseBorders(root) {
132
+ function parseBorders(root, colors) {
94
133
  const node = asObject(root["borders"]);
95
134
  if (!node) return [];
96
135
  const out = [];
@@ -101,15 +140,15 @@ function parseBorders(root) {
101
140
  continue;
102
141
  }
103
142
  const border = {};
104
- const top = parseBorderEdge(asObject(obj["top"]));
105
- const right = parseBorderEdge(asObject(obj["right"]));
106
- const bottom = parseBorderEdge(asObject(obj["bottom"]));
107
- const left = parseBorderEdge(asObject(obj["left"]));
143
+ const top = parseBorderEdge(asObject(obj["top"]), colors);
144
+ const right = parseBorderEdge(asObject(obj["right"]), colors);
145
+ const bottom = parseBorderEdge(asObject(obj["bottom"]), colors);
146
+ const left = parseBorderEdge(asObject(obj["left"]), colors);
108
147
  if (top) border.top = top;
109
148
  if (right) border.right = right;
110
149
  if (bottom) border.bottom = bottom;
111
150
  if (left) border.left = left;
112
- const diagonal = parseBorderEdge(asObject(obj["diagonal"]));
151
+ const diagonal = parseBorderEdge(asObject(obj["diagonal"]), colors);
113
152
  if (diagonal) {
114
153
  border.diagonal = diagonal;
115
154
  if (boolAttr(obj, "diagonalUp")) border.diagonalUp = true;
@@ -119,12 +158,12 @@ function parseBorders(root) {
119
158
  }
120
159
  return out;
121
160
  }
122
- function parseBorderEdge(node) {
161
+ function parseBorderEdge(node, colors) {
123
162
  if (!node) return void 0;
124
163
  const styleRaw = strAttr(node, "style");
125
164
  if (!styleRaw) return void 0;
126
165
  if (!VALID_BORDER_STYLES.has(styleRaw)) return void 0;
127
- const colorHex = colorOf(asObject(node["color"]));
166
+ const colorHex = colorOf(asObject(node["color"]), colors);
128
167
  const edge = { style: styleRaw };
129
168
  if (colorHex) edge.colorHex = colorHex;
130
169
  return edge;
@@ -142,13 +181,16 @@ function parseNumFmts(root) {
142
181
  }
143
182
  return result;
144
183
  }
145
- function parseFonts(root) {
184
+ function parseFonts(root, colors) {
146
185
  const node = asObject(root["fonts"]);
147
186
  if (!node) return [];
148
187
  const out = [];
149
188
  for (const item of asArray(node["font"])) {
150
189
  const obj = asObject(item);
151
- if (!obj) continue;
190
+ if (!obj) {
191
+ out.push({});
192
+ continue;
193
+ }
152
194
  const font = {};
153
195
  const sz = childValAttr(obj, "sz");
154
196
  if (sz !== void 0) {
@@ -157,8 +199,9 @@ function parseFonts(root) {
157
199
  }
158
200
  if (hasChild(obj, "b")) font.bold = childToggle(obj, "b");
159
201
  if (hasChild(obj, "i")) font.italic = childToggle(obj, "i");
160
- if (hasChild(obj, "u")) font.underline = childToggle(obj, "u");
161
- const colorRgb = colorOf(asObject(obj["color"]));
202
+ if (hasChild(obj, "u")) font.underline = childUnderline(obj);
203
+ if (hasChild(obj, "strike")) font.strike = childToggle(obj, "strike");
204
+ const colorRgb = colorOf(asObject(obj["color"]), colors);
162
205
  if (colorRgb) font.colorHex = colorRgb;
163
206
  const nameVal = childValAttr(obj, "name");
164
207
  if (nameVal) font.name = nameVal;
@@ -166,25 +209,28 @@ function parseFonts(root) {
166
209
  }
167
210
  return out;
168
211
  }
169
- function parseFills(root) {
212
+ function parseFills(root, colors) {
170
213
  const node = asObject(root["fills"]);
171
214
  if (!node) return [];
172
215
  const out = [];
173
216
  for (const item of asArray(node["fill"])) {
174
217
  const obj = asObject(item);
175
- if (!obj) continue;
218
+ if (!obj) {
219
+ out.push({});
220
+ continue;
221
+ }
176
222
  const fill = {};
177
223
  const pf = asObject(obj["patternFill"]);
178
224
  if (pf) {
179
225
  const pt = strAttr(pf, "patternType");
180
226
  if (pt) fill.patternType = pt;
181
- const fg = colorOf(asObject(pf["fgColor"]));
182
- const bg = colorOf(asObject(pf["bgColor"]));
227
+ const fg = colorOf(asObject(pf["fgColor"]), colors);
228
+ const bg = colorOf(asObject(pf["bgColor"]), colors);
183
229
  if (fg) fill.fgColorHex = fg;
184
230
  if (bg) fill.bgColorHex = bg;
185
231
  }
186
232
  if (!fill.patternType) {
187
- const avg = averageGradientColor(asObject(obj["gradientFill"]));
233
+ const avg = averageGradientColor(asObject(obj["gradientFill"]), colors);
188
234
  if (avg) {
189
235
  fill.patternType = "solid";
190
236
  fill.fgColorHex = avg;
@@ -200,7 +246,15 @@ function parseCellXfs(root) {
200
246
  const out = [];
201
247
  for (const item of asArray(node["xf"])) {
202
248
  const obj = asObject(item);
203
- if (!obj) continue;
249
+ if (!obj) {
250
+ out.push({
251
+ numFmtId: 0,
252
+ fontId: 0,
253
+ fillId: 0,
254
+ borderId: 0
255
+ });
256
+ continue;
257
+ }
204
258
  const xf = {
205
259
  numFmtId: numAttr(obj, "numFmtId") ?? 0,
206
260
  fontId: numAttr(obj, "fontId") ?? 0,
@@ -268,6 +322,14 @@ function childValAttr(obj, childName) {
268
322
  function hasChild(obj, childName) {
269
323
  return childName in obj;
270
324
  }
325
+ function childUnderline(obj) {
326
+ const child = obj["u"];
327
+ if (child === "" || child === null || child === void 0) return true;
328
+ if (typeof child !== "object") return true;
329
+ const val = strAttr(child, "val");
330
+ if (val === void 0) return true;
331
+ return !(val === "none" || val === "false" || val === "0");
332
+ }
271
333
  function childToggle(obj, childName) {
272
334
  const child = obj[childName];
273
335
  if (child === "" || child === null || child === void 0) return true;
@@ -276,7 +338,7 @@ function childToggle(obj, childName) {
276
338
  if (val === void 0) return true;
277
339
  return !(val === "false" || val === "0");
278
340
  }
279
- function averageGradientColor(gf) {
341
+ function averageGradientColor(gf, colors) {
280
342
  if (!gf) return void 0;
281
343
  let r = 0;
282
344
  let g = 0;
@@ -284,7 +346,7 @@ function averageGradientColor(gf) {
284
346
  let n = 0;
285
347
  for (const s of asArray(gf["stop"])) {
286
348
  const so = asObject(s);
287
- const hex = so ? colorOf(asObject(so["color"])) : void 0;
349
+ const hex = so ? colorOf(asObject(so["color"]), colors) : void 0;
288
350
  if (!hex) continue;
289
351
  r += parseInt(hex.slice(0, 2), 16);
290
352
  g += parseInt(hex.slice(2, 4), 16);
@@ -295,13 +357,88 @@ function averageGradientColor(gf) {
295
357
  const h = (x) => Math.round(x / n).toString(16).padStart(2, "0").toUpperCase();
296
358
  return `${h(r)}${h(g)}${h(b)}`;
297
359
  }
298
- function colorOf(node) {
360
+ function colorOf(node, colors) {
299
361
  if (!node) return void 0;
300
362
  const rgb = strAttr(node, "rgb");
301
363
  if (rgb) {
302
364
  if (/^[0-9A-Fa-f]{8}$/.test(rgb)) return rgb.substring(2).toUpperCase();
303
365
  if (/^[0-9A-Fa-f]{6}$/.test(rgb)) return rgb.toUpperCase();
304
366
  }
367
+ const indexed = strAttr(node, "indexed");
368
+ if (indexed !== void 0) {
369
+ const i = Number(indexed);
370
+ if (Number.isInteger(i) && i >= 0 && i < colors.indexed.length) return colors.indexed[i];
371
+ }
372
+ const themeIdx = strAttr(node, "theme");
373
+ if (themeIdx !== void 0 && colors.theme) {
374
+ const slot = THEME_SLOTS[Number(themeIdx)];
375
+ const base = slot ? colors.theme.get(slot) : void 0;
376
+ if (base) return applyTint(base, Number(strAttr(node, "tint") ?? "0"));
377
+ }
378
+ }
379
+ /** §18.8.27 — the indexed palette proper; 56 and 57+ are system colours. */
380
+ var PALETTE_ENTRIES = 56;
381
+ /**
382
+ * §18.8.27 `<colors><indexedColors>` — the workbook's OWN legacy palette. It
383
+ * replaces the built-in one wholesale (Excel writes all 56 entries), and a
384
+ * workbook that ships one means it: customIndexedColors.xlsx paints a cell from
385
+ * index 61, which is a plum in the default table and the light grey #E8E8E8 in
386
+ * its own.
387
+ */
388
+ function parseIndexedColors(root) {
389
+ const colors = asObject(root["colors"]);
390
+ const node = colors ? asObject(colors["indexedColors"]) : void 0;
391
+ if (!node) return INDEXED_COLORS;
392
+ const out = [];
393
+ for (const item of asArray(node["rgbColor"])) {
394
+ const obj = asObject(item);
395
+ const rgb = obj ? strAttr(obj, "rgb") : void 0;
396
+ if (rgb === void 0) break;
397
+ const hex = rgb.length === 8 ? rgb.slice(2) : rgb;
398
+ if (!/^[0-9A-Fa-f]{6}$/.test(hex)) break;
399
+ out.push(hex.toUpperCase());
400
+ }
401
+ if (out.length < PALETTE_ENTRIES) return INDEXED_COLORS;
402
+ return [...out, ...INDEXED_COLORS.slice(out.length)];
403
+ }
404
+ /**
405
+ * ECMA-376 §18.8.3 `<color theme>` — the index into the workbook theme's colour
406
+ * scheme. Excel's order is NOT the order the theme part declares them in: the
407
+ * first two slots are swapped, so `theme="0"` is the light background and
408
+ * `theme="1"` the dark text, while `theme1.xml` writes `dk1` before `lt1`.
409
+ */
410
+ var THEME_SLOTS = [
411
+ "lt1",
412
+ "dk1",
413
+ "lt2",
414
+ "dk2",
415
+ "accent1",
416
+ "accent2",
417
+ "accent3",
418
+ "accent4",
419
+ "accent5",
420
+ "accent6",
421
+ "hlink",
422
+ "folHlink"
423
+ ];
424
+ /**
425
+ * §18.8.19 `tint` — lighten (positive) or darken (negative) a theme colour by
426
+ * scaling its HSL luminance. Excel writes it on nearly every theme colour it
427
+ * uses, so ignoring it is not much better than ignoring the colour: a `theme="2"
428
+ * tint="-0.5"` band is half as light as its slot.
429
+ */
430
+ function applyTint(hex, tint) {
431
+ if (!Number.isFinite(tint) || tint === 0) return hex;
432
+ return tint < 0 ? applyColorMods(hex, [{
433
+ kind: "lumMod",
434
+ val: 1 + tint
435
+ }]) : applyColorMods(hex, [{
436
+ kind: "lumMod",
437
+ val: 1 - tint
438
+ }, {
439
+ kind: "lumOff",
440
+ val: tint
441
+ }]);
305
442
  }
306
443
  //#endregion
307
- export { EMPTY_XLSX_STYLES, parseXlsxStyles };
444
+ export { EMPTY_XLSX_STYLES, parseDxf, parseXlsxStyles };
@@ -3,6 +3,7 @@ import { XMLParser } from "fast-xml-parser";
3
3
  //#region src/excel/table-parser.ts
4
4
  var decoder = new TextDecoder("utf-8");
5
5
  var parser = new XMLParser({
6
+ htmlEntities: true,
6
7
  ignoreAttributes: false,
7
8
  attributeNamePrefix: "@_",
8
9
  parseAttributeValue: false,
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Where a VML shape sits, in points from the sheet's top-left — read from the
3
+ * CSS `style` attribute (`margin-left`/`margin-top`/`width`/`height`).
4
+ *
5
+ * The same rectangle is also expressed by `<x:ClientData><x:Anchor>` as
6
+ * (column, offset px, row, offset px) pairs, which needs the sheet's column
7
+ * widths and row heights to resolve. The style is already absolute, and the two
8
+ * agree exactly in every file checked, so this reads the simpler one.
9
+ */
10
+ export interface VmlShapeBox {
11
+ readonly xPt: number;
12
+ readonly yPt: number;
13
+ readonly widthPt: number;
14
+ readonly heightPt: number;
15
+ }
16
+ /** A form control declared by a legacy VML shape's `<x:ClientData>`. */
17
+ export interface VmlFormControl {
18
+ /** `ST_ObjectType` — `Radio`, `CheckBox`, `GBox`, `Button`, `Drop`, … */
19
+ readonly objectType: string;
20
+ /** The shape id (`o:spid` without its `_x0000_s` prefix), pairing with `<control shapeId>`. */
21
+ readonly shapeId?: string;
22
+ /** The visible label, from the shape's `<v:textbox>`. */
23
+ readonly caption?: string;
24
+ /** `<x:Checked>` — set on a checked check/option button, absent otherwise. */
25
+ readonly checked?: boolean;
26
+ /** `<x:FirstButton/>` — this option button starts a new group. */
27
+ readonly firstButton?: boolean;
28
+ /** Where the control sits, from the shape's `style` (§{@link VmlShapeBox}). */
29
+ readonly box?: VmlShapeBox;
30
+ /** `<v:textbox><font size>` — twentieths of a point, so 160 is 8pt. */
31
+ readonly fontSizePt?: number;
32
+ }
33
+ /** A parsed legacy VML drawing: its form controls plus every shape's box. */
34
+ export interface VmlDrawing {
35
+ readonly controls: ReadonlyArray<VmlFormControl>;
36
+ /**
37
+ * Shape id (`o:spid` without its `_x0000_s` prefix) → box, for EVERY shape in
38
+ * the part, controls or not. An ActiveX control's box lives nowhere else: its
39
+ * `activeX#.xml` carries only a class id and its `<control>` element carries
40
+ * no anchor, so the `ObjectType="Pict"` shape that shares its `shapeId` is
41
+ * the one thing that says where it goes.
42
+ */
43
+ readonly boxes: ReadonlyMap<string, VmlShapeBox>;
44
+ /**
45
+ * Shape ids whose `<x:ClientData>` clears `<x:PrintObject>` — Excel's "Print
46
+ * object" checkbox, the legacy spelling of §18.3.1.20 `<controlPr print>`.
47
+ * Such a shape is on screen only, and the `<control>` that shares its id must
48
+ * not be drawn either.
49
+ */
50
+ readonly nonPrinting: ReadonlySet<string>;
51
+ }
52
+ /**
53
+ * Read the form controls out of a legacy VML drawing part.
54
+ *
55
+ * Shapes with no `<x:ClientData>`, or whose object type is not a control (a
56
+ * comment, a text box, a picture), are skipped — this is not a general VML
57
+ * shape reader, only the control channel. See {@link CONTROL_TYPES}.
58
+ *
59
+ * @param data The raw `vmlDrawing#.vml` bytes.
60
+ * @returns One entry per control shape, in document order.
61
+ */
62
+ export declare function parseVmlFormControls(data: Uint8Array): Array<VmlFormControl>;
63
+ /**
64
+ * Read a legacy VML drawing part: its form controls and every shape's box.
65
+ *
66
+ * @param data The raw `vmlDrawing#.vml` bytes.
67
+ */
68
+ export declare function parseVmlDrawing(data: Uint8Array): VmlDrawing;
@@ -0,0 +1,158 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ //#region src/excel/vml-drawing.ts
3
+ var decoder = new TextDecoder("utf-8");
4
+ var parser = new XMLParser({
5
+ htmlEntities: true,
6
+ ignoreAttributes: false,
7
+ attributeNamePrefix: "@_",
8
+ textNodeName: "#text",
9
+ parseAttributeValue: false,
10
+ parseTagValue: false,
11
+ removeNSPrefix: true
12
+ });
13
+ var SPID_PREFIX = /^_x0000_s/;
14
+ /**
15
+ * The `x:ObjectType` values that name a FORM CONTROL.
16
+ *
17
+ * Not everything with an `<x:ClientData>` is one — a cell comment is a VML
18
+ * shape with `ObjectType="Note"`, and so are text boxes, pictures and movies.
19
+ * Taking the element's presence as the test listed every comment in the
20
+ * document as a form control.
21
+ */
22
+ var CONTROL_TYPES = new Set([
23
+ "Button",
24
+ "Checkbox",
25
+ "CheckBox",
26
+ "Dialog",
27
+ "Drop",
28
+ "Edit",
29
+ "EditBox",
30
+ "GBox",
31
+ "Label",
32
+ "List",
33
+ "Radio",
34
+ "Scroll",
35
+ "Spin"
36
+ ]);
37
+ /**
38
+ * Read a legacy VML drawing part: its form controls and every shape's box.
39
+ *
40
+ * @param data The raw `vmlDrawing#.vml` bytes.
41
+ */
42
+ function parseVmlDrawing(data) {
43
+ const tree = parser.parse(decoder.decode(data));
44
+ const root = asObject(tree["xml"]) ?? tree;
45
+ const out = [];
46
+ const boxes = /* @__PURE__ */ new Map();
47
+ const nonPrinting = /* @__PURE__ */ new Set();
48
+ for (const raw of asArray(root["shape"])) {
49
+ const shape = asObject(raw);
50
+ if (!shape) continue;
51
+ const idAttr = strAttr(shape, "id");
52
+ const shapeId = (strAttr(shape, "spid") ?? (idAttr !== void 0 && SPID_PREFIX.test(idAttr) ? idAttr : void 0))?.replace(SPID_PREFIX, "");
53
+ const box = shapeBox(strAttr(shape, "style"));
54
+ if (shapeId && box) boxes.set(shapeId, box);
55
+ const client = asObject(shape["ClientData"]);
56
+ if (!client) continue;
57
+ if (!(flatText(client["PrintObject"])?.toLowerCase() !== "false")) {
58
+ if (shapeId) nonPrinting.add(shapeId);
59
+ continue;
60
+ }
61
+ const objectType = strAttr(client, "ObjectType");
62
+ if (!objectType || !CONTROL_TYPES.has(objectType)) continue;
63
+ const control = { objectType };
64
+ if (shapeId) control.shapeId = shapeId;
65
+ if (box) control.box = box;
66
+ const caption = textboxText(shape["textbox"]);
67
+ if (caption) control.caption = caption;
68
+ const fontSizePt = textboxFontSizePt(shape["textbox"]);
69
+ if (fontSizePt !== void 0) control.fontSizePt = fontSizePt;
70
+ if (flatText(client["Checked"]) === "1") control.checked = true;
71
+ if ("FirstButton" in client) control.firstButton = true;
72
+ out.push(control);
73
+ }
74
+ return {
75
+ controls: out,
76
+ boxes,
77
+ nonPrinting
78
+ };
79
+ }
80
+ /** CSS length → points. A bare number is pixels, VML's implicit unit. */
81
+ var CSS_UNITS = new Map([
82
+ ["pt", 1],
83
+ ["px", .75],
84
+ ["in", 72],
85
+ ["cm", 72 / 2.54],
86
+ ["mm", 7.2 / 2.54],
87
+ ["pc", 12]
88
+ ]);
89
+ function cssLengthPt(value) {
90
+ if (value === void 0) return void 0;
91
+ const m = /^\s*(-?[\d.]+)\s*([a-z]*)\s*$/i.exec(value);
92
+ if (!m) return void 0;
93
+ const n = Number(m[1]);
94
+ if (!Number.isFinite(n)) return void 0;
95
+ const unit = m[2].toLowerCase();
96
+ const factor = unit === "" ? .75 : CSS_UNITS.get(unit);
97
+ return factor === void 0 ? void 0 : n * factor;
98
+ }
99
+ /** The shape's rectangle from its `style` declaration; undefined if incomplete. */
100
+ function shapeBox(style) {
101
+ if (style === void 0) return void 0;
102
+ const props = /* @__PURE__ */ new Map();
103
+ for (const decl of style.split(";")) {
104
+ const i = decl.indexOf(":");
105
+ if (i < 0) continue;
106
+ props.set(decl.slice(0, i).trim().toLowerCase(), decl.slice(i + 1).trim());
107
+ }
108
+ const widthPt = cssLengthPt(props.get("width"));
109
+ const heightPt = cssLengthPt(props.get("height"));
110
+ if (widthPt === void 0 || heightPt === void 0) return void 0;
111
+ return {
112
+ xPt: cssLengthPt(props.get("margin-left")) ?? 0,
113
+ yPt: cssLengthPt(props.get("margin-top")) ?? 0,
114
+ widthPt,
115
+ heightPt
116
+ };
117
+ }
118
+ /** `<v:textbox>`'s `<font size>` — twentieths of a point (160 ⇒ 8pt). */
119
+ function textboxFontSizePt(node) {
120
+ const box = asObject(node);
121
+ const font = asObject(((box ? asObject(box["div"]) : void 0) ?? box)?.["font"]);
122
+ const size = font ? strAttr(font, "size") : void 0;
123
+ const twips = size === void 0 ? NaN : Number(size);
124
+ return Number.isFinite(twips) && twips > 0 ? twips / 20 : void 0;
125
+ }
126
+ /** The visible text of a `<v:textbox>`, whose content is an HTML fragment. */
127
+ function textboxText(node) {
128
+ const text = flatText(node);
129
+ if (text === void 0) return void 0;
130
+ const collapsed = text.replace(/\s+/g, " ").trim();
131
+ return collapsed.length > 0 ? collapsed : void 0;
132
+ }
133
+ /** Every text node under `node`, concatenated — the element tree flattened. */
134
+ function flatText(node) {
135
+ if (node === void 0 || node === null) return void 0;
136
+ if (typeof node === "string") return node;
137
+ if (typeof node === "number" || typeof node === "boolean") return String(node);
138
+ if (Array.isArray(node)) return node.map((n) => flatText(n) ?? "").join("");
139
+ if (typeof node !== "object") return void 0;
140
+ let out = "";
141
+ for (const [key, value] of Object.entries(node)) {
142
+ if (key.startsWith("@_")) continue;
143
+ out += flatText(value) ?? "";
144
+ }
145
+ return out;
146
+ }
147
+ function asObject(value) {
148
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
149
+ }
150
+ function asArray(value) {
151
+ return Array.isArray(value) ? value : value === void 0 ? [] : [value];
152
+ }
153
+ function strAttr(obj, name) {
154
+ const v = obj[`@_${name}`];
155
+ return typeof v === "string" ? v : typeof v === "number" ? String(v) : void 0;
156
+ }
157
+ //#endregion
158
+ export { parseVmlDrawing };
@@ -7,6 +7,11 @@ export interface SheetReference {
7
7
  readonly sheetId: string;
8
8
  /** The relationship id (`r:id`) resolving to the sheet's worksheet part. */
9
9
  readonly relationshipId: string;
10
+ /**
11
+ * §18.2.19 `@state` — `hidden` or `veryHidden` when the tab is not shown.
12
+ * Excel and LibreOffice print only visible sheets.
13
+ */
14
+ readonly hidden?: boolean;
10
15
  }
11
16
  /** The parsed `workbook.xml`: the ordered sheet list, the date epoch and defined names. */
12
17
  export interface ParsedWorkbook {
@@ -2,6 +2,7 @@ import { XMLParser } from "fast-xml-parser";
2
2
  //#region src/excel/workbook-parser.ts
3
3
  var decoder = new TextDecoder("utf-8");
4
4
  var parser = new XMLParser({
5
+ htmlEntities: true,
5
6
  ignoreAttributes: false,
6
7
  attributeNamePrefix: "@_",
7
8
  textNodeName: "#text",
@@ -42,10 +43,13 @@ function parseWorkbook(data) {
42
43
  const sheetId = strAttr(obj, "sheetId");
43
44
  const rId = strAttr(obj, "r:id") ?? strAttr(obj, "id");
44
45
  if (!name || !rId) continue;
46
+ const state = strAttr(obj, "state");
47
+ const hidden = state === "hidden" || state === "veryHidden";
45
48
  out.push({
46
49
  name,
47
50
  sheetId: sheetId ?? "",
48
- relationshipId: rId
51
+ relationshipId: rId,
52
+ ...hidden ? { hidden } : {}
49
53
  });
50
54
  }
51
55
  return {