@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
|
@@ -15,8 +15,12 @@ import type { CanonicalDocumentEnvelope, SelectionSnapshot } from "../../core/st
|
|
|
15
15
|
import {
|
|
16
16
|
moveCharLeft,
|
|
17
17
|
moveCharRight,
|
|
18
|
+
moveDown,
|
|
19
|
+
moveLineEnd,
|
|
20
|
+
moveLineStart,
|
|
18
21
|
moveParagraphEnd,
|
|
19
22
|
moveParagraphStart,
|
|
23
|
+
moveUp,
|
|
20
24
|
moveWordLeft,
|
|
21
25
|
moveWordRight,
|
|
22
26
|
type CursorMoveOptions,
|
|
@@ -39,7 +43,11 @@ export type CursorMoveOp =
|
|
|
39
43
|
| "word-left"
|
|
40
44
|
| "word-right"
|
|
41
45
|
| "paragraph-start"
|
|
42
|
-
| "paragraph-end"
|
|
46
|
+
| "paragraph-end"
|
|
47
|
+
| "up"
|
|
48
|
+
| "down"
|
|
49
|
+
| "line-start"
|
|
50
|
+
| "line-end";
|
|
43
51
|
|
|
44
52
|
export interface SelectionLayer {
|
|
45
53
|
/**
|
|
@@ -83,6 +91,14 @@ export const selectionLayer: SelectionLayer = {
|
|
|
83
91
|
return moveParagraphStart(doc, selection, options);
|
|
84
92
|
case "paragraph-end":
|
|
85
93
|
return moveParagraphEnd(doc, selection, options);
|
|
94
|
+
case "up":
|
|
95
|
+
return moveUp(doc, selection, options);
|
|
96
|
+
case "down":
|
|
97
|
+
return moveDown(doc, selection, options);
|
|
98
|
+
case "line-start":
|
|
99
|
+
return moveLineStart(doc, selection, options);
|
|
100
|
+
case "line-end":
|
|
101
|
+
return moveLineEnd(doc, selection, options);
|
|
86
102
|
}
|
|
87
103
|
},
|
|
88
104
|
validate(doc, selection, maxOffset) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R.3 StructureLayer — named shell module for structural mutations. See
|
|
3
|
+
* `docs/plans/lane-1-editing-foundation.md` §R.3.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors R.2 EditLayer's pattern: a thin named entry point over existing
|
|
6
|
+
* pure functions. The layer's purpose is to be the single site where callers
|
|
7
|
+
* reach for table / list / section / fragment-splice concerns, so R.5.b
|
|
8
|
+
* (post-edit validation hook) and R.5.a (action bracketing) can attach to a
|
|
9
|
+
* single seam instead of a fanout of helper call-sites.
|
|
10
|
+
*
|
|
11
|
+
* Phase 6/Item-2 scope: expose `applyFragmentInsert` (shipped in I2 Tier B
|
|
12
|
+
* Slice 1). Adding list/table/section-op wrappers is deferred because those
|
|
13
|
+
* existing command helpers already have rich type surfaces that don't need a
|
|
14
|
+
* named re-export to be callable — reach for them directly via
|
|
15
|
+
* `src/core/commands/{list,table,section}-commands.ts` until a specific case
|
|
16
|
+
* surfaces requiring the seam. Keeping this interface minimal avoids the
|
|
17
|
+
* "enumeration trap" that larger shell refactors fall into.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { CanonicalDocumentFragment } from "../../api/public-types.ts";
|
|
21
|
+
import type {
|
|
22
|
+
CanonicalDocumentEnvelope,
|
|
23
|
+
SelectionSnapshot,
|
|
24
|
+
} from "../../core/state/editor-state.ts";
|
|
25
|
+
import type { StructuralMutationResult } from "../../core/commands/structural-helpers.ts";
|
|
26
|
+
import type { TextCommandContext } from "../../core/commands/text-commands.ts";
|
|
27
|
+
import { countLogicalPositions, parseTextStory } from "../../core/schema/text-schema.ts";
|
|
28
|
+
import { validateSelectionAgainstDocument } from "../selection/post-edit-validator.ts";
|
|
29
|
+
import { applyFragmentInsert } from "./fragment-insert.ts";
|
|
30
|
+
|
|
31
|
+
export interface StructureLayer {
|
|
32
|
+
/**
|
|
33
|
+
* Splice a `CanonicalDocumentFragment` at the current selection. Baseline
|
|
34
|
+
* semantic: the caret paragraph is split; fragment blocks are inserted
|
|
35
|
+
* between the two halves; range selections are deleted first. Empty
|
|
36
|
+
* fragments are no-ops.
|
|
37
|
+
*/
|
|
38
|
+
applyFragmentInsert(
|
|
39
|
+
doc: CanonicalDocumentEnvelope,
|
|
40
|
+
selection: SelectionSnapshot,
|
|
41
|
+
fragment: CanonicalDocumentFragment,
|
|
42
|
+
context: TextCommandContext,
|
|
43
|
+
): StructuralMutationResult;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* R.5.b — every StructureLayer return passes through the post-edit selection
|
|
48
|
+
* validator before leaving the layer. Structural mutations can legitimately
|
|
49
|
+
* shrink the document (fragment with empty trailing paragraphs, etc.), so
|
|
50
|
+
* clamping here is a defense-in-depth hook matching the EditLayer's.
|
|
51
|
+
*
|
|
52
|
+
* `StructuralMutationResult` doesn't carry a pre-computed `storyText`, so
|
|
53
|
+
* we compute the post-mutation story size via `countLogicalPositions` over
|
|
54
|
+
* the parsed story. That's one parse per structural op — acceptable; these
|
|
55
|
+
* ops are off the typing hot path.
|
|
56
|
+
*/
|
|
57
|
+
function validateResult(result: StructuralMutationResult): StructuralMutationResult {
|
|
58
|
+
if (!result.changed) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
const story = parseTextStory(result.document.content);
|
|
62
|
+
const maxOffset = countLogicalPositions(story.units);
|
|
63
|
+
const validated = validateSelectionAgainstDocument(result.document, result.selection, maxOffset);
|
|
64
|
+
if (validated === result.selection) {
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
return { ...result, selection: validated };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Default stateless StructureLayer instance. Safe to share across runtimes.
|
|
72
|
+
*/
|
|
73
|
+
export const structureLayer: StructureLayer = {
|
|
74
|
+
applyFragmentInsert(doc, selection, fragment, context) {
|
|
75
|
+
return validateResult(applyFragmentInsert(doc, selection, fragment, context));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central entry point for OOXML style cascade resolution.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the paragraph, character, run, numbering-marker, and table style
|
|
5
|
+
* resolvers from their individual modules so downstream consumers (Lane 3a
|
|
6
|
+
* measurement, Lane 1 style picker, agent tooling) can import from a single,
|
|
7
|
+
* stable location.
|
|
8
|
+
*
|
|
9
|
+
* Adding a new resolver? Add it to its feature module (paragraph, table, etc.)
|
|
10
|
+
* and re-export from here. Do not define new cascade logic in this file.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
resolveParagraphStyleChain,
|
|
15
|
+
resolveCharacterStyleChain,
|
|
16
|
+
resolveEffectiveParagraphFormatting,
|
|
17
|
+
resolveEffectiveRunFormatting,
|
|
18
|
+
resolveNumberingMarkerRunFormatting,
|
|
19
|
+
resolveTableCellTextFormatting,
|
|
20
|
+
resolveRunFontFamily,
|
|
21
|
+
getNextStyleId,
|
|
22
|
+
type ParagraphResolveInput,
|
|
23
|
+
type RunResolveInput,
|
|
24
|
+
type MarkerResolveInput,
|
|
25
|
+
} from "./paragraph-style-resolver.ts";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
resolveTableStyleResolution,
|
|
29
|
+
type ResolvedTableCellStyle,
|
|
30
|
+
type ResolvedTableRowStyle,
|
|
31
|
+
type ResolvedTableLevelProperties,
|
|
32
|
+
type ResolvedTableStyleResolution,
|
|
33
|
+
} from "./table-style-resolver.ts";
|
|
@@ -3,7 +3,9 @@ import type {
|
|
|
3
3
|
EditorSurfaceSnapshot,
|
|
4
4
|
SecondaryStorySurface,
|
|
5
5
|
SurfaceBlockSnapshot,
|
|
6
|
+
SurfaceDrawingAnchor,
|
|
6
7
|
SurfaceInlineSegment,
|
|
8
|
+
SurfacePictureEffects,
|
|
7
9
|
SurfaceTableCellSnapshot,
|
|
8
10
|
SurfaceTableRowSnapshot,
|
|
9
11
|
SurfaceTextMark,
|
|
@@ -30,6 +32,9 @@ import type {
|
|
|
30
32
|
TableCellBorders,
|
|
31
33
|
TableNode,
|
|
32
34
|
TextMark,
|
|
35
|
+
DrawingFrameNode,
|
|
36
|
+
PictureContent,
|
|
37
|
+
ShapeContent,
|
|
33
38
|
VmlShapeNode,
|
|
34
39
|
WordArtNode,
|
|
35
40
|
} from "../model/canonical-document.ts";
|
|
@@ -57,7 +62,7 @@ import {
|
|
|
57
62
|
resolveNumberingMarkerRunFormatting,
|
|
58
63
|
} from "./paragraph-style-resolver.ts";
|
|
59
64
|
import { resolveHyperlinkRunFormatting } from "./hyperlink-color-resolver.ts";
|
|
60
|
-
import { concretizeThemeColors } from "./theme-color-resolver.ts";
|
|
65
|
+
import { concretizeThemeColors, ThemeColorResolver } from "./theme-color-resolver.ts";
|
|
61
66
|
import type { CanonicalParagraphFormatting, CanonicalRunFormatting } from "../model/canonical-document.ts";
|
|
62
67
|
|
|
63
68
|
interface ParagraphAccumulator {
|
|
@@ -93,6 +98,13 @@ export function createEditorSurfaceSnapshot(
|
|
|
93
98
|
const blocks: SurfaceBlockSnapshot[] = [];
|
|
94
99
|
const lockedFragmentIds: string[] = [];
|
|
95
100
|
const numberingPrefixResolver = createNumberingPrefixResolver(document.numbering);
|
|
101
|
+
// Open a chartModelStore build pass tagged with the document envelope.
|
|
102
|
+
// Every `chart_preview` node populated during the pass records its id
|
|
103
|
+
// in the pass's seen-set; `endBuildPass()` below evicts store entries
|
|
104
|
+
// from previous documents (different owner) and stale entries from
|
|
105
|
+
// earlier builds (same owner, not seen this pass). Previously the
|
|
106
|
+
// store grew unbounded across document loads — documented v1 gap.
|
|
107
|
+
chartModelStore.beginBuildPass(document);
|
|
96
108
|
let cursor = 0;
|
|
97
109
|
const counters = {
|
|
98
110
|
paragraph: 0,
|
|
@@ -157,6 +169,12 @@ export function createEditorSurfaceSnapshot(
|
|
|
157
169
|
|
|
158
170
|
const secondaryStories = createSecondaryStorySurfaces(document, numberingPrefixResolver);
|
|
159
171
|
|
|
172
|
+
// Close the chartModelStore build pass. Evicts entries from previous
|
|
173
|
+
// documents + stale entries whose ids no longer appear in the current
|
|
174
|
+
// document. Must happen AFTER secondary stories (they may also contain
|
|
175
|
+
// chart_preview nodes).
|
|
176
|
+
chartModelStore.endBuildPass();
|
|
177
|
+
|
|
160
178
|
return {
|
|
161
179
|
storySize: cursor,
|
|
162
180
|
plainText: createPlainText(blocks),
|
|
@@ -712,6 +730,12 @@ function createParagraphBlock(
|
|
|
712
730
|
const lockedFragmentIds: string[] = [];
|
|
713
731
|
let cursor = start;
|
|
714
732
|
const children = Array.isArray(paragraph.children) ? paragraph.children : [];
|
|
733
|
+
// Build once per paragraph block — ThemeColorResolver is a thin wrapper
|
|
734
|
+
// around CanonicalTheme that applies clrMap remapping. Constructed here so
|
|
735
|
+
// it is not recreated on every text segment (inner hot loop).
|
|
736
|
+
const themeResolver = document.subParts?.canonicalTheme
|
|
737
|
+
? new ThemeColorResolver(document.subParts.canonicalTheme)
|
|
738
|
+
: undefined;
|
|
715
739
|
|
|
716
740
|
for (const child of children) {
|
|
717
741
|
const result = appendInlineSegments(
|
|
@@ -722,6 +746,7 @@ function createParagraphBlock(
|
|
|
722
746
|
promoteSecondaryStoryTextBoxes,
|
|
723
747
|
undefined,
|
|
724
748
|
cullBuild,
|
|
749
|
+
themeResolver,
|
|
725
750
|
);
|
|
726
751
|
cursor = result.nextCursor;
|
|
727
752
|
lockedFragmentIds.push(...result.lockedFragmentIds);
|
|
@@ -885,6 +910,7 @@ function appendInlineSegments(
|
|
|
885
910
|
promoteSecondaryStoryTextBoxes: boolean,
|
|
886
911
|
hyperlinkHref?: string,
|
|
887
912
|
cullBuild: boolean = false,
|
|
913
|
+
themeResolver?: ThemeColorResolver,
|
|
888
914
|
): { nextCursor: number; lockedFragmentIds: string[] } {
|
|
889
915
|
switch (node.type) {
|
|
890
916
|
case "text": {
|
|
@@ -919,11 +945,11 @@ function appendInlineSegments(
|
|
|
919
945
|
? resolveHyperlinkRunFormatting(
|
|
920
946
|
runResolveInput,
|
|
921
947
|
document.styles,
|
|
922
|
-
|
|
948
|
+
themeResolver,
|
|
923
949
|
)
|
|
924
950
|
: concretizeThemeColors(
|
|
925
951
|
resolveEffectiveRunFormatting(runResolveInput, document.styles),
|
|
926
|
-
|
|
952
|
+
themeResolver,
|
|
927
953
|
);
|
|
928
954
|
paragraph.segments.push({
|
|
929
955
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
@@ -967,6 +993,7 @@ function appendInlineSegments(
|
|
|
967
993
|
promoteSecondaryStoryTextBoxes,
|
|
968
994
|
node.href,
|
|
969
995
|
cullBuild,
|
|
996
|
+
themeResolver,
|
|
970
997
|
);
|
|
971
998
|
cursor = result.nextCursor;
|
|
972
999
|
}
|
|
@@ -1020,15 +1047,18 @@ function appendInlineSegments(
|
|
|
1020
1047
|
let parsedChartId: string | undefined;
|
|
1021
1048
|
if (node.parsedData) {
|
|
1022
1049
|
parsedChartId = stableChartId(node.rawXml);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1050
|
+
// Always call `set` (even when the entry exists) so the active
|
|
1051
|
+
// `chartModelStore` build pass records the id in its seen-set —
|
|
1052
|
+
// the pass uses the seen-set to evict stale entries from earlier
|
|
1053
|
+
// builds. The entry object is a tiny reference so the set cost
|
|
1054
|
+
// is negligible.
|
|
1055
|
+
const { widthPx, heightPx } = extractChartDimensions(node.rawXml);
|
|
1056
|
+
chartModelStore.set(parsedChartId, {
|
|
1057
|
+
model: node.parsedData,
|
|
1058
|
+
widthPx,
|
|
1059
|
+
heightPx,
|
|
1060
|
+
theme: undefined,
|
|
1061
|
+
});
|
|
1032
1062
|
}
|
|
1033
1063
|
return appendComplexPreviewSegment(paragraph, node, start, "Embedded chart", createChartDetail(node), {
|
|
1034
1064
|
previewMediaId: node.previewMediaId,
|
|
@@ -1064,6 +1094,58 @@ function appendInlineSegments(
|
|
|
1064
1094
|
);
|
|
1065
1095
|
}
|
|
1066
1096
|
return appendComplexPreviewSegment(paragraph, node, start, "Legacy VML drawing", createVmlDetail(node));
|
|
1097
|
+
case "drawing_frame": {
|
|
1098
|
+
const c = node.content;
|
|
1099
|
+
if (c.type === "picture") {
|
|
1100
|
+
const mediaId = c.mediaId ?? `drawing-frame-${start}`;
|
|
1101
|
+
const state: "editable" | "missing" = c.mediaId ? "editable" : "missing";
|
|
1102
|
+
const anchor = surfaceAnchorFromGeometry(node.anchor);
|
|
1103
|
+
const pictureEffects = surfacePictureEffectsFromContent(c);
|
|
1104
|
+
paragraph.segments.push({
|
|
1105
|
+
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
1106
|
+
kind: "image",
|
|
1107
|
+
from: start,
|
|
1108
|
+
to: start + 1,
|
|
1109
|
+
mediaId,
|
|
1110
|
+
state,
|
|
1111
|
+
display: node.anchor.display,
|
|
1112
|
+
detail: `Drawing frame image (${node.anchor.extent.widthEmu}\u00d7${node.anchor.extent.heightEmu} EMU).`,
|
|
1113
|
+
...(anchor ? { anchor } : {}),
|
|
1114
|
+
...(pictureEffects ? { pictureEffects } : {}),
|
|
1115
|
+
});
|
|
1116
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
1117
|
+
}
|
|
1118
|
+
if (c.type === "shape") {
|
|
1119
|
+
const label = c.isTextBox ? "Text box" : "Drawing shape";
|
|
1120
|
+
const detail = `DrawingFrame shape (${node.anchor.wrapMode}).`;
|
|
1121
|
+
const anchor = surfaceAnchorFromGeometry(node.anchor);
|
|
1122
|
+
const txbxText = c.isTextBox ? extractTxbxFirstText(c.txbxBlocks) : undefined;
|
|
1123
|
+
const surfaceFill = c.fill;
|
|
1124
|
+
paragraph.segments.push({
|
|
1125
|
+
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
1126
|
+
kind: "shape",
|
|
1127
|
+
from: start,
|
|
1128
|
+
to: start + 1,
|
|
1129
|
+
label,
|
|
1130
|
+
detail,
|
|
1131
|
+
...(anchor ? { anchor } : {}),
|
|
1132
|
+
...(c.geometry !== undefined ? { geometry: c.geometry } : {}),
|
|
1133
|
+
...(surfaceFill ? { fill: surfaceFill } : {}),
|
|
1134
|
+
...(c.line ? { line: c.line } : {}),
|
|
1135
|
+
...(c.isTextBox ? { isTextBox: true } : {}),
|
|
1136
|
+
...(txbxText ? { txbxText } : {}),
|
|
1137
|
+
});
|
|
1138
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
1139
|
+
}
|
|
1140
|
+
const rawXml = "rawXml" in c ? c.rawXml : "";
|
|
1141
|
+
const label =
|
|
1142
|
+
c.type === "chart_preview"
|
|
1143
|
+
? "Embedded chart"
|
|
1144
|
+
: c.type === "smartart_preview"
|
|
1145
|
+
? "SmartArt diagram"
|
|
1146
|
+
: "Drawing frame";
|
|
1147
|
+
return appendComplexPreviewSegment(paragraph, { rawXml } as DrawingFrameNode["content"] & { rawXml: string }, start, label, `DrawingFrame ${c.type} (${node.anchor.wrapMode}).`);
|
|
1148
|
+
}
|
|
1067
1149
|
case "symbol":
|
|
1068
1150
|
paragraph.segments.push({
|
|
1069
1151
|
segmentId: `${paragraph.blockId}-segment-${paragraph.segments.length}`,
|
|
@@ -1126,6 +1208,7 @@ function appendInlineSegments(
|
|
|
1126
1208
|
promoteSecondaryStoryTextBoxes,
|
|
1127
1209
|
refHyperlinkHref ?? hyperlinkHref,
|
|
1128
1210
|
cullBuild,
|
|
1211
|
+
themeResolver,
|
|
1129
1212
|
);
|
|
1130
1213
|
cursor = result.nextCursor;
|
|
1131
1214
|
lockedIds.push(...result.lockedFragmentIds);
|
|
@@ -1181,6 +1264,101 @@ function appendInlineSegments(
|
|
|
1181
1264
|
}
|
|
1182
1265
|
}
|
|
1183
1266
|
|
|
1267
|
+
/**
|
|
1268
|
+
* V2c.4 — Map a canonical `AnchorGeometry` onto the surface anchor type.
|
|
1269
|
+
* Returns `undefined` when the anchor carries only the trivial inline
|
|
1270
|
+
* defaults (no positioning / wrap / dist data) so the emitted segment
|
|
1271
|
+
* stays minimal for the common inline-image case.
|
|
1272
|
+
*/
|
|
1273
|
+
function surfaceAnchorFromGeometry(
|
|
1274
|
+
anchor: DrawingFrameNode["anchor"],
|
|
1275
|
+
): SurfaceDrawingAnchor | undefined {
|
|
1276
|
+
// Cheap inline-default short-circuit: inline pictures with no extra
|
|
1277
|
+
// metadata (no positionH/V, no distMargins, no docPr, no behindDoc/etc.)
|
|
1278
|
+
// don't need the anchor bag — N9 float-wrap consumers only care about
|
|
1279
|
+
// floating drawings or anchors with custom positioning.
|
|
1280
|
+
const trivialInline =
|
|
1281
|
+
anchor.display === "inline" &&
|
|
1282
|
+
anchor.wrapMode === "none" &&
|
|
1283
|
+
!anchor.positionH &&
|
|
1284
|
+
!anchor.positionV &&
|
|
1285
|
+
!anchor.distMargins &&
|
|
1286
|
+
anchor.relativeHeight === undefined &&
|
|
1287
|
+
anchor.behindDoc === undefined &&
|
|
1288
|
+
anchor.layoutInCell === undefined &&
|
|
1289
|
+
anchor.allowOverlap === undefined &&
|
|
1290
|
+
anchor.simplePos === undefined &&
|
|
1291
|
+
!anchor.docPr;
|
|
1292
|
+
if (trivialInline) return undefined;
|
|
1293
|
+
return {
|
|
1294
|
+
display: anchor.display,
|
|
1295
|
+
wrapMode: anchor.wrapMode,
|
|
1296
|
+
extent: { ...anchor.extent },
|
|
1297
|
+
...(anchor.positionH ? { positionH: { ...anchor.positionH } } : {}),
|
|
1298
|
+
...(anchor.positionV ? { positionV: { ...anchor.positionV } } : {}),
|
|
1299
|
+
...(anchor.distMargins ? { distMargins: { ...anchor.distMargins } } : {}),
|
|
1300
|
+
...(anchor.relativeHeight !== undefined ? { relativeHeight: anchor.relativeHeight } : {}),
|
|
1301
|
+
...(anchor.behindDoc !== undefined ? { behindDoc: anchor.behindDoc } : {}),
|
|
1302
|
+
...(anchor.layoutInCell !== undefined ? { layoutInCell: anchor.layoutInCell } : {}),
|
|
1303
|
+
...(anchor.allowOverlap !== undefined ? { allowOverlap: anchor.allowOverlap } : {}),
|
|
1304
|
+
...(anchor.simplePos !== undefined ? { simplePos: anchor.simplePos } : {}),
|
|
1305
|
+
...(anchor.docPr ? { docPr: { ...anchor.docPr } } : {}),
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* V2c.4 — Project picture-effect fields from `PictureContent`. Returns
|
|
1311
|
+
* `undefined` when none of the effect fields are set so consumers can
|
|
1312
|
+
* fast-path image rendering.
|
|
1313
|
+
*/
|
|
1314
|
+
function surfacePictureEffectsFromContent(
|
|
1315
|
+
content: PictureContent,
|
|
1316
|
+
): SurfacePictureEffects | undefined {
|
|
1317
|
+
const has =
|
|
1318
|
+
content.srcRect !== undefined ||
|
|
1319
|
+
content.rotation !== undefined ||
|
|
1320
|
+
content.flipH !== undefined ||
|
|
1321
|
+
content.flipV !== undefined ||
|
|
1322
|
+
content.presetGeom !== undefined ||
|
|
1323
|
+
content.stretch !== undefined ||
|
|
1324
|
+
content.softEdgeRadius !== undefined ||
|
|
1325
|
+
content.outerShadow !== undefined ||
|
|
1326
|
+
content.glow !== undefined;
|
|
1327
|
+
if (!has) return undefined;
|
|
1328
|
+
return {
|
|
1329
|
+
...(content.srcRect ? { srcRect: { ...content.srcRect } } : {}),
|
|
1330
|
+
...(content.rotation !== undefined ? { rotation: content.rotation } : {}),
|
|
1331
|
+
...(content.flipH !== undefined ? { flipH: content.flipH } : {}),
|
|
1332
|
+
...(content.flipV !== undefined ? { flipV: content.flipV } : {}),
|
|
1333
|
+
...(content.presetGeom !== undefined ? { presetGeom: content.presetGeom } : {}),
|
|
1334
|
+
...(content.stretch !== undefined ? { stretch: content.stretch } : {}),
|
|
1335
|
+
...(content.softEdgeRadius !== undefined ? { softEdgeRadius: content.softEdgeRadius } : {}),
|
|
1336
|
+
...(content.outerShadow !== undefined ? { outerShadow: { ...content.outerShadow } } : {}),
|
|
1337
|
+
...(content.glow !== undefined ? { glow: { ...content.glow } } : {}),
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* V2c.5 — Extract the first paragraph's plain text from a parsed
|
|
1343
|
+
* `txbxBlocks` tree for the `txbxText` segment preview. The recursion
|
|
1344
|
+
* is shallow — we only walk paragraph-shaped blocks at the top level
|
|
1345
|
+
* and read the immediate run content. Returns `undefined` when no text
|
|
1346
|
+
* is present.
|
|
1347
|
+
*/
|
|
1348
|
+
function extractTxbxFirstText(
|
|
1349
|
+
blocks: ShapeContent["txbxBlocks"],
|
|
1350
|
+
): string | undefined {
|
|
1351
|
+
if (!blocks || blocks.length === 0) return undefined;
|
|
1352
|
+
for (const block of blocks) {
|
|
1353
|
+
if (block.type !== "paragraph") continue;
|
|
1354
|
+
const runs = (block as { runs?: ReadonlyArray<{ text?: string }> }).runs;
|
|
1355
|
+
if (!runs) continue;
|
|
1356
|
+
const text = runs.map((r) => r.text ?? "").join("").trim();
|
|
1357
|
+
if (text) return text;
|
|
1358
|
+
}
|
|
1359
|
+
return undefined;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1184
1362
|
function appendComplexPreviewSegment(
|
|
1185
1363
|
paragraph: ParagraphAccumulator,
|
|
1186
1364
|
node: { rawXml: string },
|
|
@@ -1616,6 +1794,8 @@ function summarizePreviewInline(node: InlineNode): string {
|
|
|
1616
1794
|
return node.text ? `[WordArt: ${node.text}]` : "[WordArt]";
|
|
1617
1795
|
case "vml_shape":
|
|
1618
1796
|
return node.text ? `[VML: ${node.text}]` : "[Legacy VML drawing]";
|
|
1797
|
+
case "drawing_frame":
|
|
1798
|
+
return node.content.type === "picture" ? "[Image]" : "[Drawing]";
|
|
1619
1799
|
}
|
|
1620
1800
|
}
|
|
1621
1801
|
|