@beyondwork/docx-react-component 1.0.102 → 1.0.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/public-types.ts +63 -1
- package/src/api/v3/_runtime-handle.ts +2 -0
- package/src/api/v3/ai/outline.ts +2 -7
- package/src/api/v3/runtime/geometry.ts +79 -0
- package/src/core/commands/formatting-commands.ts +8 -7
- package/src/core/commands/paragraph-layout-commands.ts +11 -10
- package/src/core/commands/section-layout-commands.ts +7 -6
- package/src/core/commands/style-commands.ts +3 -2
- package/src/io/normalize/normalize-text.ts +6 -5
- package/src/io/ooxml/parse-anchor.ts +15 -15
- package/src/io/ooxml/parse-drawing.ts +103 -5
- package/src/io/ooxml/parse-fields.ts +43 -21
- package/src/io/ooxml/parse-font-table.ts +2 -1
- package/src/io/ooxml/parse-footnotes.ts +3 -2
- package/src/io/ooxml/parse-headers-footers.ts +7 -6
- package/src/io/ooxml/parse-main-document.ts +41 -40
- package/src/io/ooxml/parse-numbering.ts +3 -2
- package/src/io/ooxml/parse-object.ts +6 -6
- package/src/io/ooxml/parse-paragraph-formatting.ts +12 -11
- package/src/io/ooxml/parse-picture.ts +16 -16
- package/src/io/ooxml/parse-run-formatting.ts +11 -10
- package/src/io/ooxml/parse-settings.ts +2 -1
- package/src/io/ooxml/parse-shapes.ts +148 -17
- package/src/io/ooxml/parse-styles.ts +16 -16
- package/src/io/ooxml/parse-theme.ts +5 -4
- package/src/model/canonical-document.ts +869 -836
- package/src/model/canonical-layout-inputs.ts +979 -0
- package/src/model/layout/index.ts +6 -0
- package/src/model/layout/page-graph-types.ts +61 -0
- package/src/model/layout/runtime-page-graph-types.ts +10 -0
- package/src/runtime/collab/runtime-collab-sync.ts +3 -3
- package/src/runtime/debug/build-debug-inspector-snapshot.ts +17 -4
- package/src/runtime/document-runtime.ts +30 -14
- package/src/runtime/event-refresh-hints.ts +3 -0
- package/src/runtime/formatting/document-lookup.ts +3 -2
- package/src/runtime/formatting/formatting-context.ts +176 -34
- package/src/runtime/formatting/index.ts +20 -0
- package/src/runtime/formatting/layout-inputs.ts +320 -0
- package/src/runtime/formatting/numbering/geometry.ts +13 -12
- package/src/runtime/formatting/style-cascade.ts +2 -1
- package/src/runtime/formatting/table-style-resolver.ts +8 -7
- package/src/runtime/geometry/caret-geometry.ts +82 -10
- package/src/runtime/geometry/geometry-facet.ts +36 -0
- package/src/runtime/geometry/geometry-index.ts +891 -0
- package/src/runtime/geometry/geometry-types.ts +221 -1
- package/src/runtime/geometry/index.ts +26 -0
- package/src/runtime/geometry/inert-geometry-facet.ts +3 -0
- package/src/runtime/geometry/replacement-envelope.ts +41 -2
- package/src/runtime/layout/layout-engine-version.ts +16 -1
- package/src/runtime/layout/page-graph.ts +191 -1
- package/src/runtime/prerender/graph-canonicalize.ts +30 -0
- package/src/runtime/surface-projection.ts +74 -39
- package/src/runtime/workflow/coordinator.ts +57 -11
- package/src/session/import/normalize.ts +2 -1
- package/src/session/import/source-package-evidence.ts +612 -1
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +3 -0
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* responsibility post-v2.0; this module emits only data.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { OleEmbedNode } from "../../model/canonical-document.ts";
|
|
21
|
+
import type { OleEmbedNode, Mutable } from "../../model/canonical-document.ts";
|
|
22
22
|
import type { OpcRelationship } from "./part-manifest.ts";
|
|
23
23
|
import type { XmlElementNode } from "./xml-element.ts";
|
|
24
24
|
import { resolveOleRelationship } from "./parse-ole-relationship.ts";
|
|
@@ -93,13 +93,13 @@ export function parseObject(
|
|
|
93
93
|
|
|
94
94
|
const metadata: OleEmbedNode["metadata"] = {};
|
|
95
95
|
if (resolved.originalFilename) {
|
|
96
|
-
metadata.originalFilename = resolved.originalFilename;
|
|
96
|
+
(metadata as Mutable<typeof metadata>).originalFilename = resolved.originalFilename;
|
|
97
97
|
}
|
|
98
98
|
if (classId) {
|
|
99
|
-
metadata.classId = classId;
|
|
99
|
+
(metadata as Mutable<typeof metadata>).classId = classId;
|
|
100
100
|
}
|
|
101
101
|
if (shapeId) {
|
|
102
|
-
metadata.shapeId = shapeId;
|
|
102
|
+
(metadata as Mutable<typeof metadata>).shapeId = shapeId;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
const id =
|
|
@@ -107,7 +107,7 @@ export function parseObject(
|
|
|
107
107
|
? `ole-embed-${resolved.relationshipId}-${occurrenceIndex}`
|
|
108
108
|
: `ole-embed-${resolved.relationshipId}`;
|
|
109
109
|
|
|
110
|
-
const result: OleEmbedNode = {
|
|
110
|
+
const result: Mutable<OleEmbedNode> = {
|
|
111
111
|
type: "ole_embed",
|
|
112
112
|
id,
|
|
113
113
|
embedType: "oleObject",
|
|
@@ -116,7 +116,7 @@ export function parseObject(
|
|
|
116
116
|
rawXml,
|
|
117
117
|
};
|
|
118
118
|
if (progId) {
|
|
119
|
-
result.progId = progId;
|
|
119
|
+
(result as Mutable<typeof result>).progId = progId;
|
|
120
120
|
}
|
|
121
121
|
return result;
|
|
122
122
|
}
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ParagraphShading,
|
|
15
15
|
ParagraphSpacing,
|
|
16
16
|
TabStop,
|
|
17
|
+
Mutable,
|
|
17
18
|
} from "../../model/canonical-document.ts";
|
|
18
19
|
import { readRunProperties } from "./parse-run-formatting.ts";
|
|
19
20
|
import { findChildOptional, localName, readIntAttr, readIntVal, readOnOff } from "./xml-attr-helpers.ts";
|
|
@@ -87,31 +88,31 @@ const PPR_GRAB_BAG_DESCRIPTOR: PropertyGrabBagDescriptor = {
|
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
function readSpacing(node: XmlElementNode): ParagraphSpacing | undefined {
|
|
90
|
-
const out: ParagraphSpacing = {};
|
|
91
|
+
const out: Mutable<ParagraphSpacing> = {};
|
|
91
92
|
const before = readIntAttr(node, "w:before");
|
|
92
93
|
const after = readIntAttr(node, "w:after");
|
|
93
94
|
const line = readIntAttr(node, "w:line");
|
|
94
95
|
const rule = node.attributes["w:lineRule"] ?? node.attributes.lineRule;
|
|
95
|
-
if (before !== undefined) out.before = before;
|
|
96
|
-
if (after !== undefined) out.after = after;
|
|
96
|
+
if (before !== undefined) (out as Mutable<typeof out>).before = before;
|
|
97
|
+
if (after !== undefined) (out as Mutable<typeof out>).after = after;
|
|
97
98
|
if (line !== undefined) out.line = line;
|
|
98
99
|
if (rule) {
|
|
99
100
|
const n = rule.toLowerCase();
|
|
100
|
-
if (n === "auto" || n === "exact") out.lineRule = n;
|
|
101
|
+
if (n === "auto" || n === "exact") (out as Mutable<typeof out>).lineRule = n;
|
|
101
102
|
else if (n === "atleast") out.lineRule = "atLeast";
|
|
102
103
|
}
|
|
103
104
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function readIndent(node: XmlElementNode): ParagraphIndentation | undefined {
|
|
107
|
-
const out: ParagraphIndentation = {};
|
|
108
|
+
const out: Mutable<ParagraphIndentation> = {};
|
|
108
109
|
const left = readIntAttr(node, "w:left") ?? readIntAttr(node, "w:start");
|
|
109
110
|
const right = readIntAttr(node, "w:right") ?? readIntAttr(node, "w:end");
|
|
110
111
|
const firstLine = readIntAttr(node, "w:firstLine");
|
|
111
112
|
const hanging = readIntAttr(node, "w:hanging");
|
|
112
|
-
if (left !== undefined) out.left = left;
|
|
113
|
-
if (right !== undefined) out.right = right;
|
|
114
|
-
if (firstLine !== undefined) out.firstLine = firstLine;
|
|
113
|
+
if (left !== undefined) (out as Mutable<typeof out>).left = left;
|
|
114
|
+
if (right !== undefined) (out as Mutable<typeof out>).right = right;
|
|
115
|
+
if (firstLine !== undefined) (out as Mutable<typeof out>).firstLine = firstLine;
|
|
115
116
|
if (hanging !== undefined) out.hanging = hanging;
|
|
116
117
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
117
118
|
}
|
|
@@ -138,7 +139,7 @@ function readTabStops(node: XmlElementNode): TabStop[] | undefined {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
function readBorders(node: XmlElementNode): ParagraphBorders | undefined {
|
|
141
|
-
const out: ParagraphBorders = {};
|
|
142
|
+
const out: Mutable<ParagraphBorders> = {};
|
|
142
143
|
const sides = ["top", "bottom", "left", "right", "between", "bar"] as const;
|
|
143
144
|
for (const side of sides) {
|
|
144
145
|
const child = findChildOptional(node, side);
|
|
@@ -205,7 +206,7 @@ function readShading(node: XmlElementNode): ParagraphShading | undefined {
|
|
|
205
206
|
* text frames, drop-caps); extension attrs are rare in that corpus.
|
|
206
207
|
*/
|
|
207
208
|
export function readFrameProperties(node: XmlElementNode): FrameProperties | undefined {
|
|
208
|
-
const out: FrameProperties = {};
|
|
209
|
+
const out: Mutable<FrameProperties> = {};
|
|
209
210
|
const width = readIntAttr(node, "w:w");
|
|
210
211
|
if (width !== undefined) out.widthTwips = width;
|
|
211
212
|
const height = readIntAttr(node, "w:h");
|
|
@@ -268,7 +269,7 @@ export function readParagraphProperties(
|
|
|
268
269
|
): CanonicalParagraphFormatting | undefined {
|
|
269
270
|
if (!node) return undefined;
|
|
270
271
|
|
|
271
|
-
const out: CanonicalParagraphFormatting = {};
|
|
272
|
+
const out: Mutable<CanonicalParagraphFormatting> = {};
|
|
272
273
|
|
|
273
274
|
const spacingNode = findChildOptional(node, "spacing");
|
|
274
275
|
if (spacingNode) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PictureContent } from "../../model/canonical-document.ts";
|
|
1
|
+
import type { PictureContent, Mutable } from "../../model/canonical-document.ts";
|
|
2
2
|
import {
|
|
3
3
|
type XmlElementNode,
|
|
4
4
|
findFirstChild,
|
|
@@ -100,21 +100,21 @@ export function parsePicture(graphicDataEl: XmlElementNode): PictureContent | nu
|
|
|
100
100
|
const glowEl = effectLst ? findFirstChild(effectLst, "glow") : undefined;
|
|
101
101
|
const glow = glowEl ? parseGlow(glowEl) : undefined;
|
|
102
102
|
|
|
103
|
-
const result: PictureContent = { type: "picture", blipRef };
|
|
104
|
-
if (isLinked) result.isLinked = true;
|
|
103
|
+
const result: Mutable<PictureContent> = { type: "picture", blipRef };
|
|
104
|
+
if (isLinked) (result as Mutable<typeof result>).isLinked = true;
|
|
105
105
|
if (lum && (lum.bright !== undefined || lum.contrast !== undefined)) {
|
|
106
|
-
result.lum = lum;
|
|
106
|
+
(result as Mutable<typeof result>).lum = lum;
|
|
107
107
|
}
|
|
108
|
-
if (srcRect) result.srcRect = srcRect;
|
|
109
|
-
if (stretch !== undefined) result.stretch = stretch;
|
|
108
|
+
if (srcRect) (result as Mutable<typeof result>).srcRect = srcRect;
|
|
109
|
+
if (stretch !== undefined) (result as Mutable<typeof result>).stretch = stretch;
|
|
110
110
|
if (tile !== undefined) result.tile = tile;
|
|
111
|
-
if (rotation !== undefined) result.rotation = rotation;
|
|
112
|
-
if (flipH !== undefined) result.flipH = flipH;
|
|
113
|
-
if (flipV !== undefined) result.flipV = flipV;
|
|
114
|
-
if (presetGeom) result.presetGeom = presetGeom;
|
|
115
|
-
if (softEdgeRadius !== undefined) result.softEdgeRadius = softEdgeRadius;
|
|
116
|
-
if (outerShadow) result.outerShadow = outerShadow;
|
|
117
|
-
if (glow) result.glow = glow;
|
|
111
|
+
if (rotation !== undefined) (result as Mutable<typeof result>).rotation = rotation;
|
|
112
|
+
if (flipH !== undefined) (result as Mutable<typeof result>).flipH = flipH;
|
|
113
|
+
if (flipV !== undefined) (result as Mutable<typeof result>).flipV = flipV;
|
|
114
|
+
if (presetGeom) (result as Mutable<typeof result>).presetGeom = presetGeom;
|
|
115
|
+
if (softEdgeRadius !== undefined) (result as Mutable<typeof result>).softEdgeRadius = softEdgeRadius;
|
|
116
|
+
if (outerShadow) (result as Mutable<typeof result>).outerShadow = outerShadow;
|
|
117
|
+
if (glow) (result as Mutable<typeof result>).glow = glow;
|
|
118
118
|
return result;
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -172,13 +172,13 @@ function readTileAttrs(
|
|
|
172
172
|
const v = tileEl.attributes[key];
|
|
173
173
|
if (v !== undefined) {
|
|
174
174
|
const n = parseInt(v, 10);
|
|
175
|
-
if (Number.isFinite(n)) result[key] = n;
|
|
175
|
+
if (Number.isFinite(n)) (result as Mutable<typeof result>)[key] = n;
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
const flip = tileEl.attributes.flip;
|
|
179
|
-
if (flip === "x" || flip === "y" || flip === "xy") result.flip = flip;
|
|
179
|
+
if (flip === "x" || flip === "y" || flip === "xy") (result as Mutable<typeof result>).flip = flip;
|
|
180
180
|
const algn = tileEl.attributes.algn;
|
|
181
|
-
if (algn) result.algn = algn;
|
|
181
|
+
if (algn) (result as Mutable<typeof result>).algn = algn;
|
|
182
182
|
return Object.keys(result).length > 0 ? result : {};
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CanonicalRunFormatting,
|
|
3
3
|
ThemeFontSlot,
|
|
4
|
+
Mutable,
|
|
4
5
|
} from "../../model/canonical-document.ts";
|
|
5
6
|
import { findChildOptional, readIntVal, readOnOff, readStringAttr } from "./xml-attr-helpers.ts";
|
|
6
7
|
import type { XmlElementNode } from "./xml-element.ts";
|
|
@@ -58,7 +59,7 @@ export function readRunProperties(
|
|
|
58
59
|
): CanonicalRunFormatting | undefined {
|
|
59
60
|
if (!node) return undefined;
|
|
60
61
|
|
|
61
|
-
const rPr: CanonicalRunFormatting = {};
|
|
62
|
+
const rPr: Mutable<CanonicalRunFormatting> = {};
|
|
62
63
|
|
|
63
64
|
const bold = readOnOff(findChildOptional(node, "b"));
|
|
64
65
|
if (bold !== undefined) rPr.bold = bold;
|
|
@@ -121,9 +122,9 @@ export function readRunProperties(
|
|
|
121
122
|
const hAnsi = rFonts.attributes["w:hAnsi"] ?? rFonts.attributes.hAnsi;
|
|
122
123
|
const eastAsia = rFonts.attributes["w:eastAsia"] ?? rFonts.attributes.eastAsia;
|
|
123
124
|
const cs = rFonts.attributes["w:cs"] ?? rFonts.attributes.cs;
|
|
124
|
-
if (ascii) rPr.fontFamilyAscii = ascii;
|
|
125
|
-
if (hAnsi) rPr.fontFamilyHAnsi = hAnsi;
|
|
126
|
-
if (eastAsia) rPr.fontFamilyEastAsia = eastAsia;
|
|
125
|
+
if (ascii) (rPr as Mutable<typeof rPr>).fontFamilyAscii = ascii;
|
|
126
|
+
if (hAnsi) (rPr as Mutable<typeof rPr>).fontFamilyHAnsi = hAnsi;
|
|
127
|
+
if (eastAsia) (rPr as Mutable<typeof rPr>).fontFamilyEastAsia = eastAsia;
|
|
127
128
|
if (cs) rPr.fontFamilyCs = cs;
|
|
128
129
|
const primary = ascii ?? hAnsi ?? eastAsia ?? cs;
|
|
129
130
|
if (primary) rPr.fontFamily = primary;
|
|
@@ -150,9 +151,9 @@ export function readRunProperties(
|
|
|
150
151
|
rFonts.attributes["w:csTheme"] ??
|
|
151
152
|
rFonts.attributes.csTheme,
|
|
152
153
|
);
|
|
153
|
-
if (asciiTheme) rPr.asciiTheme = asciiTheme;
|
|
154
|
-
if (hAnsiTheme) rPr.hAnsiTheme = hAnsiTheme;
|
|
155
|
-
if (eastAsiaTheme) rPr.eastAsiaTheme = eastAsiaTheme;
|
|
154
|
+
if (asciiTheme) (rPr as Mutable<typeof rPr>).asciiTheme = asciiTheme;
|
|
155
|
+
if (hAnsiTheme) (rPr as Mutable<typeof rPr>).hAnsiTheme = hAnsiTheme;
|
|
156
|
+
if (eastAsiaTheme) (rPr as Mutable<typeof rPr>).eastAsiaTheme = eastAsiaTheme;
|
|
156
157
|
if (csTheme) rPr.csTheme = csTheme;
|
|
157
158
|
}
|
|
158
159
|
|
|
@@ -171,9 +172,9 @@ export function readRunProperties(
|
|
|
171
172
|
color.attributes["w:themeTint"] ?? color.attributes["themeTint"];
|
|
172
173
|
const shade =
|
|
173
174
|
color.attributes["w:themeShade"] ?? color.attributes["themeShade"];
|
|
174
|
-
if (val) rPr.colorHex = val;
|
|
175
|
-
if (theme) rPr.colorThemeSlot = theme;
|
|
176
|
-
if (tint) rPr.colorThemeTint = tint;
|
|
175
|
+
if (val) (rPr as Mutable<typeof rPr>).colorHex = val;
|
|
176
|
+
if (theme) (rPr as Mutable<typeof rPr>).colorThemeSlot = theme;
|
|
177
|
+
if (tint) (rPr as Mutable<typeof rPr>).colorThemeTint = tint;
|
|
177
178
|
if (shade) rPr.colorThemeShade = shade;
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
DocumentSettings,
|
|
6
6
|
FootnoteProperties,
|
|
7
7
|
ThemeColorSlot,
|
|
8
|
+
Mutable,
|
|
8
9
|
} from "../../model/canonical-document.ts";
|
|
9
10
|
import { parseXml } from "./xml-parser.ts";
|
|
10
11
|
import { localName } from "./xml-attr-helpers.ts";
|
|
@@ -160,7 +161,7 @@ function readFootnoteLikeProperties(
|
|
|
160
161
|
): FootnoteProperties | undefined {
|
|
161
162
|
if (!element) return undefined;
|
|
162
163
|
|
|
163
|
-
const result: FootnoteProperties = {};
|
|
164
|
+
const result: Mutable<FootnoteProperties> = {};
|
|
164
165
|
for (const child of element.children) {
|
|
165
166
|
if (child.type !== "element") continue;
|
|
166
167
|
const name = localName(child.name);
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
BlockNode,
|
|
15
15
|
ShapeContent,
|
|
16
16
|
TextBoxBodyProperties,
|
|
17
|
+
Mutable,
|
|
18
|
+
PreserveOnlyObjectSizing,
|
|
17
19
|
} from "../../model/canonical-document.ts";
|
|
18
20
|
import { parseFill } from "./parse-fill.ts";
|
|
19
21
|
import {
|
|
@@ -46,11 +48,12 @@ export interface ParsedWpsShape {
|
|
|
46
48
|
* shape-textbox paragraphs). Same shape + semantics as
|
|
47
49
|
* `ShapeContent.txbxBlocks` on the drawing-frame path.
|
|
48
50
|
*/
|
|
49
|
-
txbxBlocks?:
|
|
51
|
+
txbxBlocks?: BlockNode[];
|
|
50
52
|
/** DrawML geometry preset, e.g. "rect", "roundRect". */
|
|
51
53
|
geometry?: string;
|
|
52
54
|
/** Original drawing XML for lossless round-trip export. */
|
|
53
55
|
rawXml: string;
|
|
56
|
+
preserveOnlyObject?: PreserveOnlyObjectSizing;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export interface ParsedWordArt {
|
|
@@ -61,6 +64,7 @@ export interface ParsedWordArt {
|
|
|
61
64
|
geometry?: string;
|
|
62
65
|
/** Original drawing XML for lossless round-trip export. */
|
|
63
66
|
rawXml: string;
|
|
67
|
+
preserveOnlyObject?: PreserveOnlyObjectSizing;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
export interface ParsedVmlShape {
|
|
@@ -71,6 +75,7 @@ export interface ParsedVmlShape {
|
|
|
71
75
|
text?: string;
|
|
72
76
|
/** Original w:pict XML for lossless round-trip export. */
|
|
73
77
|
rawXml: string;
|
|
78
|
+
preserveOnlyObject?: PreserveOnlyObjectSizing;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
export type ParsedShape = ParsedWpsShape | ParsedWordArt | ParsedVmlShape;
|
|
@@ -112,6 +117,11 @@ export function parseShapeXml(
|
|
|
112
117
|
text: text ?? "",
|
|
113
118
|
...(prst ? { geometry: prst } : {}),
|
|
114
119
|
rawXml: drawingXml,
|
|
120
|
+
preserveOnlyObject: createDrawingPreserveOnlyObject(
|
|
121
|
+
drawingXml,
|
|
122
|
+
"wordart",
|
|
123
|
+
collectRelationshipIds(root),
|
|
124
|
+
),
|
|
115
125
|
};
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -129,7 +139,7 @@ export function parseShapeXml(
|
|
|
129
139
|
// content (CCEP "Copyright CCEP STRICTLY CONFIDENTIAL" footer band)
|
|
130
140
|
// is reachable only via the `.text` summary string — L03 cascade +
|
|
131
141
|
// L11 render can't walk runs/marks.
|
|
132
|
-
let txbxBlocks:
|
|
142
|
+
let txbxBlocks: BlockNode[] | undefined;
|
|
133
143
|
if (txbxContentXml && blockParser) {
|
|
134
144
|
try {
|
|
135
145
|
// The `blockParser` callback is supplied by parse-main-document.ts
|
|
@@ -142,7 +152,7 @@ export function parseShapeXml(
|
|
|
142
152
|
// canonical; a structural `as unknown as BlockNode[]` preserves
|
|
143
153
|
// type safety at every consumer site (L03 cascade, L11 render,
|
|
144
154
|
// validator walk).
|
|
145
|
-
txbxBlocks = blockParser(txbxContentXml) as unknown as
|
|
155
|
+
txbxBlocks = blockParser(txbxContentXml) as unknown as BlockNode[];
|
|
146
156
|
} catch {
|
|
147
157
|
txbxBlocks = undefined;
|
|
148
158
|
}
|
|
@@ -157,6 +167,11 @@ export function parseShapeXml(
|
|
|
157
167
|
...(txbxBlocks && txbxBlocks.length > 0 ? { txbxBlocks } : {}),
|
|
158
168
|
...(prst ? { geometry: prst } : {}),
|
|
159
169
|
rawXml: drawingXml,
|
|
170
|
+
preserveOnlyObject: createDrawingPreserveOnlyObject(
|
|
171
|
+
drawingXml,
|
|
172
|
+
"shape",
|
|
173
|
+
collectRelationshipIds(root),
|
|
174
|
+
),
|
|
160
175
|
};
|
|
161
176
|
}
|
|
162
177
|
|
|
@@ -199,6 +214,11 @@ export function parseVmlXml(pictXml: string): ParsedVmlShape | null {
|
|
|
199
214
|
...(shapeType ? { shapeType } : {}),
|
|
200
215
|
...(text ? { text } : {}),
|
|
201
216
|
rawXml: pictXml,
|
|
217
|
+
preserveOnlyObject: createVmlPreserveOnlyObject(
|
|
218
|
+
pictXml,
|
|
219
|
+
vmlNode,
|
|
220
|
+
collectRelationshipIds(root),
|
|
221
|
+
),
|
|
202
222
|
};
|
|
203
223
|
}
|
|
204
224
|
|
|
@@ -271,7 +291,7 @@ export function parseShapeContent(
|
|
|
271
291
|
const txbxContentXml = txbxContent ? extractRawXml(txbxContent) : undefined;
|
|
272
292
|
const text = txbxContent ? extractAllText(txbxContent).trim() || undefined : undefined;
|
|
273
293
|
|
|
274
|
-
let txbxBlocks:
|
|
294
|
+
let txbxBlocks: BlockNode[] | undefined;
|
|
275
295
|
if (txbxContentXml && blockParser) {
|
|
276
296
|
try {
|
|
277
297
|
// See `TxbxBlockParser` doc above: runtime output is canonical
|
|
@@ -279,7 +299,7 @@ export function parseShapeContent(
|
|
|
279
299
|
// footer fixture 2026-04-24). Cast at the assembly seam so
|
|
280
300
|
// downstream consumers (L03, L11, validator) get canonical types
|
|
281
301
|
// without local `as unknown` ceremony.
|
|
282
|
-
txbxBlocks = blockParser(txbxContentXml) as unknown as
|
|
302
|
+
txbxBlocks = blockParser(txbxContentXml) as unknown as BlockNode[];
|
|
283
303
|
} catch {
|
|
284
304
|
// Preserve-only fallback: keep txbxContentXml for serialization; leave
|
|
285
305
|
// txbxBlocks undefined so consumers know recursion did not succeed.
|
|
@@ -294,15 +314,15 @@ export function parseShapeContent(
|
|
|
294
314
|
// roundRect text boxes + callout/ellipse shapes as non-text.
|
|
295
315
|
const isTextBox = Boolean(txbxContent);
|
|
296
316
|
|
|
297
|
-
const result: ShapeContent = { type: "shape", rawXml: drawingRawXml };
|
|
298
|
-
if (geometry) result.geometry = geometry;
|
|
317
|
+
const result: Mutable<ShapeContent> = { type: "shape", rawXml: drawingRawXml };
|
|
318
|
+
if (geometry) (result as Mutable<typeof result>).geometry = geometry;
|
|
299
319
|
if (text) result.text = text;
|
|
300
|
-
if (fill) result.fill = fill;
|
|
301
|
-
if (line) result.line = line;
|
|
302
|
-
if (isTextBox) result.isTextBox = true;
|
|
303
|
-
if (textBoxBody) result.textBoxBody = textBoxBody;
|
|
304
|
-
if (txbxContentXml) result.txbxContentXml = txbxContentXml;
|
|
305
|
-
if (txbxBlocks && txbxBlocks.length > 0) result.txbxBlocks = txbxBlocks;
|
|
320
|
+
if (fill) (result as Mutable<typeof result>).fill = fill;
|
|
321
|
+
if (line) (result as Mutable<typeof result>).line = line;
|
|
322
|
+
if (isTextBox) (result as Mutable<typeof result>).isTextBox = true;
|
|
323
|
+
if (textBoxBody) (result as Mutable<typeof result>).textBoxBody = textBoxBody;
|
|
324
|
+
if (txbxContentXml) (result as Mutable<typeof result>).txbxContentXml = txbxContentXml;
|
|
325
|
+
if (txbxBlocks && txbxBlocks.length > 0) (result as Mutable<typeof result>).txbxBlocks = txbxBlocks;
|
|
306
326
|
return result;
|
|
307
327
|
}
|
|
308
328
|
|
|
@@ -310,7 +330,7 @@ function readTextBoxBody(wsp: XmlElementNode): TextBoxBodyProperties | undefined
|
|
|
310
330
|
const bodyPr = findFirstChild(wsp, "bodyPr");
|
|
311
331
|
if (!bodyPr) return undefined;
|
|
312
332
|
|
|
313
|
-
const result: TextBoxBodyProperties = {};
|
|
333
|
+
const result: Mutable<TextBoxBodyProperties> = {};
|
|
314
334
|
const anchor = bodyPr.attributes.anchor;
|
|
315
335
|
if (anchor === "t" || anchor === "ctr" || anchor === "b") {
|
|
316
336
|
result.anchor = anchor;
|
|
@@ -319,9 +339,9 @@ function readTextBoxBody(wsp: XmlElementNode): TextBoxBodyProperties | undefined
|
|
|
319
339
|
const top = readIntAttr(bodyPr, "tIns");
|
|
320
340
|
const right = readIntAttr(bodyPr, "rIns");
|
|
321
341
|
const bottom = readIntAttr(bodyPr, "bIns");
|
|
322
|
-
if (left !== undefined) result.insetLeftEmu = left;
|
|
323
|
-
if (top !== undefined) result.insetTopEmu = top;
|
|
324
|
-
if (right !== undefined) result.insetRightEmu = right;
|
|
342
|
+
if (left !== undefined) (result as Mutable<typeof result>).insetLeftEmu = left;
|
|
343
|
+
if (top !== undefined) (result as Mutable<typeof result>).insetTopEmu = top;
|
|
344
|
+
if (right !== undefined) (result as Mutable<typeof result>).insetRightEmu = right;
|
|
325
345
|
if (bottom !== undefined) result.insetBottomEmu = bottom;
|
|
326
346
|
|
|
327
347
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
@@ -360,3 +380,114 @@ function readIntAttr(node: XmlElementNode, attr: string): number | undefined {
|
|
|
360
380
|
const parsed = parseInt(raw, 10);
|
|
361
381
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
362
382
|
}
|
|
383
|
+
|
|
384
|
+
function createDrawingPreserveOnlyObject(
|
|
385
|
+
rawXml: string,
|
|
386
|
+
fallbackHint: PreserveOnlyObjectSizing["fallbackHint"],
|
|
387
|
+
relationshipIds: string[],
|
|
388
|
+
): PreserveOnlyObjectSizing {
|
|
389
|
+
const extentEmu = readDrawingExtentEmu(rawXml);
|
|
390
|
+
return {
|
|
391
|
+
sourceId: `drawing:${hashString(rawXml)}`,
|
|
392
|
+
display: rawXml.includes("<wp:anchor") ? "floating" : "inline",
|
|
393
|
+
...(extentEmu ? { extentEmu } : {}),
|
|
394
|
+
fallbackHint,
|
|
395
|
+
...(relationshipIds.length > 0 ? { relationshipIds } : {}),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function createVmlPreserveOnlyObject(
|
|
400
|
+
rawXml: string,
|
|
401
|
+
node: XmlElementNode,
|
|
402
|
+
relationshipIds: string[],
|
|
403
|
+
): PreserveOnlyObjectSizing {
|
|
404
|
+
const extentEmu = readVmlExtentEmu(node.attributes.style);
|
|
405
|
+
return {
|
|
406
|
+
sourceId: `vml:${node.attributes.id ?? hashString(rawXml)}`,
|
|
407
|
+
display: "unknown",
|
|
408
|
+
...(extentEmu ? { extentEmu } : {}),
|
|
409
|
+
fallbackHint: "vml-shape",
|
|
410
|
+
...(relationshipIds.length > 0 ? { relationshipIds } : {}),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function readDrawingExtentEmu(
|
|
415
|
+
rawXml: string,
|
|
416
|
+
): { widthEmu: number; heightEmu: number } | undefined {
|
|
417
|
+
const extentMatch = /<wp:extent\b[^>]*\bcx=["']([0-9]+)["'][^>]*\bcy=["']([0-9]+)["'][^>]*\/?>/u.exec(rawXml);
|
|
418
|
+
if (extentMatch) {
|
|
419
|
+
return {
|
|
420
|
+
widthEmu: Number.parseInt(extentMatch[1] ?? "0", 10),
|
|
421
|
+
heightEmu: Number.parseInt(extentMatch[2] ?? "0", 10),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const xfrmMatch = /<a:ext\b[^>]*\bcx=["']([0-9]+)["'][^>]*\bcy=["']([0-9]+)["'][^>]*\/?>/u.exec(rawXml);
|
|
425
|
+
if (!xfrmMatch) return undefined;
|
|
426
|
+
return {
|
|
427
|
+
widthEmu: Number.parseInt(xfrmMatch[1] ?? "0", 10),
|
|
428
|
+
heightEmu: Number.parseInt(xfrmMatch[2] ?? "0", 10),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function readVmlExtentEmu(
|
|
433
|
+
style: string | undefined,
|
|
434
|
+
): { widthEmu: number; heightEmu: number } | undefined {
|
|
435
|
+
if (!style) return undefined;
|
|
436
|
+
const widthTwips = readCssLengthTwips(style, "width");
|
|
437
|
+
const heightTwips = readCssLengthTwips(style, "height");
|
|
438
|
+
if (widthTwips === undefined || heightTwips === undefined) return undefined;
|
|
439
|
+
return {
|
|
440
|
+
widthEmu: Math.round(widthTwips * 635),
|
|
441
|
+
heightEmu: Math.round(heightTwips * 635),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function readCssLengthTwips(style: string, property: "width" | "height"): number | undefined {
|
|
446
|
+
const match = new RegExp(`(?:^|;)\\s*${property}\\s*:\\s*([0-9.]+)\\s*(pt|in|cm|mm|px)?`, "iu")
|
|
447
|
+
.exec(style);
|
|
448
|
+
if (!match) return undefined;
|
|
449
|
+
const value = Number.parseFloat(match[1] ?? "");
|
|
450
|
+
if (!Number.isFinite(value)) return undefined;
|
|
451
|
+
const unit = (match[2] ?? "pt").toLowerCase();
|
|
452
|
+
switch (unit) {
|
|
453
|
+
case "pt":
|
|
454
|
+
return Math.round(value * 20);
|
|
455
|
+
case "in":
|
|
456
|
+
return Math.round(value * 1440);
|
|
457
|
+
case "cm":
|
|
458
|
+
return Math.round(value * 567);
|
|
459
|
+
case "mm":
|
|
460
|
+
return Math.round(value * 56.7);
|
|
461
|
+
case "px":
|
|
462
|
+
return Math.round(value * 15);
|
|
463
|
+
default:
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function collectRelationshipIds(node: XmlElementNode): string[] {
|
|
469
|
+
const ids = new Set<string>();
|
|
470
|
+
const visit = (current: XmlElementNode): void => {
|
|
471
|
+
const id =
|
|
472
|
+
current.attributes["r:id"] ??
|
|
473
|
+
current.attributes["r:embed"] ??
|
|
474
|
+
current.attributes.embed ??
|
|
475
|
+
current.attributes["r:link"] ??
|
|
476
|
+
current.attributes.link;
|
|
477
|
+
if (id) ids.add(id);
|
|
478
|
+
for (const child of current.children) {
|
|
479
|
+
if (child.type === "element") visit(child);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
visit(node);
|
|
483
|
+
return [...ids].sort();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function hashString(value: string): string {
|
|
487
|
+
let hash = 2166136261;
|
|
488
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
489
|
+
hash ^= value.charCodeAt(index);
|
|
490
|
+
hash = Math.imul(hash, 16777619);
|
|
491
|
+
}
|
|
492
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
493
|
+
}
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
TableStyleDefinition,
|
|
24
24
|
TableStyleFormatting,
|
|
25
25
|
TableWidth,
|
|
26
|
+
Mutable,
|
|
26
27
|
} from "../../model/canonical-document.ts";
|
|
27
28
|
import {
|
|
28
29
|
readCellBorders,
|
|
@@ -372,8 +373,8 @@ function resolveStyleLinkReciprocals(
|
|
|
372
373
|
);
|
|
373
374
|
continue;
|
|
374
375
|
}
|
|
375
|
-
if (partner.linkedStyleId === undefined) {
|
|
376
|
-
partner.linkedStyleId = style.styleId;
|
|
376
|
+
if ((partner as Mutable<typeof partner>).linkedStyleId === undefined) {
|
|
377
|
+
(partner as Mutable<typeof partner>).linkedStyleId = style.styleId;
|
|
377
378
|
} else if (partner.linkedStyleId !== style.styleId) {
|
|
378
379
|
diagnostics.push(
|
|
379
380
|
`style ${label} "${style.styleId}" links to "${target}" but partner already links to "${partner.linkedStyleId}"; partner link retained`,
|
|
@@ -439,7 +440,7 @@ function readTableStyleFormatting(styleNode: XmlElementNode): TableStyleFormatti
|
|
|
439
440
|
const cellProperties = findChildElementOptional(styleNode, "tcPr");
|
|
440
441
|
const pPrNode = findChildElementOptional(styleNode, "pPr");
|
|
441
442
|
const rPrNode = findChildElementOptional(styleNode, "rPr");
|
|
442
|
-
const formatting: TableStyleFormatting = {};
|
|
443
|
+
const formatting: Mutable<TableStyleFormatting> = {};
|
|
443
444
|
|
|
444
445
|
const paragraphProperties = readParagraphProperties(pPrNode);
|
|
445
446
|
if (paragraphProperties) formatting.paragraphProperties = paragraphProperties;
|
|
@@ -454,11 +455,11 @@ function readTableStyleFormatting(styleNode: XmlElementNode): TableStyleFormatti
|
|
|
454
455
|
const cellMargins = readTableCellMargins(tableProperties);
|
|
455
456
|
const tblLook = readTableLook(tableProperties);
|
|
456
457
|
|
|
457
|
-
if (width) table.width = width as TableWidth;
|
|
458
|
-
if (alignment) table.alignment = alignment;
|
|
459
|
-
if (borders) table.borders = borders as TableBorders;
|
|
460
|
-
if (cellMargins) table.cellMargins = cellMargins as TableCellMargins;
|
|
461
|
-
if (tblLook) table.tblLook = tblLook as TableLook;
|
|
458
|
+
if (width) (table as Mutable<typeof table>).width = width as TableWidth;
|
|
459
|
+
if (alignment) (table as Mutable<typeof table>).alignment = alignment;
|
|
460
|
+
if (borders) (table as Mutable<typeof table>).borders = borders as TableBorders;
|
|
461
|
+
if (cellMargins) (table as Mutable<typeof table>).cellMargins = cellMargins as TableCellMargins;
|
|
462
|
+
if (tblLook) (table as Mutable<typeof table>).tblLook = tblLook as TableLook;
|
|
462
463
|
|
|
463
464
|
if (Object.keys(table).length > 0) {
|
|
464
465
|
formatting.table = table;
|
|
@@ -471,9 +472,9 @@ function readTableStyleFormatting(styleNode: XmlElementNode): TableStyleFormatti
|
|
|
471
472
|
const heightRule = readRowHeightRule(rowProperties);
|
|
472
473
|
const isHeader = readRowIsHeader(rowProperties);
|
|
473
474
|
|
|
474
|
-
if (height !== undefined) row.height = height;
|
|
475
|
-
if (heightRule) row.heightRule = heightRule;
|
|
476
|
-
if (isHeader !== undefined) row.isHeader = isHeader;
|
|
475
|
+
if (height !== undefined) (row as Mutable<typeof row>).height = height;
|
|
476
|
+
if (heightRule) (row as Mutable<typeof row>).heightRule = heightRule;
|
|
477
|
+
if (isHeader !== undefined) (row as Mutable<typeof row>).isHeader = isHeader;
|
|
477
478
|
|
|
478
479
|
if (Object.keys(row).length > 0) {
|
|
479
480
|
formatting.row = row;
|
|
@@ -487,10 +488,10 @@ function readTableStyleFormatting(styleNode: XmlElementNode): TableStyleFormatti
|
|
|
487
488
|
const shading = readCellShading(cellProperties);
|
|
488
489
|
const verticalAlign = readCellVerticalAlign(cellProperties);
|
|
489
490
|
|
|
490
|
-
if (width) cell.width = width as TableWidth;
|
|
491
|
-
if (borders) cell.borders = borders as TableCellBorders;
|
|
492
|
-
if (shading) cell.shading = shading as CellShading;
|
|
493
|
-
if (verticalAlign) cell.verticalAlign = verticalAlign;
|
|
491
|
+
if (width) (cell as Mutable<typeof cell>).width = width as TableWidth;
|
|
492
|
+
if (borders) (cell as Mutable<typeof cell>).borders = borders as TableCellBorders;
|
|
493
|
+
if (shading) (cell as Mutable<typeof cell>).shading = shading as CellShading;
|
|
494
|
+
if (verticalAlign) (cell as Mutable<typeof cell>).verticalAlign = verticalAlign;
|
|
494
495
|
|
|
495
496
|
if (Object.keys(cell).length > 0) {
|
|
496
497
|
formatting.cell = cell;
|
|
@@ -590,4 +591,3 @@ function findChildElementOptional(
|
|
|
590
591
|
);
|
|
591
592
|
}
|
|
592
593
|
|
|
593
|
-
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ResolvedTheme,
|
|
6
6
|
CanonicalTheme,
|
|
7
7
|
ClrSchemeMapping,
|
|
8
|
+
Mutable,
|
|
8
9
|
} from "../../model/canonical-document.ts";
|
|
9
10
|
import type { XmlElementNode } from "./xml-element.ts";
|
|
10
11
|
import { parseXml } from "./xml-parser.ts";
|
|
@@ -61,7 +62,7 @@ export function parseThemeXml(xml: string): ThemeDefinition {
|
|
|
61
62
|
findChildElementOptional(themeElements, "fontScheme"),
|
|
62
63
|
);
|
|
63
64
|
|
|
64
|
-
const result: ThemeDefinition = {};
|
|
65
|
+
const result: Mutable<ThemeDefinition> = {};
|
|
65
66
|
if (themeName) {
|
|
66
67
|
result.name = themeName;
|
|
67
68
|
}
|
|
@@ -79,7 +80,7 @@ export function parseThemeXml(xml: string): ThemeDefinition {
|
|
|
79
80
|
* Resolve a ThemeDefinition into flattened runtime theme inputs.
|
|
80
81
|
* Maps OOXML theme slot names to CSS-usable color values and font families.
|
|
81
82
|
*/
|
|
82
|
-
export function resolveTheme(theme: ThemeDefinition): ResolvedTheme {
|
|
83
|
+
export function resolveTheme(theme: Mutable<ThemeDefinition>): ResolvedTheme {
|
|
83
84
|
const colors: Record<string, string> = {};
|
|
84
85
|
|
|
85
86
|
if (theme.colorScheme?.colors) {
|
|
@@ -142,7 +143,7 @@ export const DEFAULT_CLR_SCHEME_MAPPING: ClrSchemeMapping = Object.freeze({
|
|
|
142
143
|
* CLAUDE.md §3.
|
|
143
144
|
*/
|
|
144
145
|
export function materializeCanonicalTheme(
|
|
145
|
-
theme: ThemeDefinition
|
|
146
|
+
theme: Mutable<ThemeDefinition>,
|
|
146
147
|
clrMap: ClrSchemeMapping,
|
|
147
148
|
): CanonicalTheme {
|
|
148
149
|
const clrScheme: ThemeColorScheme = theme.colorScheme ?? { name: "", colors: {} };
|
|
@@ -240,7 +241,7 @@ function parseFontScheme(
|
|
|
240
241
|
? extractFontTypeface(minorFontElement)
|
|
241
242
|
: undefined;
|
|
242
243
|
|
|
243
|
-
const result: ThemeFontScheme = { name: schemeName };
|
|
244
|
+
const result: Mutable<ThemeFontScheme> = { name: schemeName };
|
|
244
245
|
if (majorFont) {
|
|
245
246
|
result.majorFont = majorFont;
|
|
246
247
|
}
|