reamkit 1.16.0 → 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 (65) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/project.js +5 -1
  3. package/dist/esm/core/document-model/index.d.ts +1 -1
  4. package/dist/esm/core/document-model/types.d.ts +473 -7
  5. package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
  6. package/dist/esm/core/drawingml/chart-geometry.js +58 -9
  7. package/dist/esm/core/drawingml/chart-parser.js +44 -5
  8. package/dist/esm/core/drawingml/colors.d.ts +1 -1
  9. package/dist/esm/core/drawingml/colors.js +27 -1
  10. package/dist/esm/core/drawingml/preset-geometry.js +233 -0
  11. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  12. package/dist/esm/core/drawingml/shape-render.js +116 -3
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +19 -1
  15. package/dist/esm/core/font/measure.d.ts +5 -0
  16. package/dist/esm/core/font/measure.js +21 -1
  17. package/dist/esm/core/images.d.ts +2 -2
  18. package/dist/esm/core/images.js +181 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/numbering/apply.d.ts +5 -3
  21. package/dist/esm/core/numbering/apply.js +42 -11
  22. package/dist/esm/core/numbering/index.js +2 -0
  23. package/dist/esm/core/numbering/state.d.ts +13 -1
  24. package/dist/esm/core/numbering/state.js +256 -15
  25. package/dist/esm/core/po-helpers.js +16 -1
  26. package/dist/esm/core/style-cascade/resolver.js +66 -11
  27. package/dist/esm/core/style-cascade/table.js +35 -11
  28. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  29. package/dist/esm/core/style-cascade/types.js +4 -0
  30. package/dist/esm/core/vector.d.ts +17 -0
  31. package/dist/esm/excel/print-model.js +1 -1
  32. package/dist/esm/excel/sheet-to-flow.js +10 -8
  33. package/dist/esm/excel/worksheet-parser.js +1 -1
  34. package/dist/esm/html/html-writer.js +18 -4
  35. package/dist/esm/layout/page-doc.d.ts +62 -2
  36. package/dist/esm/layout/styled-layout.d.ts +44 -2
  37. package/dist/esm/layout/styled-layout.js +1870 -228
  38. package/dist/esm/pdf/shading.js +76 -6
  39. package/dist/esm/pdf/styled-page-emitter.js +217 -43
  40. package/dist/esm/pdf/vector-graphics.d.ts +28 -1
  41. package/dist/esm/pdf/vector-graphics.js +94 -11
  42. package/dist/esm/pptx/pptx-reader.js +1 -0
  43. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  44. package/dist/esm/pptx/slide-parser.js +86 -23
  45. package/dist/esm/svg/svg-writer.js +1 -1
  46. package/dist/esm/word/document-parser.d.ts +96 -7
  47. package/dist/esm/word/document-parser.js +537 -116
  48. package/dist/esm/word/docx-reader.js +135 -43
  49. package/dist/esm/word/docx-writer.js +6 -1
  50. package/dist/esm/word/drawing-parser.d.ts +53 -5
  51. package/dist/esm/word/drawing-parser.js +1540 -54
  52. package/dist/esm/word/index.d.ts +4 -4
  53. package/dist/esm/word/numbering-parser.d.ts +2 -1
  54. package/dist/esm/word/numbering-parser.js +107 -21
  55. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  56. package/dist/esm/word/paragraph-properties.js +219 -10
  57. package/dist/esm/word/run-properties.js +45 -1
  58. package/dist/esm/word/settings-parser.d.ts +24 -0
  59. package/dist/esm/word/settings-parser.js +31 -5
  60. package/dist/esm/word/shading.d.ts +11 -0
  61. package/dist/esm/word/shading.js +64 -0
  62. package/dist/esm/word/styles-parser.js +8 -2
  63. package/dist/esm/word/table-parser.d.ts +9 -1
  64. package/dist/esm/word/table-parser.js +182 -30
  65. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
- export type { HyperlinkResolver, ImageResolver, ParseContext } from './document-parser.js';
2
- export type { CommentExtension } from './document-parser.js';
1
+ export type { HyperlinkResolver, ImageResolver, ParseContext, ResolvedDiagram, } from './document-parser.js';
2
+ export type { BlockCounter, CommentExtension } from './document-parser.js';
3
3
  export { extractParagraphs } from './text-extractor.js';
4
- export { parseDocument, parseBodyElements, parseSection, parseSections, parseHeaderFooter, parseNotes, parseComments, parseCommentThreads, parseCommentsExtended, parsePeople, applyAuthorIds, EMPTY_SECTION, } from './document-parser.js';
4
+ export { parseDocument, parseBackgroundColor, parseBackgroundFill, parseBodyElements, parseSection, parseSections, parseHeaderFooter, parseNotes, parseComments, parseCommentThreads, parseCommentsExtended, parsePeople, applyAuthorIds, bodyIndexForBlock, newBlockCounter, EMPTY_SECTION, } from './document-parser.js';
5
5
  export { parseRunProperties } from './run-properties.js';
6
- export { parseParagraphProperties } from './paragraph-properties.js';
6
+ export { HTML_AUTO_SPACING_PT, parseParagraphProperties } from './paragraph-properties.js';
7
7
  export { parseStyles } from './styles-parser.js';
8
8
  export { parseTable } from './table-parser.js';
9
9
  export { loadEmbeddedFonts, deobfuscateEmbeddedFont, parseFontTable } from './font-table.js';
@@ -1,4 +1,5 @@
1
1
  import { Numbering } from '../core/document-model/index.js';
2
+ import { ResourceId } from '../core/ir/index.js';
2
3
  /** The empty {@link Numbering} returned when a document has no `numbering.xml`. */
3
4
  export declare const EMPTY_NUMBERING: Numbering;
4
5
  /**
@@ -11,4 +12,4 @@ export declare const EMPTY_NUMBERING: Numbering;
11
12
  * @param data The raw `numbering.xml` bytes.
12
13
  * @returns The parsed numbering, or {@link EMPTY_NUMBERING} when the root is absent.
13
14
  */
14
- export declare function parseNumbering(data: Uint8Array): Numbering;
15
+ export declare function parseNumbering(data: Uint8Array, resolveImage?: (relId: string) => ResourceId | undefined): Numbering;
@@ -1,3 +1,4 @@
1
+ import { pt } from "../core/ir/units.js";
1
2
  import { asArray, asElement, getAttr } from "./xml-helpers.js";
2
3
  import { parseRunProperties } from "./run-properties.js";
3
4
  import { parseParagraphProperties } from "./paragraph-properties.js";
@@ -15,10 +16,28 @@ var parser = new XMLParser({
15
16
  });
16
17
  var FORMATS = new Set([
17
18
  "decimal",
19
+ "decimalZero",
20
+ "decimalFullWidth",
21
+ "ordinal",
18
22
  "lowerLetter",
19
23
  "upperLetter",
20
24
  "lowerRoman",
21
25
  "upperRoman",
26
+ "ideographTraditional",
27
+ "ideographZodiac",
28
+ "ideographLegalTraditional",
29
+ "ideographDigital",
30
+ "koreanDigital2",
31
+ "chineseCounting",
32
+ "chineseCountingThousand",
33
+ "japaneseCounting",
34
+ "koreanCounting",
35
+ "taiwaneseCounting",
36
+ "taiwaneseCountingThousand",
37
+ "hebrew1",
38
+ "hebrew2",
39
+ "decimalEnclosedCircle",
40
+ "chicago",
22
41
  "bullet",
23
42
  "none"
24
43
  ]);
@@ -37,10 +56,11 @@ var EMPTY_NUMBERING = {
37
56
  * @param data The raw `numbering.xml` bytes.
38
57
  * @returns The parsed numbering, or {@link EMPTY_NUMBERING} when the root is absent.
39
58
  */
40
- function parseNumbering(data) {
59
+ function parseNumbering(data, resolveImage) {
41
60
  const xml = decoder.decode(data);
42
61
  const root = asElement(parser.parse(xml)["w:numbering"]);
43
62
  if (!root) return EMPTY_NUMBERING;
63
+ const picBullets = parsePicBullets(root, resolveImage);
44
64
  const abstractNums = /* @__PURE__ */ new Map();
45
65
  for (const a of asArray(root["w:abstractNum"])) {
46
66
  const el = asElement(a);
@@ -49,25 +69,8 @@ function parseNumbering(data) {
49
69
  if (!id) continue;
50
70
  const levels = /* @__PURE__ */ new Map();
51
71
  for (const lvlNode of asArray(el["w:lvl"])) {
52
- const lvlEl = asElement(lvlNode);
53
- if (!lvlEl) continue;
54
- const ilvlStr = getAttr(lvlEl, "ilvl");
55
- if (!ilvlStr) continue;
56
- const ilvl = Number(ilvlStr);
57
- if (!Number.isFinite(ilvl)) continue;
58
- const startAttr = getValVal(lvlEl["w:start"]);
59
- const start = startAttr !== void 0 ? Number(startAttr) : 1;
60
- const fmtStr = getValVal(lvlEl["w:numFmt"]) ?? "decimal";
61
- const format = FORMATS.has(fmtStr) ? fmtStr : "decimal";
62
- const lvlText = getValVal(lvlEl["w:lvlText"]) ?? "";
63
- levels.set(ilvl, {
64
- ilvl,
65
- start: Number.isFinite(start) ? start : 1,
66
- format,
67
- lvlText,
68
- paragraphProperties: parseParagraphProperties(lvlEl["w:pPr"]),
69
- runProperties: parseRunProperties(lvlEl["w:rPr"])
70
- });
72
+ const lvl = parseLevel(lvlNode, picBullets);
73
+ if (lvl) levels.set(lvl.ilvl, lvl);
71
74
  }
72
75
  abstractNums.set(id, {
73
76
  id,
@@ -82,9 +85,23 @@ function parseNumbering(data) {
82
85
  if (!numId) continue;
83
86
  const abstractNumId = getValVal(el["w:abstractNumId"]);
84
87
  if (!abstractNumId) continue;
88
+ const startOverrides = /* @__PURE__ */ new Map();
89
+ const levelOverrides = /* @__PURE__ */ new Map();
90
+ for (const o of asArray(el["w:lvlOverride"])) {
91
+ const ovr = asElement(o);
92
+ if (!ovr) continue;
93
+ const ilvl = Number(getAttr(ovr, "ilvl"));
94
+ if (!Number.isFinite(ilvl)) continue;
95
+ const start = Number(getValVal(ovr["w:startOverride"]));
96
+ if (Number.isFinite(start)) startOverrides.set(ilvl, start);
97
+ const lvl = parseLevel(ovr["w:lvl"], picBullets);
98
+ if (lvl) levelOverrides.set(ilvl, lvl);
99
+ }
85
100
  numInstances.set(numId, {
86
101
  numId,
87
- abstractNumId
102
+ abstractNumId,
103
+ ...startOverrides.size > 0 ? { startOverrides } : {},
104
+ ...levelOverrides.size > 0 ? { levelOverrides } : {}
88
105
  });
89
106
  }
90
107
  return {
@@ -92,6 +109,75 @@ function parseNumbering(data) {
92
109
  numInstances
93
110
  };
94
111
  }
112
+ function parseLevel(node, picBullets) {
113
+ const lvlEl = asElement(node);
114
+ if (!lvlEl) return void 0;
115
+ const ilvlStr = getAttr(lvlEl, "ilvl");
116
+ if (!ilvlStr) return void 0;
117
+ const ilvl = Number(ilvlStr);
118
+ if (!Number.isFinite(ilvl)) return void 0;
119
+ const startAttr = getValVal(lvlEl["w:start"]);
120
+ const start = startAttr !== void 0 ? Number(startAttr) : 0;
121
+ const fmtStr = getValVal(lvlEl["w:numFmt"]) ?? "decimal";
122
+ const format = FORMATS.has(fmtStr) ? fmtStr : "decimal";
123
+ const lvlText = getValVal(lvlEl["w:lvlText"]) ?? "";
124
+ const picId = getValVal(lvlEl["w:lvlPicBulletId"]);
125
+ const picBullet = picId !== void 0 ? picBullets.get(picId) : void 0;
126
+ const isLegal = "w:isLgl" in lvlEl && getValVal(lvlEl["w:isLgl"]) !== "0";
127
+ return {
128
+ ilvl,
129
+ start: Number.isFinite(start) ? start : 1,
130
+ format,
131
+ lvlText,
132
+ ...isLegal ? { isLegal: true } : {},
133
+ ...picBullet ? { picBullet } : {},
134
+ paragraphProperties: parseParagraphProperties(lvlEl["w:pPr"]),
135
+ runProperties: parseRunProperties(lvlEl["w:rPr"])
136
+ };
137
+ }
138
+ function parsePicBullets(root, resolveImage) {
139
+ const out = /* @__PURE__ */ new Map();
140
+ for (const node of asArray(root["w:numPicBullet"])) {
141
+ const el = asElement(node);
142
+ if (!el) continue;
143
+ const id = getAttr(el, "numPicBulletId");
144
+ if (id === void 0) continue;
145
+ const pict = asElement(el["w:pict"]);
146
+ const shape = pict ? asElement(pict["v:shape"]) : void 0;
147
+ if (!shape) continue;
148
+ const data = asElement(shape["v:imagedata"]);
149
+ const relId = data ? getAttr(data, "id") ?? getAttr(data, "r:id") : void 0;
150
+ const style = getAttr(shape, "style") ?? "";
151
+ const widthPt = cssLengthPt(/(?:^|;)\s*width\s*:\s*([^;]+)/u.exec(style)?.[1]);
152
+ const heightPt = cssLengthPt(/(?:^|;)\s*height\s*:\s*([^;]+)/u.exec(style)?.[1]);
153
+ if (widthPt === void 0 || heightPt === void 0) continue;
154
+ const resource = relId !== void 0 ? resolveImage?.(relId) : void 0;
155
+ if (resource === void 0) continue;
156
+ out.set(id, {
157
+ resource,
158
+ widthPt: pt(widthPt),
159
+ heightPt: pt(heightPt)
160
+ });
161
+ }
162
+ return out;
163
+ }
164
+ var CSS_UNITS = new Map([
165
+ ["pt", 1],
166
+ ["in", 72],
167
+ ["cm", 72 / 2.54],
168
+ ["mm", 72 / 25.4],
169
+ ["pc", 12],
170
+ ["px", .75]
171
+ ]);
172
+ function cssLengthPt(raw) {
173
+ const m = raw !== void 0 ? /^\s*(-?[\d.]+)\s*([a-z%]*)\s*$/u.exec(raw) : null;
174
+ if (!m) return void 0;
175
+ const n = Number(m[1]);
176
+ if (!Number.isFinite(n)) return void 0;
177
+ const unit = m[2] === "" ? "pt" : m[2];
178
+ const scale = CSS_UNITS.get(unit);
179
+ return scale === void 0 ? void 0 : n * scale;
180
+ }
95
181
  function getValVal(node) {
96
182
  if (!node || typeof node !== "object") return void 0;
97
183
  const v = node["@_w:val"] ?? node["@_val"];
@@ -1,4 +1,11 @@
1
1
  import { ParagraphProperties } from '../core/document-model/index.js';
2
+ /**
3
+ * §17.3.1.1/§17.3.1.3 — the gap `w:*Autospacing` asks the consumer for. Word's
4
+ * own automatic value is HTML's 14pt, and only a document old enough to state
5
+ * no `w:compatSetting compatibilityMode` still gets it: from Word 2007 on the
6
+ * automatic value is nothing at all, which is what both references leave.
7
+ */
8
+ export declare const HTML_AUTO_SPACING_PT = 14;
2
9
  /**
3
10
  * Parse a `w:pPr` element (ECMA-376 Part 1 §17.3.1) into {@link ParagraphProperties}.
4
11
  * Reads the style reference, alignment, spacing, indentation, page-break-before,
@@ -7,6 +14,9 @@ import { ParagraphProperties } from '../core/document-model/index.js';
7
14
  *
8
15
  * @param pPr The `w:pPr` element in flat (fast-xml-parser) shape, or anything
9
16
  * non-element (yielding an empty result).
17
+ * @param autoSpacingPt What `w:beforeAutospacing`/`w:afterAutospacing` resolve
18
+ * to — {@link HTML_AUTO_SPACING_PT} for a document that states no
19
+ * compatibility mode, and 0 (the default) for every Word 2007-or-later one.
10
20
  * @returns The extracted properties; an empty object when `pPr` is absent.
11
21
  */
12
- export declare function parseParagraphProperties(pPr: unknown): ParagraphProperties;
22
+ export declare function parseParagraphProperties(pPr: unknown, autoSpacingPt?: number): ParagraphProperties;
@@ -1,5 +1,5 @@
1
- import { twipsToPt } from "../core/ir/units.js";
2
- import { asElement, getAttr, getVal, parseIntAttr, parseToggle } from "./xml-helpers.js";
1
+ import { eighthPtToPt, pt, twipsToPt } from "../core/ir/units.js";
2
+ import { asArray, asElement, getAttr, getVal, parseIntAttr, parseToggle } from "./xml-helpers.js";
3
3
  import { parseRunProperties } from "./run-properties.js";
4
4
  //#region src/word/paragraph-properties.ts
5
5
  var ALIGNMENTS = new Set([
@@ -14,6 +14,83 @@ var LINE_RULES = new Set([
14
14
  "exact",
15
15
  "atLeast"
16
16
  ]);
17
+ function isAutospacing(node, name) {
18
+ const v = getAttr(node, name);
19
+ return v !== void 0 && v !== "false" && v !== "0" && v !== "off";
20
+ }
21
+ var X_ALIGNS = new Set([
22
+ "left",
23
+ "center",
24
+ "right",
25
+ "inside",
26
+ "outside"
27
+ ]);
28
+ var Y_ALIGNS = new Set([
29
+ "top",
30
+ "center",
31
+ "bottom",
32
+ "inside",
33
+ "outside"
34
+ ]);
35
+ var ANCHORS = new Set([
36
+ "text",
37
+ "margin",
38
+ "page"
39
+ ]);
40
+ var WRAPS = new Set([
41
+ "auto",
42
+ "around",
43
+ "none",
44
+ "notBeside",
45
+ "tight",
46
+ "through"
47
+ ]);
48
+ /**
49
+ * §17.3.1.11 `w:framePr` — the box a floating text frame takes and where it
50
+ * hangs. Lengths are twips; the alignments and anchors are read only when they
51
+ * name a value the spec defines.
52
+ *
53
+ * @param node The `w:framePr` element in flat shape.
54
+ * @returns The frame, or `undefined` when the element says nothing usable.
55
+ */
56
+ function parseFramePr(node) {
57
+ if (!asElement(node)) return void 0;
58
+ const twips = (name) => {
59
+ const v = parseIntAttr(node, name);
60
+ return v === void 0 ? void 0 : twipsToPt(v);
61
+ };
62
+ const pick = (name, allowed) => {
63
+ const v = getAttr(node, name);
64
+ return v !== void 0 && allowed.has(v) ? v : void 0;
65
+ };
66
+ const rule = getAttr(node, "hRule");
67
+ const out = {};
68
+ const w = twips("w");
69
+ const h = twips("h");
70
+ const x = twips("x");
71
+ const y = twips("y");
72
+ const hSpace = twips("hSpace");
73
+ const vSpace = twips("vSpace");
74
+ if (w !== void 0) out.widthPt = w;
75
+ if (h !== void 0) out.heightPt = h;
76
+ if (rule === "auto" || rule === "exact" || rule === "atLeast") out.heightRule = rule;
77
+ if (x !== void 0) out.xPt = x;
78
+ if (y !== void 0) out.yPt = y;
79
+ if (hSpace !== void 0) out.hSpacePt = hSpace;
80
+ if (vSpace !== void 0) out.vSpacePt = vSpace;
81
+ const xAlign = pick("xAlign", X_ALIGNS);
82
+ const yAlign = pick("yAlign", Y_ALIGNS);
83
+ const hAnchor = pick("hAnchor", ANCHORS);
84
+ const vAnchor = pick("vAnchor", ANCHORS);
85
+ const wrap = pick("wrap", WRAPS);
86
+ if (xAlign) out.xAlign = xAlign;
87
+ if (yAlign) out.yAlign = yAlign;
88
+ if (hAnchor) out.hAnchor = hAnchor;
89
+ if (vAnchor) out.vAnchor = vAnchor;
90
+ if (wrap) out.wrap = wrap;
91
+ if (!(w !== void 0 && w > 0 || h !== void 0 && h > 0) && !((x !== void 0 || y !== void 0 || xAlign !== void 0 || yAlign !== void 0) && (x ?? 0) + (y ?? 0) !== 0)) return void 0;
92
+ return Object.keys(out).length > 0 ? out : void 0;
93
+ }
17
94
  /**
18
95
  * Parse a `w:pPr` element (ECMA-376 Part 1 §17.3.1) into {@link ParagraphProperties}.
19
96
  * Reads the style reference, alignment, spacing, indentation, page-break-before,
@@ -22,9 +99,12 @@ var LINE_RULES = new Set([
22
99
  *
23
100
  * @param pPr The `w:pPr` element in flat (fast-xml-parser) shape, or anything
24
101
  * non-element (yielding an empty result).
102
+ * @param autoSpacingPt What `w:beforeAutospacing`/`w:afterAutospacing` resolve
103
+ * to — {@link HTML_AUTO_SPACING_PT} for a document that states no
104
+ * compatibility mode, and 0 (the default) for every Word 2007-or-later one.
25
105
  * @returns The extracted properties; an empty object when `pPr` is absent.
26
106
  */
27
- function parseParagraphProperties(pPr) {
107
+ function parseParagraphProperties(pPr, autoSpacingPt = 0) {
28
108
  const el = asElement(pPr);
29
109
  if (!el) return {};
30
110
  const out = {};
@@ -42,6 +122,8 @@ function parseParagraphProperties(pPr) {
42
122
  const after = parseIntAttr(node, "after");
43
123
  const line = parseIntAttr(node, "line");
44
124
  const lineRule = getAttr(node, "lineRule");
125
+ if (isAutospacing(node, "beforeAutospacing")) out.spacingBeforeAuto = pt(autoSpacingPt);
126
+ if (isAutospacing(node, "afterAutospacing")) out.spacingAfterAuto = pt(autoSpacingPt);
45
127
  if (before !== void 0) out.spacingBefore = twipsToPt(before);
46
128
  if (after !== void 0) out.spacingAfter = twipsToPt(after);
47
129
  if (line !== void 0) out.spacingLine = twipsToPt(line);
@@ -58,6 +140,34 @@ function parseParagraphProperties(pPr) {
58
140
  if (firstLine !== void 0) out.indentFirstLine = twipsToPt(firstLine);
59
141
  else if (hanging !== void 0) out.indentFirstLine = twipsToPt(-hanging);
60
142
  }
143
+ if ("w:framePr" in el) {
144
+ const frame = parseFramePr(el["w:framePr"]);
145
+ if (frame) out.frame = frame;
146
+ }
147
+ if ("w:tabs" in el) {
148
+ const stops = parseTabs(el["w:tabs"]);
149
+ if (stops.length > 0) out.tabs = stops;
150
+ }
151
+ if ("w:pBdr" in el) {
152
+ const borders = parseParagraphBorders(el["w:pBdr"]);
153
+ if (borders) out.borders = borders;
154
+ }
155
+ if ("w:shd" in el) {
156
+ const fill = getAttr(el["w:shd"], "fill");
157
+ if (fill && fill !== "auto" && /^[0-9A-Fa-f]{6}$/u.test(fill)) out.shading = { colorHex: fill.toUpperCase() };
158
+ }
159
+ if ("w:textDirection" in el) {
160
+ const v = getVal(el["w:textDirection"]);
161
+ if (v === "btLr" || v === "tbRl") out.textDirection = v;
162
+ }
163
+ if ("w:snapToGrid" in el) {
164
+ const v = parseToggle(el["w:snapToGrid"]);
165
+ if (v !== void 0) out.snapToGrid = v;
166
+ }
167
+ if ("w:contextualSpacing" in el) {
168
+ const v = parseToggle(el["w:contextualSpacing"]);
169
+ if (v !== void 0) out.contextualSpacing = v;
170
+ }
61
171
  if ("w:pageBreakBefore" in el) {
62
172
  const v = parseToggle(el["w:pageBreakBefore"]);
63
173
  if (v !== void 0) out.pageBreakBefore = v;
@@ -77,13 +187,13 @@ function parseParagraphProperties(pPr) {
77
187
  const ilvlVal = numPr["w:ilvl"];
78
188
  const numIdAttr = typeof numIdVal === "object" && numIdVal !== null ? getAttr(numIdVal, "val") : void 0;
79
189
  const ilvlAttr = typeof ilvlVal === "object" && ilvlVal !== null ? getAttr(ilvlVal, "val") : void 0;
80
- if (numIdAttr !== void 0) {
81
- const ilvlNum = ilvlAttr !== void 0 ? Number(ilvlAttr) : 0;
82
- out.numbering = {
83
- numId: numIdAttr,
84
- ilvl: Number.isFinite(ilvlNum) ? ilvlNum : 0
85
- };
86
- }
190
+ const ilvlNum = ilvlAttr !== void 0 ? Number(ilvlAttr) : 0;
191
+ const ilvl = Number.isFinite(ilvlNum) ? ilvlNum : 0;
192
+ if (numIdAttr !== void 0) out.numbering = {
193
+ numId: numIdAttr,
194
+ ilvl
195
+ };
196
+ else if (ilvlAttr !== void 0) out.numberingLevel = ilvl;
87
197
  }
88
198
  }
89
199
  if ("w:rPr" in el) {
@@ -92,5 +202,104 @@ function parseParagraphProperties(pPr) {
92
202
  }
93
203
  return out;
94
204
  }
205
+ var TAB_ALIGNMENTS = new Map([
206
+ ["left", "left"],
207
+ ["start", "left"],
208
+ ["center", "center"],
209
+ ["right", "right"],
210
+ ["end", "right"],
211
+ ["decimal", "decimal"]
212
+ ]);
213
+ var TAB_LEADERS = new Set([
214
+ "dot",
215
+ "hyphen",
216
+ "underscore",
217
+ "middleDot"
218
+ ]);
219
+ var BORDER_STYLES = new Set([
220
+ "none",
221
+ "single",
222
+ "double",
223
+ "thick",
224
+ "dotted",
225
+ "dashed",
226
+ "dashSmallGap"
227
+ ]);
228
+ /**
229
+ * §17.18.2 ST_Border — the rule's pattern, of which the standard names some
230
+ * hundred and eighty. The handful we draw pass through; `nil` and `none` are no
231
+ * rule at all; everything else is a rule we cannot draw exactly, and a solid
232
+ * one of the stated width and colour is far closer than none. Rejected
233
+ * outright, SdtContent.docx lost the `thickThinSmallGap` rule under its header.
234
+ *
235
+ * @param val The `w:val` attribute.
236
+ * @returns The style to draw, or `undefined` when there is nothing to draw.
237
+ */
238
+ function borderStyleOf(val) {
239
+ if (!val) return void 0;
240
+ if (val === "nil" || val === "none") return "none";
241
+ return BORDER_STYLES.has(val) ? val : "single";
242
+ }
243
+ /**
244
+ * §17.3.1.24 `w:pBdr` — the rules around a paragraph. Spelled exactly as a
245
+ * cell's `w:tcBorders` is, but reached through the flat parse shape this module
246
+ * works in, and with the `w:space` a cell border does not have.
247
+ *
248
+ * @param node The `w:pBdr` element.
249
+ * @returns The edges that name a rule, or `undefined` when none do.
250
+ */
251
+ function parseParagraphBorders(node) {
252
+ const el = asElement(node);
253
+ if (!el) return void 0;
254
+ const edge = (...names) => {
255
+ const b = asElement(names.map((n) => el[n]).find((v) => v !== void 0));
256
+ if (!b) return void 0;
257
+ const style = borderStyleOf(getVal(b));
258
+ if (!style) return void 0;
259
+ const sz = parseIntAttr(b, "sz");
260
+ const space = parseIntAttr(b, "space");
261
+ const color = getAttr(b, "color");
262
+ return {
263
+ style,
264
+ ...sz !== void 0 ? { width: eighthPtToPt(sz) } : {},
265
+ ...space !== void 0 ? { spacePt: pt(space) } : {},
266
+ ...color && color !== "auto" && /^[0-9A-Fa-f]{6}$/u.test(color) ? { colorHex: color.toUpperCase() } : {}
267
+ };
268
+ };
269
+ const out = {};
270
+ const top = edge("w:top");
271
+ const bottom = edge("w:bottom");
272
+ const left = edge("w:left", "w:start");
273
+ const right = edge("w:right", "w:end");
274
+ if (top) out.top = top;
275
+ if (bottom) out.bottom = bottom;
276
+ if (left) out.left = left;
277
+ if (right) out.right = right;
278
+ return Object.keys(out).length > 0 ? out : void 0;
279
+ }
280
+ /**
281
+ * §17.3.1.37 `w:tabs` — the paragraph's own stops, in ascending position.
282
+ *
283
+ * @param node The `w:tabs` element.
284
+ * @returns The stops that place text, sorted; `[]` when it declares none.
285
+ */
286
+ function parseTabs(node) {
287
+ const out = [];
288
+ for (const raw of asArray(asElement(node)?.["w:tab"])) {
289
+ const el = asElement(raw);
290
+ if (!el) continue;
291
+ const pos = parseIntAttr(el, "pos");
292
+ if (pos === void 0) continue;
293
+ const alignment = TAB_ALIGNMENTS.get(getAttr(el, "val") ?? "left");
294
+ if (!alignment) continue;
295
+ const leader = getAttr(el, "leader");
296
+ out.push({
297
+ positionPt: twipsToPt(pos),
298
+ alignment,
299
+ ...leader !== void 0 && TAB_LEADERS.has(leader) ? { leader } : {}
300
+ });
301
+ }
302
+ return out.sort((a, b) => a.positionPt - b.positionPt);
303
+ }
95
304
  //#endregion
96
305
  export { parseParagraphProperties };
@@ -1,5 +1,6 @@
1
- import { halfPtToPt } from "../core/ir/units.js";
1
+ import { halfPtToPt, twipsToPt } from "../core/ir/units.js";
2
2
  import { asElement, getAttr, getVal, parseIntAttr, parseToggle } from "./xml-helpers.js";
3
+ import { shadingFillHex } from "./shading.js";
3
4
  //#region src/word/run-properties.ts
4
5
  var UNDERLINE_STYLES = new Set([
5
6
  "none",
@@ -17,6 +18,24 @@ var VERTICAL_ALIGNS = new Set([
17
18
  "superscript",
18
19
  "subscript"
19
20
  ]);
21
+ var HIGHLIGHT_COLORS = new Map([
22
+ ["black", "000000"],
23
+ ["blue", "0000FF"],
24
+ ["cyan", "00FFFF"],
25
+ ["darkBlue", "000080"],
26
+ ["darkCyan", "008080"],
27
+ ["darkGray", "808080"],
28
+ ["darkGreen", "008000"],
29
+ ["darkMagenta", "800080"],
30
+ ["darkRed", "800000"],
31
+ ["darkYellow", "808000"],
32
+ ["green", "00FF00"],
33
+ ["lightGray", "C0C0C0"],
34
+ ["magenta", "FF00FF"],
35
+ ["red", "FF0000"],
36
+ ["white", "FFFFFF"],
37
+ ["yellow", "FFFF00"]
38
+ ]);
20
39
  /**
21
40
  * Parse a run-properties element (`w:rPr`, ECMA-376 Part 1 §17.3.2) into the
22
41
  * typed {@link RunProperties}. Reads the character formatting the renderer needs:
@@ -47,9 +66,19 @@ function parseRunProperties(rPr) {
47
66
  const v = parseToggle(el["w:strike"]);
48
67
  if (v !== void 0) out.strike = v;
49
68
  }
69
+ if ("w:caps" in el) {
70
+ const v = parseToggle(el["w:caps"]);
71
+ if (v !== void 0) out.caps = v;
72
+ }
73
+ if ("w:smallCaps" in el) {
74
+ const v = parseToggle(el["w:smallCaps"]);
75
+ if (v !== void 0) out.smallCaps = v;
76
+ }
50
77
  if ("w:u" in el) {
51
78
  const v = getVal(el["w:u"]);
52
79
  if (v && UNDERLINE_STYLES.has(v)) out.underline = v;
80
+ const c = getAttr(el["w:u"], "color");
81
+ if (c && /^[0-9A-Fa-f]{6}$/u.test(c)) out.underlineColorHex = c.toUpperCase();
53
82
  }
54
83
  if ("w:sz" in el) {
55
84
  const v = parseIntAttr(el["w:sz"], "val");
@@ -58,6 +87,7 @@ function parseRunProperties(rPr) {
58
87
  if ("w:color" in el) {
59
88
  const v = getVal(el["w:color"]);
60
89
  if (v && /^[0-9A-Fa-f]{6}$/.test(v)) out.colorHex = v.toUpperCase();
90
+ else if (v === "auto") out.colorHex = "000000";
61
91
  }
62
92
  if ("w:rFonts" in el) {
63
93
  const ff = parseFontFamily(el["w:rFonts"]);
@@ -71,6 +101,20 @@ function parseRunProperties(rPr) {
71
101
  const v = parseToggle(el["w:rtl"]);
72
102
  if (v !== void 0) out.rtl = v;
73
103
  }
104
+ if ("w:spacing" in el) {
105
+ const v = getVal(el["w:spacing"]);
106
+ const n = v === void 0 ? NaN : Number(v);
107
+ if (Number.isFinite(n) && n !== 0) out.letterSpacingPt = twipsToPt(n);
108
+ }
109
+ if ("w:shd" in el) {
110
+ const shd = el["w:shd"];
111
+ const hex = shadingFillHex(getVal(shd), getAttr(shd, "color"), getAttr(shd, "fill"));
112
+ if (hex !== void 0 && hex !== "FFFFFF") out.shadingColorHex = hex;
113
+ }
114
+ if ("w:highlight" in el) {
115
+ const hex = HIGHLIGHT_COLORS.get(getVal(el["w:highlight"]) ?? "");
116
+ if (hex !== void 0) out.shadingColorHex = hex;
117
+ }
74
118
  if ("w:lang" in el) {
75
119
  const v = getAttr(el["w:lang"], "val");
76
120
  if (v) out.lang = v;
@@ -5,6 +5,30 @@ export interface DocumentSettings {
5
5
  * pages use the `'even'` header/footer references instead of `'default'`.
6
6
  */
7
7
  readonly evenAndOddHeaders: boolean;
8
+ /**
9
+ * ECMA-376 §17.15.1.35 — `w:compat/w:doNotExpandShiftReturn`. When `true`, a
10
+ * justified line that ends at a soft line break (`w:br`) is left at its
11
+ * natural width instead of being stretched to the measure.
12
+ */
13
+ readonly doNotExpandShiftReturn: boolean;
14
+ /**
15
+ * ECMA-376 §17.15.1.28 — `w:displayBackgroundShape`. The page background
16
+ * (§17.2.1 `w:background`) is drawn only when this is set; without it Word
17
+ * keeps the colour but prints white.
18
+ */
19
+ readonly displayBackgroundShape: boolean;
20
+ /**
21
+ * ECMA-376 §17.15.1.38 — `w:gutterAtTop`. The binding space (`w:pgMar
22
+ * @w:gutter`) is added to the TOP margin rather than the left.
23
+ */
24
+ readonly gutterAtTop: boolean;
25
+ /**
26
+ * `w:compat/w:compatSetting @w:name="compatibilityMode"` — the Word version
27
+ * the document was saved for. Absent means the file predates the setting,
28
+ * which is the only case where §17.3.1.1's automatic paragraph spacing is
29
+ * still HTML's 14pt rather than nothing.
30
+ */
31
+ readonly compatibilityMode?: number;
8
32
  }
9
33
  /** The all-defaults {@link DocumentSettings}, returned when no `w:settings` root is found. */
10
34
  export declare const EMPTY_SETTINGS: DocumentSettings;
@@ -12,7 +12,12 @@ var parser = new XMLParser({
12
12
  trimValues: false
13
13
  });
14
14
  /** The all-defaults {@link DocumentSettings}, returned when no `w:settings` root is found. */
15
- var EMPTY_SETTINGS = { evenAndOddHeaders: false };
15
+ var EMPTY_SETTINGS = {
16
+ evenAndOddHeaders: false,
17
+ doNotExpandShiftReturn: false,
18
+ displayBackgroundShape: false,
19
+ gutterAtTop: false
20
+ };
16
21
  /**
17
22
  * Parse `word/settings.xml` (ECMA-376 §17.15), extracting only the flags the
18
23
  * renderer needs; everything else (compat, autoSpaceDE, …) is ignored.
@@ -25,11 +30,32 @@ function parseSettings(data) {
25
30
  const settings = poFindByPath(parser.parse(xml), ["w:settings"]);
26
31
  if (!settings) return EMPTY_SETTINGS;
27
32
  let evenAndOddHeaders = false;
28
- for (const child of poChildren(settings)) if (poIs(child, "w:evenAndOddHeaders")) {
29
- const val = poAttr(child, "val");
30
- evenAndOddHeaders = val === void 0 || val === "" || val !== "0" && val !== "false";
33
+ let doNotExpandShiftReturn = false;
34
+ let displayBackgroundShape = false;
35
+ let gutterAtTop = false;
36
+ let compatibilityMode;
37
+ for (const child of poChildren(settings)) if (poIs(child, "w:evenAndOddHeaders")) evenAndOddHeaders = onOff(child);
38
+ else if (poIs(child, "w:displayBackgroundShape")) displayBackgroundShape = onOff(child);
39
+ else if (poIs(child, "w:gutterAtTop")) gutterAtTop = onOff(child);
40
+ else if (poIs(child, "w:compat")) {
41
+ for (const flag of poChildren(child)) if (poIs(flag, "w:doNotExpandShiftReturn")) doNotExpandShiftReturn = onOff(flag);
42
+ else if (poIs(flag, "w:compatSetting") && poAttr(flag, "name") === "compatibilityMode") {
43
+ const n = Number(poAttr(flag, "val"));
44
+ if (Number.isFinite(n)) compatibilityMode = n;
45
+ }
31
46
  }
32
- return { evenAndOddHeaders };
47
+ return {
48
+ evenAndOddHeaders,
49
+ doNotExpandShiftReturn,
50
+ displayBackgroundShape,
51
+ gutterAtTop,
52
+ ...compatibilityMode !== void 0 ? { compatibilityMode } : {}
53
+ };
54
+ }
55
+ /** §17.17.4 ST_OnOff: an absent or empty `w:val` means on; `0`/`false` mean off. */
56
+ function onOff(node) {
57
+ const val = poAttr(node, "val");
58
+ return val === void 0 || val === "" || val !== "0" && val !== "false";
33
59
  }
34
60
  //#endregion
35
61
  export { EMPTY_SETTINGS, parseSettings };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The colour a `w:shd` actually paints: its `w:fill` background with the
3
+ * pattern colour blended in at the pattern's density. `clear` (and an absent
4
+ * `w:val`) is the fill alone; `nil` paints nothing.
5
+ *
6
+ * @param val `w:val` — the pattern.
7
+ * @param color `w:color` — the pattern's own colour (`auto` ⇒ black).
8
+ * @param fill `w:fill` — the background (`auto` ⇒ white).
9
+ * @returns The 6-hex colour to paint, or `undefined` when nothing is painted.
10
+ */
11
+ export declare function shadingFillHex(val: string | undefined, color: string | undefined, fill: string | undefined): string | undefined;