reamkit 1.15.4 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/facade.d.ts +5 -0
  3. package/dist/esm/core/converter/project.js +5 -1
  4. package/dist/esm/core/converter/ream.d.ts +6 -0
  5. package/dist/esm/core/converter/ream.js +34 -8
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +659 -7
  8. package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
  9. package/dist/esm/core/drawingml/chart-geometry.js +424 -92
  10. package/dist/esm/core/drawingml/chart-parser.js +301 -11
  11. package/dist/esm/core/drawingml/colors.d.ts +7 -7
  12. package/dist/esm/core/drawingml/colors.js +84 -18
  13. package/dist/esm/core/drawingml/preset-geometry.js +240 -6
  14. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  15. package/dist/esm/core/drawingml/shape-render.js +116 -3
  16. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  17. package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
  18. package/dist/esm/core/drawingml/theme-parser.js +92 -1
  19. package/dist/esm/core/font/measure.d.ts +5 -0
  20. package/dist/esm/core/font/measure.js +21 -1
  21. package/dist/esm/core/font/ttf-subset.js +26 -1
  22. package/dist/esm/core/images.d.ts +2 -2
  23. package/dist/esm/core/images.js +353 -43
  24. package/dist/esm/core/indexed-colors.d.ts +11 -0
  25. package/dist/esm/core/indexed-colors.js +81 -0
  26. package/dist/esm/core/ir/flow.d.ts +23 -1
  27. package/dist/esm/core/ir/sheet.d.ts +61 -0
  28. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  29. package/dist/esm/core/number-format.js +800 -0
  30. package/dist/esm/core/numbering/apply.d.ts +5 -3
  31. package/dist/esm/core/numbering/apply.js +42 -11
  32. package/dist/esm/core/numbering/index.js +2 -0
  33. package/dist/esm/core/numbering/state.d.ts +13 -1
  34. package/dist/esm/core/numbering/state.js +256 -15
  35. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  36. package/dist/esm/core/opc/alternate-content.js +72 -0
  37. package/dist/esm/core/opc/core-properties.js +1 -0
  38. package/dist/esm/core/opc/package.d.ts +14 -1
  39. package/dist/esm/core/opc/package.js +43 -4
  40. package/dist/esm/core/opc/relationships.js +1 -0
  41. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  42. package/dist/esm/core/opc/xml-entities.js +91 -0
  43. package/dist/esm/core/po-helpers.js +16 -1
  44. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  45. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  46. package/dist/esm/core/style-cascade/resolver.js +66 -11
  47. package/dist/esm/core/style-cascade/table.js +35 -11
  48. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  49. package/dist/esm/core/style-cascade/types.js +4 -0
  50. package/dist/esm/core/vector.d.ts +30 -0
  51. package/dist/esm/excel/activex-parser.js +1 -0
  52. package/dist/esm/excel/column-bands.d.ts +26 -1
  53. package/dist/esm/excel/column-bands.js +156 -20
  54. package/dist/esm/excel/comments-parser.js +5 -3
  55. package/dist/esm/excel/conditional-format.d.ts +25 -2
  56. package/dist/esm/excel/conditional-format.js +63 -19
  57. package/dist/esm/excel/escaped-text.d.ts +9 -0
  58. package/dist/esm/excel/escaped-text.js +20 -0
  59. package/dist/esm/excel/form-control-parser.js +1 -0
  60. package/dist/esm/excel/formula/dates.js +1 -1
  61. package/dist/esm/excel/header-footer.d.ts +9 -1
  62. package/dist/esm/excel/header-footer.js +162 -14
  63. package/dist/esm/excel/index.d.ts +1 -1
  64. package/dist/esm/excel/index.js +2 -2
  65. package/dist/esm/excel/pivot-table-parser.js +1 -0
  66. package/dist/esm/excel/print-model.d.ts +75 -0
  67. package/dist/esm/excel/print-model.js +899 -77
  68. package/dist/esm/excel/printer-settings.d.ts +16 -0
  69. package/dist/esm/excel/printer-settings.js +36 -0
  70. package/dist/esm/excel/rich-value.d.ts +13 -0
  71. package/dist/esm/excel/rich-value.js +120 -0
  72. package/dist/esm/excel/shared-strings-parser.js +11 -7
  73. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  74. package/dist/esm/excel/sheet-drawing.js +26 -3
  75. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  76. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  77. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  78. package/dist/esm/excel/sheet-to-flow.js +553 -36
  79. package/dist/esm/excel/slicer-parser.js +1 -0
  80. package/dist/esm/excel/styles-parser.d.ts +30 -2
  81. package/dist/esm/excel/styles-parser.js +177 -40
  82. package/dist/esm/excel/table-parser.js +1 -0
  83. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  84. package/dist/esm/excel/vml-drawing.js +158 -0
  85. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  86. package/dist/esm/excel/workbook-parser.js +5 -1
  87. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  88. package/dist/esm/excel/worksheet-parser.js +150 -61
  89. package/dist/esm/excel/xlsx-reader.js +242 -11
  90. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  91. package/dist/esm/html/html-writer.js +29 -6
  92. package/dist/esm/layout/page-doc.d.ts +87 -1
  93. package/dist/esm/layout/styled-layout.d.ts +46 -2
  94. package/dist/esm/layout/styled-layout.js +2345 -274
  95. package/dist/esm/pdf/cid-font.js +20 -9
  96. package/dist/esm/pdf/objects.d.ts +13 -0
  97. package/dist/esm/pdf/objects.js +18 -1
  98. package/dist/esm/pdf/shading.js +76 -6
  99. package/dist/esm/pdf/styled-page-emitter.js +355 -49
  100. package/dist/esm/pdf/text-page-renderer.js +1 -0
  101. package/dist/esm/pdf/vector-graphics.d.ts +31 -2
  102. package/dist/esm/pdf/vector-graphics.js +103 -3
  103. package/dist/esm/pptx/pptx-reader.js +2 -0
  104. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  105. package/dist/esm/pptx/slide-parser.js +98 -24
  106. package/dist/esm/svg/svg-writer.js +23 -4
  107. package/dist/esm/word/document-parser.d.ts +96 -7
  108. package/dist/esm/word/document-parser.js +538 -116
  109. package/dist/esm/word/docx-reader.js +135 -43
  110. package/dist/esm/word/docx-writer.js +6 -1
  111. package/dist/esm/word/drawing-parser.d.ts +86 -5
  112. package/dist/esm/word/drawing-parser.js +1590 -54
  113. package/dist/esm/word/index.d.ts +4 -4
  114. package/dist/esm/word/numbering-parser.d.ts +2 -1
  115. package/dist/esm/word/numbering-parser.js +108 -21
  116. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  117. package/dist/esm/word/paragraph-properties.js +219 -10
  118. package/dist/esm/word/run-properties.js +45 -1
  119. package/dist/esm/word/settings-parser.d.ts +24 -0
  120. package/dist/esm/word/settings-parser.js +32 -5
  121. package/dist/esm/word/shading.d.ts +11 -0
  122. package/dist/esm/word/shading.js +64 -0
  123. package/dist/esm/word/styles-parser.js +9 -2
  124. package/dist/esm/word/table-parser.d.ts +9 -1
  125. package/dist/esm/word/table-parser.js +182 -30
  126. package/package.json +7 -3
  127. package/dist/esm/excel/number-format.js +0 -476
@@ -1,4 +1,4 @@
1
- import { BodyElement, Numbering } from '../document-model/index.js';
1
+ import { BodyElement, Numbering, StyleSheet } from '../document-model/index.js';
2
2
  /**
3
3
  * Apply list numbering to a body as a FlowDoc transform (§17.9): walk the
4
4
  * elements (recursing into table cells), advancing list counters and prepending
@@ -8,9 +8,11 @@ import { BodyElement, Numbering } from '../document-model/index.js';
8
8
  *
9
9
  * @param body The body elements to transform.
10
10
  * @param numbering The parsed numbering definitions, or `undefined`.
11
+ * @param styles The style sheet, for a paragraph whose numbering comes from
12
+ * its STYLE (§17.9.24) rather than its own `w:numPr`.
11
13
  * @returns A new body with markers materialized.
12
14
  */
13
- export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numbering: Numbering | undefined): Array<BodyElement>;
15
+ export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numbering: Numbering | undefined, styles?: StyleSheet): Array<BodyElement>;
14
16
  /**
15
17
  * Apply {@link applyNumbering} to each header/footer band independently (each
16
18
  * gets its own counter state), keyed as in the input map.
@@ -19,4 +21,4 @@ export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numberi
19
21
  * @param numbering The parsed numbering definitions, or `undefined`.
20
22
  * @returns A new map with numbering applied (empty when `hf` is empty/absent).
21
23
  */
22
- export declare function applyNumberingToHeadersFooters(hf: ReadonlyMap<string, ReadonlyArray<BodyElement>> | undefined, numbering: Numbering | undefined): ReadonlyMap<string, ReadonlyArray<BodyElement>>;
24
+ export declare function applyNumberingToHeadersFooters(hf: ReadonlyMap<string, ReadonlyArray<BodyElement>> | undefined, numbering: Numbering | undefined, styles?: StyleSheet): ReadonlyMap<string, ReadonlyArray<BodyElement>>;
@@ -1,5 +1,13 @@
1
- import { NumberingState } from "./state.js";
1
+ import { pt } from "../ir/units.js";
2
+ import { NumberingState, effectiveAbstract } from "./state.js";
3
+ import { resolveParagraphProperties } from "../style-cascade/resolver.js";
4
+ import "../style-cascade/index.js";
2
5
  //#region src/core/numbering/apply.ts
6
+ /** The height a picture bullet takes when neither the level nor the paragraph
7
+ * mark states a font size — Word's own default text size. */
8
+ var DEFAULT_BULLET_HEIGHT_PT = 11;
9
+ /** SOFT HYPHEN, which a `w:lvlText` may spell a dash bullet with (§17.9.11). */
10
+ var SOFT_HYPHEN = "­";
3
11
  /**
4
12
  * Apply list numbering to a body as a FlowDoc transform (§17.9): walk the
5
13
  * elements (recursing into table cells), advancing list counters and prepending
@@ -9,28 +17,51 @@ import { NumberingState } from "./state.js";
9
17
  *
10
18
  * @param body The body elements to transform.
11
19
  * @param numbering The parsed numbering definitions, or `undefined`.
20
+ * @param styles The style sheet, for a paragraph whose numbering comes from
21
+ * its STYLE (§17.9.24) rather than its own `w:numPr`.
12
22
  * @returns A new body with markers materialized.
13
23
  */
14
- function applyNumbering(body, numbering) {
24
+ function applyNumbering(body, numbering, styles) {
15
25
  if (!numbering || numbering.abstractNums.size === 0) return body.map((b) => b);
16
26
  const state = new NumberingState();
27
+ const numberingOf = (p) => p.properties.numbering ?? (styles ? resolveParagraphProperties(p.properties, styles).numbering : void 0);
17
28
  const transformParagraph = (p) => {
18
- const ref = p.properties.numbering;
29
+ if (p.properties.sectionBreak === true && p.runs.every((r) => r.text === "")) return p;
30
+ const ref = numberingOf(p);
19
31
  if (!ref) return p;
20
32
  const marker = state.resolveMarker(numbering, ref);
21
33
  if (marker === null) return p;
22
34
  const instance = numbering.numInstances.get(ref.numId);
23
- const level = (instance ? numbering.abstractNums.get(instance.abstractNumId) : void 0)?.levels.get(ref.ilvl);
24
- const markerRun = {
25
- text: `${marker}\t`,
26
- properties: level?.runProperties ?? {},
35
+ const level = (instance ? effectiveAbstract(numbering, instance) : void 0)?.levels.get(ref.ilvl);
36
+ const pic = level?.picBullet;
37
+ const bulletCapPt = level?.runProperties.fontSizePt ?? p.properties.runProperties?.fontSizePt ?? DEFAULT_BULLET_HEIGHT_PT;
38
+ const bulletScale = pic ? Math.min(1, bulletCapPt / Math.max(1, pic.heightPt)) : 1;
39
+ const markerRuns = pic ? [{
40
+ text: "",
41
+ properties: {},
42
+ listMarker: true,
43
+ inlineImage: {
44
+ resource: pic.resource,
45
+ width: pt(pic.widthPt * bulletScale),
46
+ height: pt(pic.heightPt * bulletScale)
47
+ }
48
+ }, {
49
+ text: " ",
50
+ properties: {},
27
51
  listMarker: true
28
- };
52
+ }] : [{
53
+ text: `${marker.replaceAll(SOFT_HYPHEN, "-")}\t`,
54
+ properties: {
55
+ ...p.properties.runProperties,
56
+ ...level?.runProperties
57
+ },
58
+ listMarker: true
59
+ }];
29
60
  const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
30
61
  return {
31
62
  ...p,
32
63
  properties: newProps,
33
- runs: [markerRun, ...p.runs]
64
+ runs: [...markerRuns, ...p.runs]
34
65
  };
35
66
  };
36
67
  const visit = (el) => {
@@ -73,11 +104,11 @@ function mergeIndentFromLevel(paragraphProps, levelProps) {
73
104
  * @param numbering The parsed numbering definitions, or `undefined`.
74
105
  * @returns A new map with numbering applied (empty when `hf` is empty/absent).
75
106
  */
76
- function applyNumberingToHeadersFooters(hf, numbering) {
107
+ function applyNumberingToHeadersFooters(hf, numbering, styles) {
77
108
  if (!hf || hf.size === 0) return /* @__PURE__ */ new Map();
78
109
  if (!numbering || numbering.abstractNums.size === 0) return hf;
79
110
  const out = /* @__PURE__ */ new Map();
80
- for (const [key, value] of hf) out.set(key, applyNumbering(value, numbering));
111
+ for (const [key, value] of hf) out.set(key, applyNumbering(value, numbering, styles));
81
112
  return out;
82
113
  }
83
114
  //#endregion
@@ -0,0 +1,2 @@
1
+ import "./state.js";
2
+ import "./apply.js";
@@ -1,4 +1,15 @@
1
- import { AbstractNumbering, Numbering, NumberingLevel, NumberingReference } from '../document-model/index.js';
1
+ import { AbstractNumbering, Numbering, NumberingInstance, NumberingLevel, NumberingReference } from '../document-model/index.js';
2
+ /**
3
+ * §17.9.27 — the levels an INSTANCE actually numbers by: its abstract
4
+ * definition's, with any `w:lvlOverride/w:lvl` shadowing them. Cached per
5
+ * instance, since every list paragraph asks for it.
6
+ *
7
+ * @param numbering The parsed numbering definitions.
8
+ * @param instance The `w:num` the paragraph refers to.
9
+ * @returns The effective abstract numbering, or `undefined` when the instance
10
+ * names one that is not there.
11
+ */
12
+ export declare function effectiveAbstract(numbering: Numbering, instance: NumberingInstance): AbstractNumbering | undefined;
2
13
  /**
3
14
  * Mutable list-counter state for one numbering scope (e.g. a body or a single
4
15
  * header/footer band). Tracks a counter per list per level and advances them as
@@ -6,6 +17,7 @@ import { AbstractNumbering, Numbering, NumberingLevel, NumberingReference } from
6
17
  */
7
18
  export declare class NumberingState {
8
19
  private readonly counters;
20
+ private readonly restarted;
9
21
  /**
10
22
  * Advance the counter for `ref` (resetting deeper levels) and format its
11
23
  * marker text. The first hit at a level seeds it from the level's `start`;
@@ -1,11 +1,37 @@
1
1
  //#region src/core/numbering/state.ts
2
2
  /**
3
+ * §17.9.27 — the levels an INSTANCE actually numbers by: its abstract
4
+ * definition's, with any `w:lvlOverride/w:lvl` shadowing them. Cached per
5
+ * instance, since every list paragraph asks for it.
6
+ *
7
+ * @param numbering The parsed numbering definitions.
8
+ * @param instance The `w:num` the paragraph refers to.
9
+ * @returns The effective abstract numbering, or `undefined` when the instance
10
+ * names one that is not there.
11
+ */
12
+ function effectiveAbstract(numbering, instance) {
13
+ const base = numbering.abstractNums.get(instance.abstractNumId);
14
+ if (!base || !instance.levelOverrides || instance.levelOverrides.size === 0) return base;
15
+ const hit = effectiveCache.get(instance);
16
+ if (hit) return hit;
17
+ const levels = new Map(base.levels);
18
+ for (const [ilvl, lvl] of instance.levelOverrides) levels.set(ilvl, lvl);
19
+ const merged = {
20
+ id: base.id,
21
+ levels
22
+ };
23
+ effectiveCache.set(instance, merged);
24
+ return merged;
25
+ }
26
+ var effectiveCache = /* @__PURE__ */ new WeakMap();
27
+ /**
3
28
  * Mutable list-counter state for one numbering scope (e.g. a body or a single
4
29
  * header/footer band). Tracks a counter per list per level and advances them as
5
30
  * paragraphs are visited in order.
6
31
  */
7
32
  var NumberingState = class {
8
33
  counters = /* @__PURE__ */ new Map();
34
+ restarted = /* @__PURE__ */ new Set();
9
35
  /**
10
36
  * Advance the counter for `ref` (resetting deeper levels) and format its
11
37
  * marker text. The first hit at a level seeds it from the level's `start`;
@@ -19,19 +45,27 @@ var NumberingState = class {
19
45
  resolveMarker(numbering, ref) {
20
46
  const instance = numbering.numInstances.get(ref.numId);
21
47
  if (!instance) return null;
22
- const abstractNum = numbering.abstractNums.get(instance.abstractNumId);
48
+ const abstractNum = effectiveAbstract(numbering, instance);
23
49
  if (!abstractNum) return null;
24
50
  const level = abstractNum.levels.get(ref.ilvl);
25
51
  if (!level) return null;
26
- let arr = this.counters.get(ref.numId);
52
+ let arr = this.counters.get(instance.abstractNumId);
27
53
  if (!arr) {
28
- arr = new Array(9).fill(0);
29
- this.counters.set(ref.numId, arr);
54
+ arr = new Array(9).fill(void 0);
55
+ this.counters.set(instance.abstractNumId, arr);
56
+ }
57
+ for (let k = ref.ilvl + 1; k < arr.length; k++) arr[k] = void 0;
58
+ const startAt = (i, fallback) => instance.startOverrides?.get(i) ?? fallback ?? 0;
59
+ const restartKey = `${ref.numId}:${String(ref.ilvl)}`;
60
+ const override = instance.startOverrides?.get(ref.ilvl);
61
+ if (override !== void 0 && !this.restarted.has(restartKey)) {
62
+ this.restarted.add(restartKey);
63
+ arr[ref.ilvl] = override;
64
+ } else {
65
+ const current = arr[ref.ilvl];
66
+ arr[ref.ilvl] = current === void 0 ? startAt(ref.ilvl, level.start) : current + 1;
30
67
  }
31
- for (let k = ref.ilvl + 1; k < arr.length; k++) arr[k] = 0;
32
- if (arr[ref.ilvl] === 0) arr[ref.ilvl] = level.start;
33
- else arr[ref.ilvl] += 1;
34
- return formatLevelMarker(abstractNum, level, arr);
68
+ return formatLevelMarker(abstractNum, level, arr.map((n, i) => n ?? startAt(i, abstractNum.levels.get(i)?.start)));
35
69
  }
36
70
  };
37
71
  function formatLevelMarker(abstractNum, currentLevel, counters) {
@@ -40,20 +74,227 @@ function formatLevelMarker(abstractNum, currentLevel, counters) {
40
74
  const lvlIdx = Number(n) - 1;
41
75
  const level = abstractNum.levels.get(lvlIdx);
42
76
  const counter = counters[lvlIdx] ?? 0;
43
- return formatCounter(level?.format ?? "decimal", counter);
77
+ return formatCounter(currentLevel.isLegal === true && lvlIdx !== currentLevel.ilvl ? "decimal" : level?.format ?? "decimal", counter);
44
78
  });
45
79
  }
46
80
  function formatCounter(format, n) {
47
- if (n <= 0) return "";
81
+ if (n < 0) return "";
48
82
  switch (format) {
49
83
  case "decimal": return String(n);
50
- case "lowerLetter": return toLetters(n).toLowerCase();
51
- case "upperLetter": return toLetters(n);
52
- case "lowerRoman": return toRoman(n).toLowerCase();
53
- case "upperRoman": return toRoman(n);
84
+ case "decimalZero": return n < 10 ? `0${String(n)}` : String(n);
85
+ case "decimalFullWidth": return digitByDigit(n, FULL_WIDTH_DIGITS);
86
+ case "ordinal": return `${String(n)}${ordinalSuffix(n)}`;
87
+ case "lowerLetter": return n === 0 ? "" : toLetters(n).toLowerCase();
88
+ case "upperLetter": return n === 0 ? "" : toLetters(n);
89
+ case "lowerRoman": return n === 0 ? "" : toRoman(n).toLowerCase();
90
+ case "upperRoman": return n === 0 ? "" : toRoman(n);
91
+ case "ideographTraditional": return n === 0 ? "" : n <= HEAVENLY_STEMS.length ? HEAVENLY_STEMS[n - 1] : String(n);
92
+ case "ideographZodiac": return n === 0 ? "" : n <= EARTHLY_BRANCHES.length ? EARTHLY_BRANCHES[n - 1] : String(n);
93
+ case "ideographDigital":
94
+ case "koreanDigital2": return digitByDigit(n, IDEOGRAPH_DIGITS);
95
+ case "ideographLegalTraditional": return counting(n, LEGAL_DIGITS, LEGAL_UNITS, false);
96
+ case "chineseCounting":
97
+ case "chineseCountingThousand":
98
+ case "japaneseCounting":
99
+ case "koreanCounting":
100
+ case "taiwaneseCounting":
101
+ case "taiwaneseCountingThousand": return counting(n, IDEOGRAPH_DIGITS, COUNTING_UNITS, true);
102
+ case "hebrew1": return n === 0 ? "" : toHebrewNumeral(n);
103
+ case "hebrew2": return n === 0 ? "" : HEBREW_ALPHABET[(n - 1) % HEBREW_ALPHABET.length];
104
+ case "decimalEnclosedCircle": return n >= 1 && n <= 20 ? String.fromCodePoint(9311 + n) : String(n);
105
+ case "chicago":
106
+ if (n < 1) return "";
107
+ return CHICAGO_MARKS[(n - 1) % CHICAGO_MARKS.length].repeat(Math.floor((n - 1) / CHICAGO_MARKS.length) + 1);
54
108
  default: return "";
55
109
  }
56
110
  }
111
+ var HEBREW_ONES = [
112
+ "",
113
+ "א",
114
+ "ב",
115
+ "ג",
116
+ "ד",
117
+ "ה",
118
+ "ו",
119
+ "ז",
120
+ "ח",
121
+ "ט"
122
+ ];
123
+ var HEBREW_TENS = [
124
+ "",
125
+ "י",
126
+ "כ",
127
+ "ל",
128
+ "מ",
129
+ "נ",
130
+ "ס",
131
+ "ע",
132
+ "פ",
133
+ "צ"
134
+ ];
135
+ var HEBREW_HUNDREDS = [
136
+ "",
137
+ "ק",
138
+ "ר",
139
+ "ש",
140
+ "ת"
141
+ ];
142
+ var HEBREW_ALPHABET = [
143
+ "א",
144
+ "ב",
145
+ "ג",
146
+ "ד",
147
+ "ה",
148
+ "ו",
149
+ "ז",
150
+ "ח",
151
+ "ט",
152
+ "י",
153
+ "כ",
154
+ "ל",
155
+ "מ",
156
+ "נ",
157
+ "ס",
158
+ "ע",
159
+ "פ",
160
+ "צ",
161
+ "ק",
162
+ "ר",
163
+ "ש",
164
+ "ת"
165
+ ];
166
+ function toHebrewNumeral(n) {
167
+ let rest = n;
168
+ let out = "";
169
+ while (rest >= 400) {
170
+ out += "ת";
171
+ rest -= 400;
172
+ }
173
+ out += HEBREW_HUNDREDS[Math.floor(rest / 100)] ?? "";
174
+ rest %= 100;
175
+ if (rest === 15 || rest === 16) out += rest === 15 ? "טו" : "טז";
176
+ else {
177
+ out += HEBREW_TENS[Math.floor(rest / 10)] ?? "";
178
+ out += HEBREW_ONES[rest % 10] ?? "";
179
+ }
180
+ return out;
181
+ }
182
+ var CHICAGO_MARKS = [
183
+ "*",
184
+ "†",
185
+ "‡",
186
+ "§"
187
+ ];
188
+ var IDEOGRAPH_DIGITS = [
189
+ "零",
190
+ "一",
191
+ "二",
192
+ "三",
193
+ "四",
194
+ "五",
195
+ "六",
196
+ "七",
197
+ "八",
198
+ "九"
199
+ ];
200
+ var COUNTING_UNITS = [
201
+ "",
202
+ "十",
203
+ "百",
204
+ "千"
205
+ ];
206
+ var LEGAL_DIGITS = [
207
+ "零",
208
+ "壹",
209
+ "貳",
210
+ "參",
211
+ "肆",
212
+ "伍",
213
+ "陸",
214
+ "柒",
215
+ "捌",
216
+ "玖"
217
+ ];
218
+ var LEGAL_UNITS = [
219
+ "",
220
+ "拾",
221
+ "佰",
222
+ "仟"
223
+ ];
224
+ var HEAVENLY_STEMS = [
225
+ "甲",
226
+ "乙",
227
+ "丙",
228
+ "丁",
229
+ "戊",
230
+ "己",
231
+ "庚",
232
+ "辛",
233
+ "壬",
234
+ "癸"
235
+ ];
236
+ var EARTHLY_BRANCHES = [
237
+ "子",
238
+ "丑",
239
+ "寅",
240
+ "卯",
241
+ "辰",
242
+ "巳",
243
+ "午",
244
+ "未",
245
+ "申",
246
+ "酉",
247
+ "戌",
248
+ "亥"
249
+ ];
250
+ var FULL_WIDTH_DIGITS = [
251
+ "0",
252
+ "1",
253
+ "2",
254
+ "3",
255
+ "4",
256
+ "5",
257
+ "6",
258
+ "7",
259
+ "8",
260
+ "9"
261
+ ];
262
+ /** Each decimal digit as its own glyph — no units, no elision (10 → 一零). */
263
+ function digitByDigit(n, digits) {
264
+ return String(n).split("").map((d) => digits[Number(d)] ?? d).join("");
265
+ }
266
+ /**
267
+ * The counting systems: digits carrying place units (十百千). `elideTen` drops
268
+ * the leading 一 of the tens place, which is what the plain counting systems do
269
+ * (11 → 十一) and the formal one does not (11 → 壹拾壹). Above the units the
270
+ * table covers, plain digits — a list marker never gets that far.
271
+ */
272
+ function counting(n, digits, units, elideTen) {
273
+ if (n >= 10 ** units.length) return String(n);
274
+ const ds = String(n).split("").map(Number);
275
+ let out = "";
276
+ ds.forEach((d, i) => {
277
+ const place = ds.length - 1 - i;
278
+ if (d === 0) {
279
+ if (out.length > 0 && !out.endsWith(digits[0]) && ds.slice(i).some((x) => x > 0)) out += digits[0];
280
+ return;
281
+ }
282
+ const head = elideTen && d === 1 && place === 1 && i === 0 ? "" : digits[d];
283
+ out += head + units[place];
284
+ });
285
+ return out;
286
+ }
287
+ /** `1st`, `2nd`, `3rd`, `4th` — the English ordinal suffixes (§17.18.59). */
288
+ function ordinalSuffix(n) {
289
+ const two = n % 100;
290
+ if (two >= 11 && two <= 13) return "th";
291
+ switch (n % 10) {
292
+ case 1: return "st";
293
+ case 2: return "nd";
294
+ case 3: return "rd";
295
+ default: return "th";
296
+ }
297
+ }
57
298
  function toLetters(n) {
58
299
  let s = "";
59
300
  let v = n;
@@ -95,4 +336,4 @@ function normalizeBullet(lvlText) {
95
336
  return lvlText;
96
337
  }
97
338
  //#endregion
98
- export { NumberingState };
339
+ export { NumberingState, effectiveAbstract, formatLevelMarker };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Replace every `<mc:AlternateContent>` with the markup its `<mc:Fallback>`
3
+ * holds, so the part reaching the XML parser is plain OOXML in its original
4
+ * element order. A block with no Fallback resolves to nothing — which is what
5
+ * §10.2 of the Markup Compatibility spec asks of a reader that understands
6
+ * none of the offered choices.
7
+ *
8
+ * Text with no AlternateContent is returned unchanged, which is most files.
9
+ *
10
+ * @param xml The decoded part, as read from the package.
11
+ * @returns The part with its compatibility blocks resolved.
12
+ */
13
+ export declare function resolveAlternateContent(xml: string): string;
@@ -0,0 +1,72 @@
1
+ //#region src/core/opc/alternate-content.ts
2
+ /** Deepest nesting of one AlternateContent inside another that we will unwrap. */
3
+ var MAX_DEPTH = 8;
4
+ var OPEN = /<(?:[A-Za-z_][\w.-]*:)?AlternateContent(?=[\s/>])/g;
5
+ /**
6
+ * Find the element starting at `from` (the index of its `<`) and return the
7
+ * span of its content plus the index just past its closing tag. Returns
8
+ * undefined for an unclosed element — malformed markup we leave alone.
9
+ */
10
+ function elementAt(xml, from, local) {
11
+ const nameEnd = xml.indexOf(">", from);
12
+ if (nameEnd < 0) return void 0;
13
+ if (xml[nameEnd - 1] === "/") return {
14
+ inner: "",
15
+ end: nameEnd + 1
16
+ };
17
+ const open = new RegExp(`<(?:[A-Za-z_][\\w.-]*:)?${local}(?=[\\s/>])`, "g");
18
+ const close = new RegExp(`</(?:[A-Za-z_][\\w.-]*:)?${local}\\s*>`, "g");
19
+ let depth = 1;
20
+ let cursor = nameEnd + 1;
21
+ while (depth > 0) {
22
+ close.lastIndex = cursor;
23
+ const shut = close.exec(xml);
24
+ if (!shut) return void 0;
25
+ open.lastIndex = cursor;
26
+ for (let o = open.exec(xml); o && o.index < shut.index; o = open.exec(xml)) if (xml[xml.indexOf(">", o.index) - 1] !== "/") depth++;
27
+ depth--;
28
+ cursor = shut.index + shut[0].length;
29
+ if (depth === 0) return {
30
+ inner: xml.slice(nameEnd + 1, shut.index),
31
+ end: cursor
32
+ };
33
+ }
34
+ }
35
+ /** The content of the first `<mc:Fallback>` directly inside `inner`, or ''. */
36
+ function fallbackOf(inner) {
37
+ const at = (/* @__PURE__ */ new RegExp("<(?:[A-Za-z_][\\w.-]*:)?Fallback(?=[\\s/>])")).exec(inner);
38
+ if (!at) return "";
39
+ return elementAt(inner, at.index, "Fallback")?.inner ?? "";
40
+ }
41
+ /**
42
+ * Replace every `<mc:AlternateContent>` with the markup its `<mc:Fallback>`
43
+ * holds, so the part reaching the XML parser is plain OOXML in its original
44
+ * element order. A block with no Fallback resolves to nothing — which is what
45
+ * §10.2 of the Markup Compatibility spec asks of a reader that understands
46
+ * none of the offered choices.
47
+ *
48
+ * Text with no AlternateContent is returned unchanged, which is most files.
49
+ *
50
+ * @param xml The decoded part, as read from the package.
51
+ * @returns The part with its compatibility blocks resolved.
52
+ */
53
+ function resolveAlternateContent(xml) {
54
+ let out = xml;
55
+ for (let pass = 0; pass < MAX_DEPTH; pass++) {
56
+ if (!out.includes("AlternateContent")) return out;
57
+ let cursor = 0;
58
+ let next = "";
59
+ OPEN.lastIndex = 0;
60
+ for (let found = OPEN.exec(out); found; found = OPEN.exec(out)) {
61
+ const el = elementAt(out, found.index, "AlternateContent");
62
+ if (!el) break;
63
+ next += out.slice(cursor, found.index) + fallbackOf(el.inner);
64
+ cursor = el.end;
65
+ OPEN.lastIndex = cursor;
66
+ }
67
+ out = next + out.slice(cursor);
68
+ }
69
+ return out;
70
+ }
71
+ //#endregion
72
+ export { resolveAlternateContent };
@@ -2,6 +2,7 @@ import { XMLParser } from "fast-xml-parser";
2
2
  //#region src/core/opc/core-properties.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",
@@ -28,6 +28,7 @@ export declare class OpcPackage {
28
28
  */
29
29
  private constructor();
30
30
  private readonly relsCache;
31
+ private folded;
31
32
  /**
32
33
  * Unzip and validate a package's bytes. Rejects an OLE compound file (an
33
34
  * encrypted OOXML container or a legacy binary `.doc`/`.xls`) and enforces the
@@ -37,8 +38,20 @@ export declare class OpcPackage {
37
38
  * `_rels/.rels` is missing.
38
39
  */
39
40
  static open(buffer: Uint8Array, options?: OpcOpenOptions): OpcPackage;
40
- /** The bytes of the part at `path`, or undefined when absent. */
41
+ /**
42
+ * The bytes of the part at `path`, or undefined when absent.
43
+ *
44
+ * OPC part names are compared case-insensitively (ISO/IEC 29500-2 §9.1.1.1),
45
+ * so two names differing only in case are the SAME part. Producers do mix
46
+ * them: 123233_charts.xlsx writes `xl/worksheets/Sheet1.xml` and its
47
+ * relationships as `_rels/sheet1.xml.rels`, and an exact lookup found no
48
+ * relationships for that sheet at all — which cost it four charts, silently,
49
+ * because a sheet with no drawing rel is indistinguishable from one with no
50
+ * drawing. An exact hit still wins; the fold is only a fallback.
51
+ */
41
52
  getPart(path: string): Uint8Array | undefined;
53
+ /** Parts keyed by their case-folded path, built once on first miss. */
54
+ private foldedParts;
42
55
  /**
43
56
  * The bytes of the part at `path`.
44
57
  *
@@ -19,6 +19,7 @@ var OpcPackage = class OpcPackage {
19
19
  this.rootRelationships = rootRelationships;
20
20
  }
21
21
  relsCache = /* @__PURE__ */ new Map();
22
+ folded;
22
23
  /**
23
24
  * Unzip and validate a package's bytes. Rejects an OLE compound file (an
24
25
  * encrypted OOXML container or a legacy binary `.doc`/`.xls`) and enforces the
@@ -37,7 +38,7 @@ var OpcPackage = class OpcPackage {
37
38
  let total = 0;
38
39
  let count = 0;
39
40
  let violation;
40
- const entries = unzipSync(buffer, { filter: (info) => {
41
+ const entries = unzipGuarded(buffer, { filter: (info) => {
41
42
  if (++count > maxEntries) {
42
43
  violation ??= `more than ${maxEntries} entries`;
43
44
  return false;
@@ -61,9 +62,32 @@ var OpcPackage = class OpcPackage {
61
62
  if (!relsBytes) throw new Error(`OPC package missing ${ROOT_RELS_PATH}`);
62
63
  return new OpcPackage(parts, parseRelationships(relsBytes));
63
64
  }
64
- /** The bytes of the part at `path`, or undefined when absent. */
65
+ /**
66
+ * The bytes of the part at `path`, or undefined when absent.
67
+ *
68
+ * OPC part names are compared case-insensitively (ISO/IEC 29500-2 §9.1.1.1),
69
+ * so two names differing only in case are the SAME part. Producers do mix
70
+ * them: 123233_charts.xlsx writes `xl/worksheets/Sheet1.xml` and its
71
+ * relationships as `_rels/sheet1.xml.rels`, and an exact lookup found no
72
+ * relationships for that sheet at all — which cost it four charts, silently,
73
+ * because a sheet with no drawing rel is indistinguishable from one with no
74
+ * drawing. An exact hit still wins; the fold is only a fallback.
75
+ */
65
76
  getPart(path) {
66
- return this.parts.get(normalizePath(path));
77
+ const normalized = normalizePath(path);
78
+ return this.parts.get(normalized) ?? this.foldedParts().get(normalized.toLowerCase());
79
+ }
80
+ /** Parts keyed by their case-folded path, built once on first miss. */
81
+ foldedParts() {
82
+ if (!this.folded) {
83
+ const folded = /* @__PURE__ */ new Map();
84
+ for (const [path, data] of this.parts) {
85
+ const key = path.toLowerCase();
86
+ if (!folded.has(key)) folded.set(key, data);
87
+ }
88
+ this.folded = folded;
89
+ }
90
+ return this.folded;
67
91
  }
68
92
  /**
69
93
  * The bytes of the part at `path`.
@@ -92,7 +116,7 @@ var OpcPackage = class OpcPackage {
92
116
  const dir = slash >= 0 ? normalized.substring(0, slash) : "";
93
117
  const base = slash >= 0 ? normalized.substring(slash + 1) : normalized;
94
118
  const relsPath = dir.length > 0 ? `${dir}/_rels/${base}.rels` : `_rels/${base}.rels`;
95
- const data = this.parts.get(relsPath);
119
+ const data = this.getPart(relsPath);
96
120
  const rels = data ? parseRelationships(data) : [];
97
121
  this.relsCache.set(normalized, rels);
98
122
  return rels;
@@ -162,5 +186,20 @@ function collapseDotSegments(p) {
162
186
  }
163
187
  return out.join("/");
164
188
  }
189
+ /**
190
+ * {@link unzipSync}, with a decompression failure turned into a named refusal.
191
+ *
192
+ * @param buffer The archive bytes.
193
+ * @param options The fflate options (the zip-bomb entry filter).
194
+ * @returns The decompressed entries.
195
+ * @throws Error naming the archive as invalid when inflation fails.
196
+ */
197
+ function unzipGuarded(buffer, options) {
198
+ try {
199
+ return unzipSync(buffer, options);
200
+ } catch (e) {
201
+ throw new Error(`invalid zip data: ${e instanceof Error ? e.message : String(e)}`);
202
+ }
203
+ }
165
204
  //#endregion
166
205
  export { OpcPackage };
@@ -2,6 +2,7 @@ import { XMLParser } from "fast-xml-parser";
2
2
  //#region src/core/opc/relationships.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
  parseAttributeValue: false,