@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
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type OpcPackagePart,
|
|
8
8
|
type OpcRelationship,
|
|
9
9
|
} from "../../io/ooxml/part-manifest.ts";
|
|
10
|
-
import { parseXml } from "../../io/ooxml/xml-parser.ts";
|
|
10
|
+
import { parseXml, parseXmlWithOffsets } from "../../io/ooxml/xml-parser.ts";
|
|
11
11
|
import type { XmlElementNode } from "../../io/ooxml/xml-element.ts";
|
|
12
12
|
import { localName } from "../../io/ooxml/xml-attr-helpers.ts";
|
|
13
13
|
import {
|
|
@@ -50,6 +50,95 @@ export interface SourcePackageRelationshipEvidence {
|
|
|
50
50
|
targetExists: boolean;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export interface SourcePackageLocationEvidence {
|
|
54
|
+
sourceId: string;
|
|
55
|
+
partPath: string;
|
|
56
|
+
storyKind: SourcePackageStoryKind;
|
|
57
|
+
element: string;
|
|
58
|
+
ordinal: number;
|
|
59
|
+
startOffset?: number;
|
|
60
|
+
endOffset?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SourcePackageStoryPartDescriptor {
|
|
64
|
+
sourceId: string;
|
|
65
|
+
partPath: string;
|
|
66
|
+
storyKind: SourcePackageStoryKind;
|
|
67
|
+
relationshipId?: string;
|
|
68
|
+
relationshipType?: string;
|
|
69
|
+
relationshipSourcePartPath?: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SourcePackageHeaderFooterReferenceEvidence {
|
|
73
|
+
kind: "header" | "footer";
|
|
74
|
+
relationshipId: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
resolvedTarget?: string;
|
|
77
|
+
targetExists?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SourcePackageSectionDescriptorEvidence
|
|
81
|
+
extends SourcePackageLocationEvidence {
|
|
82
|
+
headerFooterReferences: SourcePackageHeaderFooterReferenceEvidence[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SourcePackageObjectExtentEvidence
|
|
86
|
+
extends SourcePackageLocationEvidence {
|
|
87
|
+
kind: "drawing" | "vml-shape" | "object";
|
|
88
|
+
display: "inline" | "floating" | "unknown";
|
|
89
|
+
preserveOnly: true;
|
|
90
|
+
fallbackHint:
|
|
91
|
+
| "drawing-inline"
|
|
92
|
+
| "drawing-floating"
|
|
93
|
+
| "vml-shape"
|
|
94
|
+
| "ole-object"
|
|
95
|
+
| "opaque-object";
|
|
96
|
+
extentEmu?: { width: number; height: number };
|
|
97
|
+
extentTwips?: { width: number; height: number };
|
|
98
|
+
relationshipIds: string[];
|
|
99
|
+
shapeId?: string;
|
|
100
|
+
shapeType?: string;
|
|
101
|
+
vmlStyle?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SourcePackageRelationshipGroupEvidence {
|
|
105
|
+
headerFooters: SourcePackageRelationshipEvidence[];
|
|
106
|
+
media: SourcePackageRelationshipEvidence[];
|
|
107
|
+
charts: SourcePackageRelationshipEvidence[];
|
|
108
|
+
embedded: SourcePackageRelationshipEvidence[];
|
|
109
|
+
customXml: SourcePackageRelationshipEvidence[];
|
|
110
|
+
settings: SourcePackageRelationshipEvidence[];
|
|
111
|
+
numbering: SourcePackageRelationshipEvidence[];
|
|
112
|
+
comments: SourcePackageRelationshipEvidence[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface SourcePackagePartGroupEvidence {
|
|
116
|
+
media: SourcePackageStoryPartDescriptor[];
|
|
117
|
+
images: SourcePackageStoryPartDescriptor[];
|
|
118
|
+
charts: SourcePackageStoryPartDescriptor[];
|
|
119
|
+
embedded: SourcePackageStoryPartDescriptor[];
|
|
120
|
+
customXml: SourcePackageStoryPartDescriptor[];
|
|
121
|
+
settings: SourcePackageStoryPartDescriptor[];
|
|
122
|
+
numbering: SourcePackageStoryPartDescriptor[];
|
|
123
|
+
comments: SourcePackageStoryPartDescriptor[];
|
|
124
|
+
themes: SourcePackageStoryPartDescriptor[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface SourcePackageLayoutInventoryEvidence {
|
|
128
|
+
storyParts: SourcePackageStoryPartDescriptor[];
|
|
129
|
+
partGroups: SourcePackagePartGroupEvidence;
|
|
130
|
+
relationships: SourcePackageRelationshipGroupEvidence;
|
|
131
|
+
sections: SourcePackageSectionDescriptorEvidence[];
|
|
132
|
+
tables: SourcePackageLocationEvidence[];
|
|
133
|
+
fields: SourcePackageLocationEvidence[];
|
|
134
|
+
drawings: SourcePackageLocationEvidence[];
|
|
135
|
+
vmlShapes: SourcePackageLocationEvidence[];
|
|
136
|
+
contentControls: SourcePackageLocationEvidence[];
|
|
137
|
+
bookmarks: SourcePackageLocationEvidence[];
|
|
138
|
+
notes: SourcePackageLocationEvidence[];
|
|
139
|
+
objects: SourcePackageObjectExtentEvidence[];
|
|
140
|
+
}
|
|
141
|
+
|
|
53
142
|
export interface SourcePackageEvidence {
|
|
54
143
|
schemaVersion: 1;
|
|
55
144
|
docId: string;
|
|
@@ -78,6 +167,7 @@ export interface SourcePackageEvidence {
|
|
|
78
167
|
};
|
|
79
168
|
parts: SourcePackagePartEvidence[];
|
|
80
169
|
relationships: SourcePackageRelationshipEvidence[];
|
|
170
|
+
layoutInventory: SourcePackageLayoutInventoryEvidence;
|
|
81
171
|
parseErrors: Array<{ partPath: string; message: string }>;
|
|
82
172
|
}
|
|
83
173
|
|
|
@@ -162,6 +252,13 @@ export function createSourcePackageEvidence(
|
|
|
162
252
|
(part) => part.storyKind !== "relationships" && part.storyKind !== "content-types",
|
|
163
253
|
);
|
|
164
254
|
const aggregateCounts = aggregateElementCounts(parts.map((part) => part.counts));
|
|
255
|
+
const layoutInventory = collectLayoutInventory(
|
|
256
|
+
sourcePackage,
|
|
257
|
+
parts,
|
|
258
|
+
relationships,
|
|
259
|
+
mainDocumentPartPath,
|
|
260
|
+
parseErrors,
|
|
261
|
+
);
|
|
165
262
|
|
|
166
263
|
return {
|
|
167
264
|
schemaVersion: 1,
|
|
@@ -192,6 +289,7 @@ export function createSourcePackageEvidence(
|
|
|
192
289
|
},
|
|
193
290
|
parts,
|
|
194
291
|
relationships,
|
|
292
|
+
layoutInventory,
|
|
195
293
|
parseErrors,
|
|
196
294
|
};
|
|
197
295
|
}
|
|
@@ -308,6 +406,365 @@ function countElements(root: XmlElementNode): SourcePackageElementCounts {
|
|
|
308
406
|
return counts;
|
|
309
407
|
}
|
|
310
408
|
|
|
409
|
+
function collectLayoutInventory(
|
|
410
|
+
sourcePackage: OpcPackage,
|
|
411
|
+
parts: SourcePackagePartEvidence[],
|
|
412
|
+
relationships: SourcePackageRelationshipEvidence[],
|
|
413
|
+
mainDocumentPartPath: string | undefined,
|
|
414
|
+
parseErrors: SourcePackageEvidence["parseErrors"],
|
|
415
|
+
): SourcePackageLayoutInventoryEvidence {
|
|
416
|
+
const storyParts = collectStoryPartDescriptors(parts, relationships);
|
|
417
|
+
const partGroups = groupParts(storyParts, parts);
|
|
418
|
+
const relationshipGroups = groupRelationships(relationships);
|
|
419
|
+
const locations: Pick<
|
|
420
|
+
SourcePackageLayoutInventoryEvidence,
|
|
421
|
+
| "sections"
|
|
422
|
+
| "tables"
|
|
423
|
+
| "fields"
|
|
424
|
+
| "drawings"
|
|
425
|
+
| "vmlShapes"
|
|
426
|
+
| "contentControls"
|
|
427
|
+
| "bookmarks"
|
|
428
|
+
| "notes"
|
|
429
|
+
| "objects"
|
|
430
|
+
> = {
|
|
431
|
+
sections: [],
|
|
432
|
+
tables: [],
|
|
433
|
+
fields: [],
|
|
434
|
+
drawings: [],
|
|
435
|
+
vmlShapes: [],
|
|
436
|
+
contentControls: [],
|
|
437
|
+
bookmarks: [],
|
|
438
|
+
notes: [],
|
|
439
|
+
objects: [],
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const partEvidenceByPath = new Map(parts.map((part) => [part.path, part]));
|
|
443
|
+
const sourceRelationshipByPart = relationships.reduce((map, relationship) => {
|
|
444
|
+
if (!relationship.sourcePartPath) return map;
|
|
445
|
+
const list = map.get(relationship.sourcePartPath) ?? [];
|
|
446
|
+
list.push(relationship);
|
|
447
|
+
map.set(relationship.sourcePartPath, list);
|
|
448
|
+
return map;
|
|
449
|
+
}, new Map<string, SourcePackageRelationshipEvidence[]>());
|
|
450
|
+
|
|
451
|
+
for (const part of sourcePackage.parts.values()) {
|
|
452
|
+
const partEvidence = partEvidenceByPath.get(part.path);
|
|
453
|
+
const storyKind = partEvidence?.storyKind ?? classifyPart(part, mainDocumentPartPath);
|
|
454
|
+
if (!shouldParseXmlPart(part, storyKind)) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let root: XmlElementNode;
|
|
459
|
+
try {
|
|
460
|
+
root = parseXmlWithOffsets(new TextDecoder("utf-8").decode(part.bytes));
|
|
461
|
+
} catch (error) {
|
|
462
|
+
parseErrors.push({
|
|
463
|
+
partPath: part.path,
|
|
464
|
+
message: error instanceof Error ? error.message : String(error),
|
|
465
|
+
});
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
collectPartLocations(
|
|
470
|
+
root,
|
|
471
|
+
{
|
|
472
|
+
partPath: part.path,
|
|
473
|
+
storyKind,
|
|
474
|
+
relationships: sourceRelationshipByPart.get(part.path) ?? [],
|
|
475
|
+
},
|
|
476
|
+
locations,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
storyParts,
|
|
482
|
+
partGroups,
|
|
483
|
+
relationships: relationshipGroups,
|
|
484
|
+
...locations,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function collectStoryPartDescriptors(
|
|
489
|
+
parts: SourcePackagePartEvidence[],
|
|
490
|
+
relationships: SourcePackageRelationshipEvidence[],
|
|
491
|
+
): SourcePackageStoryPartDescriptor[] {
|
|
492
|
+
const descriptorByPath = new Map<string, SourcePackageStoryPartDescriptor>();
|
|
493
|
+
for (const part of parts) {
|
|
494
|
+
if (!isLayoutRelevantStoryKind(part.storyKind)) continue;
|
|
495
|
+
descriptorByPath.set(part.path, {
|
|
496
|
+
sourceId: partSourceId(part.path),
|
|
497
|
+
partPath: part.path,
|
|
498
|
+
storyKind: part.storyKind,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
for (const relationship of relationships) {
|
|
503
|
+
if (relationship.targetMode !== "internal") continue;
|
|
504
|
+
const resolvedPath = normalizePartPath(relationship.resolvedTarget);
|
|
505
|
+
const descriptor = descriptorByPath.get(resolvedPath);
|
|
506
|
+
if (!descriptor) continue;
|
|
507
|
+
descriptorByPath.set(resolvedPath, {
|
|
508
|
+
...descriptor,
|
|
509
|
+
relationshipId: relationship.relationshipId,
|
|
510
|
+
relationshipType: relationship.relationshipType,
|
|
511
|
+
relationshipSourcePartPath: relationship.sourcePartPath,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return [...descriptorByPath.values()].sort((left, right) =>
|
|
516
|
+
left.partPath.localeCompare(right.partPath),
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function groupParts(
|
|
521
|
+
descriptors: SourcePackageStoryPartDescriptor[],
|
|
522
|
+
parts: SourcePackagePartEvidence[],
|
|
523
|
+
): SourcePackagePartGroupEvidence {
|
|
524
|
+
const contentTypeByPath = new Map(parts.map((part) => [part.path, part.contentType]));
|
|
525
|
+
const byKind = (kind: SourcePackageStoryKind): SourcePackageStoryPartDescriptor[] =>
|
|
526
|
+
descriptors.filter((part) => part.storyKind === kind);
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
media: byKind("media"),
|
|
530
|
+
images: byKind("media").filter((part) =>
|
|
531
|
+
contentTypeByPath.get(part.partPath)?.startsWith("image/") === true,
|
|
532
|
+
),
|
|
533
|
+
charts: byKind("chart"),
|
|
534
|
+
embedded: byKind("embedded"),
|
|
535
|
+
customXml: byKind("custom-xml"),
|
|
536
|
+
settings: byKind("settings"),
|
|
537
|
+
numbering: byKind("numbering"),
|
|
538
|
+
comments: byKind("comments"),
|
|
539
|
+
themes: byKind("theme"),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function groupRelationships(
|
|
544
|
+
relationships: SourcePackageRelationshipEvidence[],
|
|
545
|
+
): SourcePackageRelationshipGroupEvidence {
|
|
546
|
+
const byRole = (role: string): SourcePackageRelationshipEvidence[] =>
|
|
547
|
+
relationships.filter((relationship) => relationshipRole(relationship) === role);
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
headerFooters: relationships.filter((relationship) => {
|
|
551
|
+
const role = relationshipRole(relationship);
|
|
552
|
+
return role === "header" || role === "footer";
|
|
553
|
+
}),
|
|
554
|
+
media: byRole("image"),
|
|
555
|
+
charts: byRole("chart"),
|
|
556
|
+
embedded: relationships.filter((relationship) => {
|
|
557
|
+
const role = relationshipRole(relationship);
|
|
558
|
+
return role === "oleObject" || role === "package";
|
|
559
|
+
}),
|
|
560
|
+
customXml: byRole("customXml"),
|
|
561
|
+
settings: byRole("settings"),
|
|
562
|
+
numbering: byRole("numbering"),
|
|
563
|
+
comments: byRole("comments"),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function collectPartLocations(
|
|
568
|
+
root: XmlElementNode,
|
|
569
|
+
context: {
|
|
570
|
+
partPath: string;
|
|
571
|
+
storyKind: SourcePackageStoryKind;
|
|
572
|
+
relationships: SourcePackageRelationshipEvidence[];
|
|
573
|
+
},
|
|
574
|
+
locations: Pick<
|
|
575
|
+
SourcePackageLayoutInventoryEvidence,
|
|
576
|
+
| "sections"
|
|
577
|
+
| "tables"
|
|
578
|
+
| "fields"
|
|
579
|
+
| "drawings"
|
|
580
|
+
| "vmlShapes"
|
|
581
|
+
| "contentControls"
|
|
582
|
+
| "bookmarks"
|
|
583
|
+
| "notes"
|
|
584
|
+
| "objects"
|
|
585
|
+
>,
|
|
586
|
+
): void {
|
|
587
|
+
const ordinals = new Map<string, number>();
|
|
588
|
+
const nextOrdinal = (element: string): number => {
|
|
589
|
+
const next = (ordinals.get(element) ?? 0) + 1;
|
|
590
|
+
ordinals.set(element, next);
|
|
591
|
+
return next;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
const visit = (node: XmlElementNode): void => {
|
|
595
|
+
const name = localName(node.name);
|
|
596
|
+
switch (name) {
|
|
597
|
+
case "sectPr":
|
|
598
|
+
locations.sections.push({
|
|
599
|
+
...locationFor(context, node, name, nextOrdinal(name)),
|
|
600
|
+
headerFooterReferences: collectHeaderFooterReferences(node, context.relationships),
|
|
601
|
+
});
|
|
602
|
+
break;
|
|
603
|
+
case "tbl":
|
|
604
|
+
locations.tables.push(locationFor(context, node, name, nextOrdinal(name)));
|
|
605
|
+
break;
|
|
606
|
+
case "fldSimple":
|
|
607
|
+
case "fldChar":
|
|
608
|
+
case "instrText":
|
|
609
|
+
locations.fields.push(locationFor(context, node, name, nextOrdinal(name)));
|
|
610
|
+
break;
|
|
611
|
+
case "drawing": {
|
|
612
|
+
const ordinal = nextOrdinal(name);
|
|
613
|
+
locations.drawings.push(locationFor(context, node, name, ordinal));
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
case "inline":
|
|
617
|
+
case "anchor": {
|
|
618
|
+
const ordinal = nextOrdinal(name);
|
|
619
|
+
locations.objects.push(drawingObjectFor(context, node, name, ordinal));
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
case "shape": {
|
|
623
|
+
const ordinal = nextOrdinal(name);
|
|
624
|
+
locations.vmlShapes.push(locationFor(context, node, name, ordinal));
|
|
625
|
+
locations.objects.push(vmlObjectFor(context, node, ordinal));
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case "object": {
|
|
629
|
+
const ordinal = nextOrdinal(name);
|
|
630
|
+
locations.objects.push(objectFor(context, node, ordinal));
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
case "sdt":
|
|
634
|
+
locations.contentControls.push(locationFor(context, node, name, nextOrdinal(name)));
|
|
635
|
+
break;
|
|
636
|
+
case "bookmarkStart":
|
|
637
|
+
locations.bookmarks.push(locationFor(context, node, name, nextOrdinal(name)));
|
|
638
|
+
break;
|
|
639
|
+
case "footnote":
|
|
640
|
+
case "endnote":
|
|
641
|
+
locations.notes.push(locationFor(context, node, name, nextOrdinal(name)));
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const child of node.children) {
|
|
646
|
+
if (child.type === "element") {
|
|
647
|
+
visit(child);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
visit(root);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function locationFor(
|
|
656
|
+
context: {
|
|
657
|
+
partPath: string;
|
|
658
|
+
storyKind: SourcePackageStoryKind;
|
|
659
|
+
},
|
|
660
|
+
node: XmlElementNode,
|
|
661
|
+
element: string,
|
|
662
|
+
ordinal: number,
|
|
663
|
+
): SourcePackageLocationEvidence {
|
|
664
|
+
return {
|
|
665
|
+
sourceId: elementSourceId(context.partPath, element, ordinal),
|
|
666
|
+
partPath: context.partPath,
|
|
667
|
+
storyKind: context.storyKind,
|
|
668
|
+
element,
|
|
669
|
+
ordinal,
|
|
670
|
+
...(node.start !== undefined ? { startOffset: node.start } : {}),
|
|
671
|
+
...(node.end !== undefined ? { endOffset: node.end } : {}),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function drawingObjectFor(
|
|
676
|
+
context: {
|
|
677
|
+
partPath: string;
|
|
678
|
+
storyKind: SourcePackageStoryKind;
|
|
679
|
+
},
|
|
680
|
+
node: XmlElementNode,
|
|
681
|
+
element: string,
|
|
682
|
+
ordinal: number,
|
|
683
|
+
): SourcePackageObjectExtentEvidence {
|
|
684
|
+
const extentEmu = readDrawingExtentEmu(node);
|
|
685
|
+
return {
|
|
686
|
+
...locationFor(context, node, element, ordinal),
|
|
687
|
+
kind: "drawing",
|
|
688
|
+
display: element === "anchor" ? "floating" : "inline",
|
|
689
|
+
preserveOnly: true,
|
|
690
|
+
fallbackHint: element === "anchor" ? "drawing-floating" : "drawing-inline",
|
|
691
|
+
...(extentEmu ? { extentEmu, extentTwips: extentEmuToTwips(extentEmu) } : {}),
|
|
692
|
+
relationshipIds: collectRelationshipIds(node),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function vmlObjectFor(
|
|
697
|
+
context: {
|
|
698
|
+
partPath: string;
|
|
699
|
+
storyKind: SourcePackageStoryKind;
|
|
700
|
+
},
|
|
701
|
+
node: XmlElementNode,
|
|
702
|
+
ordinal: number,
|
|
703
|
+
): SourcePackageObjectExtentEvidence {
|
|
704
|
+
const extentTwips = readVmlExtentTwips(node.attributes.style);
|
|
705
|
+
return {
|
|
706
|
+
...locationFor(context, node, "shape", ordinal),
|
|
707
|
+
kind: "vml-shape",
|
|
708
|
+
display: "unknown",
|
|
709
|
+
preserveOnly: true,
|
|
710
|
+
fallbackHint: "vml-shape",
|
|
711
|
+
...(extentTwips ? { extentTwips, extentEmu: extentTwipsToEmu(extentTwips) } : {}),
|
|
712
|
+
relationshipIds: collectRelationshipIds(node),
|
|
713
|
+
...(node.attributes.id ? { shapeId: node.attributes.id } : {}),
|
|
714
|
+
...(node.attributes.type ? { shapeType: node.attributes.type } : {}),
|
|
715
|
+
...(node.attributes.style ? { vmlStyle: node.attributes.style } : {}),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function objectFor(
|
|
720
|
+
context: {
|
|
721
|
+
partPath: string;
|
|
722
|
+
storyKind: SourcePackageStoryKind;
|
|
723
|
+
},
|
|
724
|
+
node: XmlElementNode,
|
|
725
|
+
ordinal: number,
|
|
726
|
+
): SourcePackageObjectExtentEvidence {
|
|
727
|
+
return {
|
|
728
|
+
...locationFor(context, node, "object", ordinal),
|
|
729
|
+
kind: "object",
|
|
730
|
+
display: "unknown",
|
|
731
|
+
preserveOnly: true,
|
|
732
|
+
fallbackHint: hasDescendant(node, "OLEObject") ? "ole-object" : "opaque-object",
|
|
733
|
+
relationshipIds: collectRelationshipIds(node),
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function collectHeaderFooterReferences(
|
|
738
|
+
node: XmlElementNode,
|
|
739
|
+
relationships: SourcePackageRelationshipEvidence[],
|
|
740
|
+
): SourcePackageHeaderFooterReferenceEvidence[] {
|
|
741
|
+
const byId = new Map(relationships.map((relationship) => [relationship.relationshipId, relationship]));
|
|
742
|
+
const refs: SourcePackageHeaderFooterReferenceEvidence[] = [];
|
|
743
|
+
const visit = (current: XmlElementNode): void => {
|
|
744
|
+
const name = localName(current.name);
|
|
745
|
+
if (name === "headerReference" || name === "footerReference") {
|
|
746
|
+
const relationshipId = readRelationshipId(current);
|
|
747
|
+
if (relationshipId) {
|
|
748
|
+
const relationship = byId.get(relationshipId);
|
|
749
|
+
refs.push({
|
|
750
|
+
kind: name === "headerReference" ? "header" : "footer",
|
|
751
|
+
relationshipId,
|
|
752
|
+
...(current.attributes["w:type"] ?? current.attributes.type
|
|
753
|
+
? { type: current.attributes["w:type"] ?? current.attributes.type }
|
|
754
|
+
: {}),
|
|
755
|
+
...(relationship ? { resolvedTarget: relationship.resolvedTarget } : {}),
|
|
756
|
+
...(relationship ? { targetExists: relationship.targetExists } : {}),
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
for (const child of current.children) {
|
|
761
|
+
if (child.type === "element") visit(child);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
visit(node);
|
|
765
|
+
return refs;
|
|
766
|
+
}
|
|
767
|
+
|
|
311
768
|
function collectRelationships(
|
|
312
769
|
sourcePackage: OpcPackage,
|
|
313
770
|
): SourcePackageRelationshipEvidence[] {
|
|
@@ -403,3 +860,157 @@ function countParts(
|
|
|
403
860
|
): number {
|
|
404
861
|
return parts.filter((part) => part.storyKind === storyKind).length;
|
|
405
862
|
}
|
|
863
|
+
|
|
864
|
+
function isLayoutRelevantStoryKind(storyKind: SourcePackageStoryKind): boolean {
|
|
865
|
+
return (
|
|
866
|
+
storyKind === "main-document" ||
|
|
867
|
+
storyKind === "header" ||
|
|
868
|
+
storyKind === "footer" ||
|
|
869
|
+
storyKind === "footnotes" ||
|
|
870
|
+
storyKind === "endnotes" ||
|
|
871
|
+
storyKind === "comments" ||
|
|
872
|
+
storyKind === "numbering" ||
|
|
873
|
+
storyKind === "settings" ||
|
|
874
|
+
storyKind === "theme" ||
|
|
875
|
+
storyKind === "chart" ||
|
|
876
|
+
storyKind === "media" ||
|
|
877
|
+
storyKind === "embedded" ||
|
|
878
|
+
storyKind === "custom-xml"
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function relationshipRole(relationship: SourcePackageRelationshipEvidence): string {
|
|
883
|
+
const slashIndex = relationship.relationshipType.lastIndexOf("/");
|
|
884
|
+
return slashIndex >= 0
|
|
885
|
+
? relationship.relationshipType.slice(slashIndex + 1)
|
|
886
|
+
: relationship.relationshipType;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function partSourceId(partPath: string): string {
|
|
890
|
+
return `part:${partPath}`;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function elementSourceId(partPath: string, element: string, ordinal: number): string {
|
|
894
|
+
return `${partSourceId(partPath)}#${element}:${ordinal}`;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function readDrawingExtentEmu(
|
|
898
|
+
node: XmlElementNode,
|
|
899
|
+
): { width: number; height: number } | undefined {
|
|
900
|
+
const extent = findFirstDescendantByLocalName(node, "extent");
|
|
901
|
+
const width = extent ? readIntAttribute(extent, "cx") : undefined;
|
|
902
|
+
const height = extent ? readIntAttribute(extent, "cy") : undefined;
|
|
903
|
+
if (width !== undefined && height !== undefined) {
|
|
904
|
+
return { width, height };
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const transform = findFirstDescendantByLocalName(node, "xfrm");
|
|
908
|
+
const ext = transform ? findFirstDescendantByLocalName(transform, "ext") : undefined;
|
|
909
|
+
const fallbackWidth = ext ? readIntAttribute(ext, "cx") : undefined;
|
|
910
|
+
const fallbackHeight = ext ? readIntAttribute(ext, "cy") : undefined;
|
|
911
|
+
if (fallbackWidth !== undefined && fallbackHeight !== undefined) {
|
|
912
|
+
return { width: fallbackWidth, height: fallbackHeight };
|
|
913
|
+
}
|
|
914
|
+
return undefined;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function readVmlExtentTwips(
|
|
918
|
+
style: string | undefined,
|
|
919
|
+
): { width: number; height: number } | undefined {
|
|
920
|
+
if (!style) return undefined;
|
|
921
|
+
const width = readCssLengthTwips(style, "width");
|
|
922
|
+
const height = readCssLengthTwips(style, "height");
|
|
923
|
+
if (width !== undefined && height !== undefined) {
|
|
924
|
+
return { width, height };
|
|
925
|
+
}
|
|
926
|
+
return undefined;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function readCssLengthTwips(style: string, property: "width" | "height"): number | undefined {
|
|
930
|
+
const match = new RegExp(`(?:^|;)\\s*${property}\\s*:\\s*([0-9.]+)\\s*(pt|in|cm|mm|px)?`, "iu")
|
|
931
|
+
.exec(style);
|
|
932
|
+
if (!match) return undefined;
|
|
933
|
+
const value = Number.parseFloat(match[1] ?? "");
|
|
934
|
+
if (!Number.isFinite(value)) return undefined;
|
|
935
|
+
const unit = (match[2] ?? "pt").toLowerCase();
|
|
936
|
+
switch (unit) {
|
|
937
|
+
case "pt":
|
|
938
|
+
return Math.round(value * 20);
|
|
939
|
+
case "in":
|
|
940
|
+
return Math.round(value * 1440);
|
|
941
|
+
case "cm":
|
|
942
|
+
return Math.round(value * 567);
|
|
943
|
+
case "mm":
|
|
944
|
+
return Math.round(value * 56.7);
|
|
945
|
+
case "px":
|
|
946
|
+
return Math.round(value * 15);
|
|
947
|
+
default:
|
|
948
|
+
return undefined;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function extentEmuToTwips(
|
|
953
|
+
extent: { width: number; height: number },
|
|
954
|
+
): { width: number; height: number } {
|
|
955
|
+
return {
|
|
956
|
+
width: Math.round(extent.width / 635),
|
|
957
|
+
height: Math.round(extent.height / 635),
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function extentTwipsToEmu(
|
|
962
|
+
extent: { width: number; height: number },
|
|
963
|
+
): { width: number; height: number } {
|
|
964
|
+
return {
|
|
965
|
+
width: Math.round(extent.width * 635),
|
|
966
|
+
height: Math.round(extent.height * 635),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function collectRelationshipIds(node: XmlElementNode): string[] {
|
|
971
|
+
const ids = new Set<string>();
|
|
972
|
+
const visit = (current: XmlElementNode): void => {
|
|
973
|
+
const id = readRelationshipId(current);
|
|
974
|
+
if (id) ids.add(id);
|
|
975
|
+
for (const child of current.children) {
|
|
976
|
+
if (child.type === "element") visit(child);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
visit(node);
|
|
980
|
+
return [...ids].sort();
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function readRelationshipId(node: XmlElementNode): string | undefined {
|
|
984
|
+
return (
|
|
985
|
+
node.attributes["r:id"] ??
|
|
986
|
+
node.attributes.id ??
|
|
987
|
+
node.attributes["r:embed"] ??
|
|
988
|
+
node.attributes.embed ??
|
|
989
|
+
node.attributes["r:link"] ??
|
|
990
|
+
node.attributes.link
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function findFirstDescendantByLocalName(
|
|
995
|
+
node: XmlElementNode,
|
|
996
|
+
name: string,
|
|
997
|
+
): XmlElementNode | undefined {
|
|
998
|
+
for (const child of node.children) {
|
|
999
|
+
if (child.type !== "element") continue;
|
|
1000
|
+
if (localName(child.name) === name) return child;
|
|
1001
|
+
const nested = findFirstDescendantByLocalName(child, name);
|
|
1002
|
+
if (nested) return nested;
|
|
1003
|
+
}
|
|
1004
|
+
return undefined;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function hasDescendant(node: XmlElementNode, name: string): boolean {
|
|
1008
|
+
return findFirstDescendantByLocalName(node, name) !== undefined;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function readIntAttribute(node: XmlElementNode, name: string): number | undefined {
|
|
1012
|
+
const raw = node.attributes[name];
|
|
1013
|
+
if (raw === undefined) return undefined;
|
|
1014
|
+
const value = Number.parseInt(raw, 10);
|
|
1015
|
+
return Number.isFinite(value) ? value : undefined;
|
|
1016
|
+
}
|
|
@@ -839,6 +839,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
839
839
|
null,
|
|
840
840
|
);
|
|
841
841
|
if (geometryRects !== null) {
|
|
842
|
+
incrementInvalidationCounter("overlay.page.geometry.hit");
|
|
842
843
|
setRectsIfChanged(reconcilePaperRectsWithFlow(geometryRects, pageCount));
|
|
843
844
|
return;
|
|
844
845
|
}
|
|
@@ -854,6 +855,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
854
855
|
// above via `geometryFacet` or the UI-API resolver. Lines below
|
|
855
856
|
// fire only before the first render frame.
|
|
856
857
|
if (origin) {
|
|
858
|
+
incrementInvalidationCounter("overlay.page.dom.degraded");
|
|
857
859
|
const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
|
|
858
860
|
pageCount,
|
|
859
861
|
visiblePageIndexRange: null,
|
|
@@ -870,6 +872,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
870
872
|
});
|
|
871
873
|
setRectsIfChanged(reconcilePaperRectsWithFlow(domRects, pageCount));
|
|
872
874
|
} else {
|
|
875
|
+
incrementInvalidationCounter("overlay.page.dom.degraded");
|
|
873
876
|
const widgets = measureWidgetsViaOffsetChain(scrollRoot, {
|
|
874
877
|
pageCount,
|
|
875
878
|
visiblePageIndexRange: null,
|