@beyondwork/docx-react-component 1.0.56 → 1.0.58
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/README.md +1 -1
- package/package.json +1 -1
- package/src/api/public-types.ts +330 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +17 -11
- package/src/core/selection/mapping.ts +18 -1
- package/src/core/selection/review-anchors.ts +29 -18
- package/src/io/chart-preview-resolver.ts +175 -41
- package/src/io/docx-session.ts +57 -2
- package/src/io/export/serialize-main-document.ts +82 -0
- package/src/io/export/serialize-styles.ts +61 -3
- package/src/io/export/table-properties-xml.ts +19 -4
- package/src/io/normalize/normalize-text.ts +33 -0
- package/src/io/ooxml/parse-anchor.ts +182 -0
- package/src/io/ooxml/parse-drawing.ts +319 -0
- package/src/io/ooxml/parse-fields.ts +115 -2
- package/src/io/ooxml/parse-fill.ts +215 -0
- package/src/io/ooxml/parse-font-table.ts +190 -0
- package/src/io/ooxml/parse-footnotes.ts +52 -1
- package/src/io/ooxml/parse-main-document.ts +241 -1
- package/src/io/ooxml/parse-numbering.ts +96 -0
- package/src/io/ooxml/parse-picture.ts +158 -0
- package/src/io/ooxml/parse-settings.ts +34 -0
- package/src/io/ooxml/parse-shapes.ts +87 -0
- package/src/io/ooxml/parse-solid-fill.ts +11 -0
- package/src/io/ooxml/parse-styles.ts +74 -1
- package/src/io/ooxml/parse-theme.ts +60 -0
- package/src/io/paste/html-clipboard.ts +449 -0
- package/src/io/paste/word-clipboard.ts +5 -1
- package/src/legal/_document-root.ts +26 -0
- package/src/legal/bookmarks.ts +4 -3
- package/src/legal/cross-references.ts +3 -2
- package/src/legal/defined-terms.ts +2 -1
- package/src/legal/signature-blocks.ts +2 -1
- package/src/model/canonical-document.ts +421 -3
- package/src/runtime/chart/chart-model-store.ts +73 -10
- package/src/runtime/document-runtime.ts +760 -41
- package/src/runtime/document-search.ts +61 -0
- package/src/runtime/edit-ops/index.ts +129 -0
- package/src/runtime/event-refresh-hints.ts +7 -0
- package/src/runtime/field-resolver.ts +341 -0
- package/src/runtime/footnote-resolver.ts +55 -0
- package/src/runtime/hyperlink-color-resolver.ts +13 -10
- package/src/runtime/object-grab/index.ts +51 -0
- package/src/runtime/paragraph-style-resolver.ts +105 -0
- package/src/runtime/query-scopes.ts +186 -0
- package/src/runtime/resolved-numbering-geometry.ts +12 -0
- package/src/runtime/scope-resolver.ts +60 -0
- package/src/runtime/selection/cursor-ops.ts +186 -15
- package/src/runtime/selection/index.ts +17 -1
- package/src/runtime/structure-ops/index.ts +77 -0
- package/src/runtime/styles-cascade.ts +33 -0
- package/src/runtime/surface-projection.ts +192 -12
- package/src/runtime/theme-color-resolver.ts +189 -44
- package/src/runtime/units.ts +46 -0
- package/src/runtime/view-state.ts +13 -2
- package/src/ui/WordReviewEditor.tsx +239 -11
- package/src/ui/editor-runtime-boundary.ts +97 -1
- package/src/ui/editor-shell-view.tsx +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +17 -3
- package/src/ui-tailwind/chart/ChartSurface.tsx +36 -10
- package/src/ui-tailwind/chart/layout/plot-area.ts +120 -45
- package/src/ui-tailwind/chart/render/area.tsx +22 -4
- package/src/ui-tailwind/chart/render/bar-column.tsx +37 -11
- package/src/ui-tailwind/chart/render/bubble.tsx +6 -2
- package/src/ui-tailwind/chart/render/combo.tsx +37 -4
- package/src/ui-tailwind/chart/render/line.tsx +28 -5
- package/src/ui-tailwind/chart/render/pie.tsx +36 -16
- package/src/ui-tailwind/chart/render/progressive-render.ts +8 -1
- package/src/ui-tailwind/chart/render/scatter.tsx +9 -4
- package/src/ui-tailwind/chrome/avatar-initials.ts +15 -0
- package/src/ui-tailwind/chrome/tw-comment-preview.tsx +3 -1
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +14 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +3 -2
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +30 -11
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +15 -2
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +1 -1
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +24 -7
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +31 -12
- package/src/ui-tailwind/chrome-overlay/page-border-resolver.ts +211 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +24 -0
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +74 -0
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +65 -0
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-border-overlay.tsx +233 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +135 -13
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +51 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +12 -4
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +32 -12
- package/src/ui-tailwind/chrome-overlay/tw-toc-outline-sidebar.tsx +133 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +49 -10
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +119 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +236 -9
- package/src/ui-tailwind/editor-surface/pm-schema.ts +214 -11
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +32 -2
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +206 -0
- package/src/ui-tailwind/editor-surface/surface-layer.ts +66 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +29 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +7 -1
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +22 -6
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +10 -16
- package/src/ui-tailwind/review/tw-health-panel.tsx +0 -25
- package/src/ui-tailwind/review/tw-rail-card.tsx +38 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +2 -2
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +5 -12
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +2 -2
- package/src/ui-tailwind/theme/editor-theme.css +1 -0
- package/src/ui-tailwind/theme/tokens.css +6 -0
- package/src/ui-tailwind/theme/tokens.ts +10 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +23 -0
- package/src/validation/compatibility-engine.ts +2 -0
- package/src/validation/docx-comment-proof.ts +12 -3
|
@@ -1,44 +1,112 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* string, applying the `w:themeTint` / `w:themeShade` HSL-luminance
|
|
4
|
-
* modulation per ECMA-376 §17.18.85 / §17.18.83.
|
|
2
|
+
* Unified runtime color resolver for every OOXML theme reference.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* round-trips the theme reference (not the computed hex).
|
|
4
|
+
* Covers:
|
|
5
|
+
* - `<w:color>` §17.18.85/§17.18.83 byte-form `w:themeTint`/`w:themeShade`
|
|
6
|
+
* (HSL luminance modulation).
|
|
7
|
+
* - `<a:schemeClr>` §20.1.2.3.x DrawingML modifier chain
|
|
8
|
+
* `lumMod → lumOff → satMod → hueMod → tint → shade` (per-100,000 units).
|
|
9
|
+
* - `<w:clrSchemeMapping>` §17.15.1.17 style-slot → scheme-slot remap
|
|
10
|
+
* (including the implicit identity default when settings.xml omits it).
|
|
11
|
+
* - `<w:color w:val="auto"/>` → the `t1` style slot via the clrMap.
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* Cascade layer entry points:
|
|
14
|
+
* - `ThemeColorResolver` class — build once per `CanonicalTheme` snapshot,
|
|
15
|
+
* key the cache on `(themeHash, clrMapHash)` per CLAUDE.md §3.
|
|
16
|
+
* - `concretizeThemeColors(cascade, resolver)` — run-formatting post-pass.
|
|
20
17
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
18
|
+
* LibreOffice parity: port of `vendor/libreoffice/oox/source/drawingml/color.cxx`
|
|
19
|
+
* (shape only; no code copied). Modifier order + HSL/sRGB color-space split
|
|
20
|
+
* match LO; modifiers are applied in declaration order — do NOT sort.
|
|
21
|
+
*
|
|
22
|
+
* The original `colorThemeSlot` / `Tint` / `Shade` fields are preserved on
|
|
23
|
+
* returned cascades so the export path can re-emit the original theme
|
|
24
|
+
* reference byte-for-byte.
|
|
27
25
|
*/
|
|
28
26
|
|
|
29
|
-
import type { CanonicalRunFormatting, ResolvedTheme } from "../model/canonical-document.ts";
|
|
27
|
+
import type { CanonicalRunFormatting, CanonicalTheme, ClrSchemeMappingSlot, ResolvedTheme } from "../model/canonical-document.ts";
|
|
30
28
|
import { resolveThemeColor } from "../io/ooxml/parse-theme.ts";
|
|
31
29
|
|
|
30
|
+
/**
|
|
31
|
+
* DrawingML color modifier (ECMA-376 §20.1.2.3.x).
|
|
32
|
+
* Values are in per-100,000 units (e.g. 65_000 = 65%).
|
|
33
|
+
* Apply in declaration order — do NOT sort or reorder.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from w:themeTint / w:themeShade (ECMA-376 §17) which use
|
|
36
|
+
* hex-byte 0x00–0xFF encoding.
|
|
37
|
+
*/
|
|
38
|
+
export interface DrawingMlColorMod {
|
|
39
|
+
kind: "lumMod" | "lumOff" | "satMod" | "hueMod" | "shade" | "tint" | "alpha";
|
|
40
|
+
value: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DML_UNIT = 100_000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Unified runtime theme color resolver.
|
|
47
|
+
* Construct once per CanonicalTheme (keyed on themeHash + clrMapHash).
|
|
48
|
+
* All resolution methods apply clrMap remapping before any slot lookup.
|
|
49
|
+
*/
|
|
50
|
+
export class ThemeColorResolver {
|
|
51
|
+
private readonly theme: CanonicalTheme;
|
|
52
|
+
|
|
53
|
+
constructor(theme: CanonicalTheme) {
|
|
54
|
+
this.theme = theme;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Look up a scheme slot with clrMap remapping applied. Returns #RRGGBB or undefined. */
|
|
58
|
+
resolveSchemeSlot(slot: string): string | undefined {
|
|
59
|
+
const mapped = this.theme.clrMap[slot as ClrSchemeMappingSlot] ?? slot;
|
|
60
|
+
const hex = this.theme.clrScheme.colors[mapped];
|
|
61
|
+
return hex ?? undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Resolve w:color val="auto" → "text 1" style slot (t1) via clrMap. */
|
|
65
|
+
resolveAuto(): string | undefined {
|
|
66
|
+
// "auto" = windowText = "text 1" style slot (t1).
|
|
67
|
+
// Pass "t1" so clrMap remapping is honoured (e.g. <w:clrSchemeMapping w:t1="dk2"/>).
|
|
68
|
+
return this.resolveSchemeSlot("t1");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a w:themeColor + w:themeTint/w:themeShade (§17 byte form, 0x00–0xFF).
|
|
73
|
+
* Delegates to the existing applyThemeTintShade for the HSL transform.
|
|
74
|
+
*/
|
|
75
|
+
resolveWordThemeColor(
|
|
76
|
+
colorThemeSlot: string,
|
|
77
|
+
colorThemeTint: string | undefined,
|
|
78
|
+
colorThemeShade: string | undefined,
|
|
79
|
+
): string | undefined {
|
|
80
|
+
const baseHex = this.resolveSchemeSlot(colorThemeSlot);
|
|
81
|
+
if (!baseHex) return undefined;
|
|
82
|
+
return applyThemeTintShade(baseHex, colorThemeTint, colorThemeShade);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve an a:schemeClr reference with DrawingML modifiers (§20).
|
|
87
|
+
* Modifiers are applied in declaration order. Alpha mods are ignored.
|
|
88
|
+
* Returns undefined when the slot is absent.
|
|
89
|
+
*/
|
|
90
|
+
resolveWithModifiers(
|
|
91
|
+
slot: string,
|
|
92
|
+
mods: readonly DrawingMlColorMod[],
|
|
93
|
+
): string | undefined {
|
|
94
|
+
const baseHex = this.resolveSchemeSlot(slot);
|
|
95
|
+
if (!baseHex) return undefined;
|
|
96
|
+
if (mods.length === 0) return baseHex;
|
|
97
|
+
return applyDmlMods(baseHex.startsWith("#") ? baseHex : `#${baseHex}`, mods);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
32
101
|
/**
|
|
33
102
|
* Collapse `<w:color>`-style theme-slot + tint + shade references into a
|
|
34
|
-
* single resolved hex colour.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* - `undefined` otherwise (no colour declared, or theme slot absent).
|
|
103
|
+
* single resolved hex colour. §17-only byte-form path; does NOT apply
|
|
104
|
+
* `w:clrSchemeMapping` remap.
|
|
105
|
+
*
|
|
106
|
+
* @deprecated Use `ThemeColorResolver.resolveWordThemeColor` instead —
|
|
107
|
+
* it honours clrSchemeMapping. This free function is retained only for
|
|
108
|
+
* its targeted unit tests (`test/io/theme-color-tint-shade.test.ts`) and
|
|
109
|
+
* will be removed once those migrate to the class.
|
|
42
110
|
*/
|
|
43
111
|
export function resolveThemeColorHex(
|
|
44
112
|
rPr: Pick<
|
|
@@ -81,23 +149,22 @@ export function resolveThemeColorHex(
|
|
|
81
149
|
*/
|
|
82
150
|
export function concretizeThemeColors(
|
|
83
151
|
cascade: CanonicalRunFormatting,
|
|
84
|
-
|
|
152
|
+
resolver: ThemeColorResolver | undefined,
|
|
85
153
|
): CanonicalRunFormatting {
|
|
86
154
|
if (!cascade.colorThemeSlot) return cascade;
|
|
87
155
|
if (cascade.colorHex && cascade.colorHex !== "auto") return cascade;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
// the fallback
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
},
|
|
99
|
-
theme,
|
|
156
|
+
if (!resolver) return cascade;
|
|
157
|
+
|
|
158
|
+
// `colorHex === "auto"` with a themeColor slot means "paint via the theme
|
|
159
|
+
// slot; auto is only the fallback when theme is missing" — so we always
|
|
160
|
+
// resolve via the slot here. clrSchemeMapping remap is applied inside
|
|
161
|
+
// `resolver.resolveWordThemeColor`.
|
|
162
|
+
const resolved = resolver.resolveWordThemeColor(
|
|
163
|
+
cascade.colorThemeSlot,
|
|
164
|
+
cascade.colorThemeTint,
|
|
165
|
+
cascade.colorThemeShade,
|
|
100
166
|
);
|
|
167
|
+
|
|
101
168
|
if (!resolved || resolved === "auto") return cascade;
|
|
102
169
|
if (resolved === cascade.colorHex) return cascade;
|
|
103
170
|
return { ...cascade, colorHex: resolved };
|
|
@@ -174,6 +241,84 @@ function formatHexColor(rgb: { r: number; g: number; b: number }): string {
|
|
|
174
241
|
return `${to2(rgb.r)}${to2(rgb.g)}${to2(rgb.b)}`;
|
|
175
242
|
}
|
|
176
243
|
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// DrawingML modifier pipeline (§20 forms — per-100,000 units)
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
function applyDmlMods(
|
|
249
|
+
hex: string,
|
|
250
|
+
mods: readonly DrawingMlColorMod[],
|
|
251
|
+
): string {
|
|
252
|
+
let rgb = parseHexToRgbDml(hex);
|
|
253
|
+
for (const mod of mods) {
|
|
254
|
+
const frac = mod.value / DML_UNIT;
|
|
255
|
+
switch (mod.kind) {
|
|
256
|
+
case "lumMod": {
|
|
257
|
+
const hsl = rgbToHsl(rgb);
|
|
258
|
+
rgb = hslToRgb({ ...hsl, l: clamp01(hsl.l * frac) });
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case "lumOff": {
|
|
262
|
+
const hsl = rgbToHsl(rgb);
|
|
263
|
+
rgb = hslToRgb({ ...hsl, l: clamp01(hsl.l + frac) });
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "satMod": {
|
|
267
|
+
const hsl = rgbToHsl(rgb);
|
|
268
|
+
rgb = hslToRgb({ ...hsl, s: clamp01(hsl.s * frac) });
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "hueMod": {
|
|
272
|
+
const hsl = rgbToHsl(rgb);
|
|
273
|
+
// Multiplicative scale per LO color.cxx, not additive rotation
|
|
274
|
+
rgb = hslToRgb({ ...hsl, h: ((hsl.h * frac) % 360 + 360) % 360 });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "shade": {
|
|
278
|
+
// §20.1.2.3.31: sRGB space (NOT HSL) — multiply channels toward black
|
|
279
|
+
rgb = {
|
|
280
|
+
r: clamp255(rgb.r * frac),
|
|
281
|
+
g: clamp255(rgb.g * frac),
|
|
282
|
+
b: clamp255(rgb.b * frac),
|
|
283
|
+
};
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case "tint": {
|
|
287
|
+
// §20.1.2.3.34: sRGB space — C' = C * frac + 255 * (1 - frac)
|
|
288
|
+
const inv = 1 - frac;
|
|
289
|
+
rgb = {
|
|
290
|
+
r: clamp255(rgb.r * frac + 255 * inv),
|
|
291
|
+
g: clamp255(rgb.g * frac + 255 * inv),
|
|
292
|
+
b: clamp255(rgb.b * frac + 255 * inv),
|
|
293
|
+
};
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case "alpha":
|
|
297
|
+
break; // opacity not reflected in sRGB output
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return formatDmlHex(rgb);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseHexToRgbDml(hex: string): { r: number; g: number; b: number } {
|
|
304
|
+
const n = Number.parseInt(hex.slice(1), 16);
|
|
305
|
+
return { r: (n >>> 16) & 0xff, g: (n >>> 8) & 0xff, b: n & 0xff };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function formatDmlHex(rgb: { r: number; g: number; b: number }): string {
|
|
309
|
+
const h2 = (n: number): string =>
|
|
310
|
+
Math.round(clamp255(n)).toString(16).padStart(2, "0").toUpperCase();
|
|
311
|
+
return `#${h2(rgb.r)}${h2(rgb.g)}${h2(rgb.b)}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function clamp01(x: number): number {
|
|
315
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function clamp255(x: number): number {
|
|
319
|
+
return x < 0 ? 0 : x > 255 ? 255 : x;
|
|
320
|
+
}
|
|
321
|
+
|
|
177
322
|
function rgbToHsl(rgb: { r: number; g: number; b: number }): {
|
|
178
323
|
h: number;
|
|
179
324
|
s: number;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOXML unit conversions used across the runtime + UI.
|
|
3
|
+
*
|
|
4
|
+
* Hoisted from per-file constants to deduplicate the magic numbers that
|
|
5
|
+
* appear in `pm-schema.ts`, `chart-model-store.ts`, `float-wrap-resolver.ts`,
|
|
6
|
+
* `shape-renderer.ts`, and several other call sites. Renaming an alias
|
|
7
|
+
* here propagates everywhere; renaming a literal would not.
|
|
8
|
+
*
|
|
9
|
+
* Conventions:
|
|
10
|
+
* - All EMU helpers assume 96 dpi (the default OOXML rendering DPI).
|
|
11
|
+
* - Twip helpers assume 1440 twips per inch (OOXML standard).
|
|
12
|
+
* - Rotation values use OOXML's 60 000ths of a degree (`a:xfrm a:rot`).
|
|
13
|
+
* - Picture crop (`a:srcRect`) values use 1/1000 of a percent per edge.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** EMU (English Metric Units) per CSS pixel at 96 dpi. */
|
|
17
|
+
export const EMU_PER_PX = 9525;
|
|
18
|
+
|
|
19
|
+
/** OOXML rotation units per degree (`a:xfrm a:rot` = 60 000ths°). */
|
|
20
|
+
export const ROTATION_UNITS_PER_DEGREE = 60000;
|
|
21
|
+
|
|
22
|
+
/** OOXML picture-crop units per percent (`a:srcRect` uses 1/1000 of a percent). */
|
|
23
|
+
export const SRCRECT_UNITS_PER_PERCENT = 1000;
|
|
24
|
+
|
|
25
|
+
/** Twips per CSS pixel at 96 dpi (1440 twips/inch ÷ 96 px/inch = 15). */
|
|
26
|
+
export const TWIPS_PER_PX = 15;
|
|
27
|
+
|
|
28
|
+
/** Convert EMU → CSS pixels at 96 dpi. */
|
|
29
|
+
export function emuToPx(emu: number): number {
|
|
30
|
+
return emu / EMU_PER_PX;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Convert OOXML rotation units → CSS degrees. */
|
|
34
|
+
export function rotationToDeg(units: number): number {
|
|
35
|
+
return units / ROTATION_UNITS_PER_DEGREE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Convert an `a:srcRect` edge value → CSS percent. */
|
|
39
|
+
export function srcRectToPercent(units: number): number {
|
|
40
|
+
return units / SRCRECT_UNITS_PER_PERCENT;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Convert twips → CSS pixels at 96 dpi. */
|
|
44
|
+
export function twipsToPx(twips: number): number {
|
|
45
|
+
return twips / TWIPS_PER_PX;
|
|
46
|
+
}
|
|
@@ -403,6 +403,14 @@ function deriveActiveObjectFrame(
|
|
|
403
403
|
};
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
if (segment.kind === "shape") {
|
|
407
|
+
return {
|
|
408
|
+
kind: "shape",
|
|
409
|
+
anchorPos: segment.from,
|
|
410
|
+
display: segment.anchor?.display === "floating" ? "floating" : "inline",
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
406
414
|
const objectKind = inferOpaqueObjectKind(segment);
|
|
407
415
|
if (!objectKind) {
|
|
408
416
|
return null;
|
|
@@ -491,7 +499,7 @@ function normalizeZoomLevel(
|
|
|
491
499
|
function findObjectSegmentAtPosition(
|
|
492
500
|
segments: readonly SurfaceInlineSegment[],
|
|
493
501
|
position: number,
|
|
494
|
-
): Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" }> | null {
|
|
502
|
+
): Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" | "shape" }> | null {
|
|
495
503
|
for (const segment of segments) {
|
|
496
504
|
if (!isObjectLikeSegment(segment)) {
|
|
497
505
|
continue;
|
|
@@ -505,10 +513,13 @@ function findObjectSegmentAtPosition(
|
|
|
505
513
|
|
|
506
514
|
function isObjectLikeSegment(
|
|
507
515
|
segment: SurfaceInlineSegment,
|
|
508
|
-
): segment is Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" }> {
|
|
516
|
+
): segment is Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" | "shape" }> {
|
|
509
517
|
if (segment.kind === "image") {
|
|
510
518
|
return true;
|
|
511
519
|
}
|
|
520
|
+
if (segment.kind === "shape") {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
512
523
|
if (segment.kind !== "opaque_inline") {
|
|
513
524
|
return false;
|
|
514
525
|
}
|