@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
|
@@ -74,7 +74,8 @@ interface PendingResolution {
|
|
|
74
74
|
readonly heightEmu: number;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
type
|
|
77
|
+
type PointerStep = number | { rowIndex: number; cellIndex: number };
|
|
78
|
+
type Pointer = PointerStep[];
|
|
78
79
|
|
|
79
80
|
export async function resolveChartPreviewsForDocument(
|
|
80
81
|
doc: CanonicalDocument,
|
|
@@ -189,30 +190,92 @@ export function scheduleChartPreviewResolution(
|
|
|
189
190
|
function collectUnresolvedChartPreviews(doc: CanonicalDocument, pkg: OpcPackage): PendingResolution[] {
|
|
190
191
|
const out: PendingResolution[] = [];
|
|
191
192
|
const documentRels = collectDocumentPartRelationships(pkg);
|
|
193
|
+
collectUnresolvedChartPreviewsFromBlocks(
|
|
194
|
+
doc.content.children,
|
|
195
|
+
[],
|
|
196
|
+
documentRels,
|
|
197
|
+
pkg,
|
|
198
|
+
out,
|
|
199
|
+
);
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
192
202
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
203
|
+
function collectUnresolvedChartPreviewsFromBlocks(
|
|
204
|
+
blocks: readonly BlockNode[],
|
|
205
|
+
pointerPrefix: Pointer,
|
|
206
|
+
documentRels: Map<string, string>,
|
|
207
|
+
pkg: OpcPackage,
|
|
208
|
+
out: PendingResolution[],
|
|
209
|
+
): void {
|
|
210
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
|
|
211
|
+
const block = blocks[blockIndex];
|
|
212
|
+
if (!block) continue;
|
|
213
|
+
switch (block.type) {
|
|
214
|
+
case "paragraph":
|
|
215
|
+
collectUnresolvedChartPreviewsFromParagraph(
|
|
216
|
+
block,
|
|
217
|
+
[...pointerPrefix, blockIndex],
|
|
218
|
+
documentRels,
|
|
219
|
+
pkg,
|
|
220
|
+
out,
|
|
221
|
+
);
|
|
222
|
+
break;
|
|
223
|
+
case "table":
|
|
224
|
+
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
|
|
225
|
+
const row = block.rows[rowIndex];
|
|
226
|
+
if (!row) continue;
|
|
227
|
+
for (let cellIndex = 0; cellIndex < row.cells.length; cellIndex += 1) {
|
|
228
|
+
const cell = row.cells[cellIndex];
|
|
229
|
+
if (!cell) continue;
|
|
230
|
+
collectUnresolvedChartPreviewsFromBlocks(
|
|
231
|
+
cell.children,
|
|
232
|
+
[...pointerPrefix, blockIndex, { rowIndex, cellIndex }],
|
|
233
|
+
documentRels,
|
|
234
|
+
pkg,
|
|
235
|
+
out,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case "sdt":
|
|
241
|
+
case "custom_xml":
|
|
242
|
+
collectUnresolvedChartPreviewsFromBlocks(
|
|
243
|
+
block.children,
|
|
244
|
+
[...pointerPrefix, blockIndex],
|
|
245
|
+
documentRels,
|
|
246
|
+
pkg,
|
|
247
|
+
out,
|
|
248
|
+
);
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
break;
|
|
213
252
|
}
|
|
214
253
|
}
|
|
215
|
-
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function collectUnresolvedChartPreviewsFromParagraph(
|
|
257
|
+
paragraph: ParagraphNode,
|
|
258
|
+
pointerPrefix: Pointer,
|
|
259
|
+
documentRels: Map<string, string>,
|
|
260
|
+
pkg: OpcPackage,
|
|
261
|
+
out: PendingResolution[],
|
|
262
|
+
): void {
|
|
263
|
+
for (let childIndex = 0; childIndex < paragraph.children.length; childIndex += 1) {
|
|
264
|
+
const child = paragraph.children[childIndex];
|
|
265
|
+
if (!child || child.type !== "chart_preview") continue;
|
|
266
|
+
const chartNode = child as ChartPreviewNode;
|
|
267
|
+
if (chartNode.previewMediaId) continue;
|
|
268
|
+
const resolved = resolveChartPart(chartNode, documentRels, pkg);
|
|
269
|
+
if (!resolved) continue;
|
|
270
|
+
out.push({
|
|
271
|
+
pointer: [...pointerPrefix, childIndex],
|
|
272
|
+
node: chartNode,
|
|
273
|
+
chartPartPath: resolved.chartPartPath,
|
|
274
|
+
chartXml: resolved.chartXml,
|
|
275
|
+
widthEmu: resolved.widthEmu,
|
|
276
|
+
heightEmu: resolved.heightEmu,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
216
279
|
}
|
|
217
280
|
|
|
218
281
|
function collectDocumentPartRelationships(pkg: OpcPackage): Map<string, string> {
|
|
@@ -323,7 +386,7 @@ function applyResolutions(
|
|
|
323
386
|
resolutions: Array<{ entry: PendingResolution; bytes: Uint8Array }>,
|
|
324
387
|
): CanonicalDocument {
|
|
325
388
|
const newMediaItems: Record<string, MediaItem> = { ...doc.media.items };
|
|
326
|
-
|
|
389
|
+
let newBlocks = doc.content.children;
|
|
327
390
|
|
|
328
391
|
let seq = 0;
|
|
329
392
|
for (const { entry, bytes } of resolutions) {
|
|
@@ -340,31 +403,102 @@ function applyResolutions(
|
|
|
340
403
|
widthEmu: entry.widthEmu,
|
|
341
404
|
heightEmu: entry.heightEmu,
|
|
342
405
|
};
|
|
343
|
-
|
|
344
|
-
updates.set(pointerKey, { previewMediaId: mediaId });
|
|
406
|
+
newBlocks = updateChartPreviewAtPointer(newBlocks, entry.pointer, mediaId);
|
|
345
407
|
}
|
|
346
408
|
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
// Clone the content tree along pointer paths only — everything else
|
|
350
|
-
// keeps object identity so downstream React memoization stays stable.
|
|
351
|
-
const newParagraphs: BlockNode[] = doc.content.children.slice();
|
|
352
|
-
for (const [pointerKey, update] of updates) {
|
|
353
|
-
const [pi, ci] = pointerKey.split(",").map((s) => parseInt(s, 10)) as [number, number];
|
|
354
|
-
const paragraph = newParagraphs[pi];
|
|
355
|
-
if (!paragraph || paragraph.type !== "paragraph") continue;
|
|
356
|
-
const newChildren: InlineNode[] = (paragraph as ParagraphNode).children.slice();
|
|
357
|
-
const existing = newChildren[ci];
|
|
358
|
-
if (!existing || existing.type !== "chart_preview") continue;
|
|
359
|
-
newChildren[ci] = { ...(existing as ChartPreviewNode), previewMediaId: update.previewMediaId };
|
|
360
|
-
newParagraphs[pi] = { ...(paragraph as ParagraphNode), children: newChildren };
|
|
361
|
-
}
|
|
409
|
+
if (newBlocks === doc.content.children) return doc;
|
|
362
410
|
|
|
363
|
-
const newContent: DocumentRootNode = { ...doc.content, children:
|
|
411
|
+
const newContent: DocumentRootNode = { ...doc.content, children: newBlocks };
|
|
364
412
|
const newMedia = { items: newMediaItems };
|
|
365
413
|
return { ...doc, content: newContent, media: newMedia };
|
|
366
414
|
}
|
|
367
415
|
|
|
416
|
+
function updateChartPreviewAtPointer(
|
|
417
|
+
blocks: readonly BlockNode[],
|
|
418
|
+
pointer: Pointer,
|
|
419
|
+
previewMediaId: string,
|
|
420
|
+
): BlockNode[] {
|
|
421
|
+
if (pointer.length < 2) return blocks as BlockNode[];
|
|
422
|
+
const [head, ...rest] = pointer;
|
|
423
|
+
if (typeof head !== "number") return blocks as BlockNode[];
|
|
424
|
+
const targetBlock = blocks[head];
|
|
425
|
+
if (!targetBlock) return blocks as BlockNode[];
|
|
426
|
+
|
|
427
|
+
switch (targetBlock.type) {
|
|
428
|
+
case "paragraph": {
|
|
429
|
+
if (rest.length !== 1 || typeof rest[0] !== "number") {
|
|
430
|
+
return blocks as BlockNode[];
|
|
431
|
+
}
|
|
432
|
+
const inlineIndex = rest[0];
|
|
433
|
+
const child = targetBlock.children[inlineIndex];
|
|
434
|
+
if (!child || child.type !== "chart_preview") return blocks as BlockNode[];
|
|
435
|
+
const nextChildren: InlineNode[] = targetBlock.children.slice();
|
|
436
|
+
nextChildren[inlineIndex] = {
|
|
437
|
+
...(child as ChartPreviewNode),
|
|
438
|
+
previewMediaId,
|
|
439
|
+
};
|
|
440
|
+
const nextBlocks = blocks.slice();
|
|
441
|
+
nextBlocks[head] = {
|
|
442
|
+
...targetBlock,
|
|
443
|
+
children: nextChildren,
|
|
444
|
+
};
|
|
445
|
+
return nextBlocks;
|
|
446
|
+
}
|
|
447
|
+
case "table": {
|
|
448
|
+
const [cellPointer, ...nested] = rest;
|
|
449
|
+
if (
|
|
450
|
+
!cellPointer ||
|
|
451
|
+
typeof cellPointer === "number" ||
|
|
452
|
+
nested.length === 0
|
|
453
|
+
) {
|
|
454
|
+
return blocks as BlockNode[];
|
|
455
|
+
}
|
|
456
|
+
const row = targetBlock.rows[cellPointer.rowIndex];
|
|
457
|
+
const cell = row?.cells[cellPointer.cellIndex];
|
|
458
|
+
if (!row || !cell) return blocks as BlockNode[];
|
|
459
|
+
const nextChildren = updateChartPreviewAtPointer(
|
|
460
|
+
cell.children,
|
|
461
|
+
nested,
|
|
462
|
+
previewMediaId,
|
|
463
|
+
);
|
|
464
|
+
if (nextChildren === cell.children) return blocks as BlockNode[];
|
|
465
|
+
const nextCells = row.cells.slice();
|
|
466
|
+
nextCells[cellPointer.cellIndex] = {
|
|
467
|
+
...cell,
|
|
468
|
+
children: nextChildren,
|
|
469
|
+
};
|
|
470
|
+
const nextRows = targetBlock.rows.slice();
|
|
471
|
+
nextRows[cellPointer.rowIndex] = {
|
|
472
|
+
...row,
|
|
473
|
+
cells: nextCells,
|
|
474
|
+
};
|
|
475
|
+
const nextBlocks = blocks.slice();
|
|
476
|
+
nextBlocks[head] = {
|
|
477
|
+
...targetBlock,
|
|
478
|
+
rows: nextRows,
|
|
479
|
+
};
|
|
480
|
+
return nextBlocks;
|
|
481
|
+
}
|
|
482
|
+
case "sdt":
|
|
483
|
+
case "custom_xml": {
|
|
484
|
+
const nextChildren = updateChartPreviewAtPointer(
|
|
485
|
+
targetBlock.children,
|
|
486
|
+
rest,
|
|
487
|
+
previewMediaId,
|
|
488
|
+
);
|
|
489
|
+
if (nextChildren === targetBlock.children) return blocks as BlockNode[];
|
|
490
|
+
const nextBlocks = blocks.slice();
|
|
491
|
+
nextBlocks[head] = {
|
|
492
|
+
...targetBlock,
|
|
493
|
+
children: nextChildren,
|
|
494
|
+
};
|
|
495
|
+
return nextBlocks;
|
|
496
|
+
}
|
|
497
|
+
default:
|
|
498
|
+
return blocks as BlockNode[];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
368
502
|
/**
|
|
369
503
|
* Content-type sniff from the first bytes of the rendered preview.
|
|
370
504
|
* PNG magic is 0x89 0x50 0x4E 0x47; everything else is assumed to be
|
package/src/io/docx-session.ts
CHANGED
|
@@ -148,10 +148,10 @@ import {
|
|
|
148
148
|
parseFooterXml,
|
|
149
149
|
} from "./ooxml/parse-headers-footers.ts";
|
|
150
150
|
import { parseFootnotesXml, parseEndnotesXml } from "./ooxml/parse-footnotes.ts";
|
|
151
|
-
import { parseThemeXml } from "./ooxml/parse-theme.ts";
|
|
152
|
-
import { resolveTheme } from "./ooxml/parse-theme.ts";
|
|
151
|
+
import { materializeCanonicalTheme, parseThemeXml, resolveTheme } from "./ooxml/parse-theme.ts";
|
|
153
152
|
import { parseSettingsXml } from "./ooxml/parse-settings.ts";
|
|
154
153
|
import { parseStylesXml, type ParseStylesResult } from "./ooxml/parse-styles.ts";
|
|
154
|
+
import { parseFontTable } from "./ooxml/parse-font-table.ts";
|
|
155
155
|
import {
|
|
156
156
|
serializeHeaderXml,
|
|
157
157
|
serializeHeaderXmlWithRevisions,
|
|
@@ -224,6 +224,9 @@ const SETTINGS_RELATIONSHIP_TYPE =
|
|
|
224
224
|
const STYLES_RELATIONSHIP_TYPE =
|
|
225
225
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
226
226
|
const STYLES_PART_PATH = "/word/styles.xml";
|
|
227
|
+
const FONT_TABLE_RELATIONSHIP_TYPE =
|
|
228
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
|
|
229
|
+
const FONT_TABLE_PART_PATH = "/word/fontTable.xml";
|
|
227
230
|
const FOOTNOTES_PART_PATH = "/word/footnotes.xml";
|
|
228
231
|
const ENDNOTES_PART_PATH = "/word/endnotes.xml";
|
|
229
232
|
const SETTINGS_PART_PATH = "/word/settings.xml";
|
|
@@ -717,6 +720,13 @@ export function loadDocxEditorSession(
|
|
|
717
720
|
decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
|
|
718
721
|
)
|
|
719
722
|
: undefined;
|
|
723
|
+
const canonicalTheme =
|
|
724
|
+
parsedTheme !== undefined
|
|
725
|
+
? materializeCanonicalTheme(
|
|
726
|
+
parsedTheme,
|
|
727
|
+
parsedSettings?.clrSchemeMapping ?? {},
|
|
728
|
+
)
|
|
729
|
+
: undefined;
|
|
720
730
|
const settingsXmlForProtection =
|
|
721
731
|
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
722
732
|
? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
|
|
@@ -739,6 +749,21 @@ export function loadDocxEditorSession(
|
|
|
739
749
|
)
|
|
740
750
|
: parseStylesXml("");
|
|
741
751
|
|
|
752
|
+
// ---- Parse fontTable.xml for canonical font catalog ----
|
|
753
|
+
const fontTablePartPath = resolveDocumentRelatedPartPath(
|
|
754
|
+
sourcePackage,
|
|
755
|
+
mainDocumentPath,
|
|
756
|
+
documentPart.relationships,
|
|
757
|
+
FONT_TABLE_RELATIONSHIP_TYPE,
|
|
758
|
+
FONT_TABLE_PART_PATH,
|
|
759
|
+
);
|
|
760
|
+
const parsedFontTable =
|
|
761
|
+
fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
|
|
762
|
+
? parseFontTable(
|
|
763
|
+
decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
|
|
764
|
+
)
|
|
765
|
+
: undefined;
|
|
766
|
+
|
|
742
767
|
const subParts: SubPartsCatalog | undefined =
|
|
743
768
|
parsedHeaders.length > 0 ||
|
|
744
769
|
parsedFooters.length > 0 ||
|
|
@@ -746,6 +771,7 @@ export function loadDocxEditorSession(
|
|
|
746
771
|
parsedTheme !== undefined ||
|
|
747
772
|
normalizedDocument.finalSectionProperties !== undefined ||
|
|
748
773
|
resolvedTheme !== undefined ||
|
|
774
|
+
canonicalTheme !== undefined ||
|
|
749
775
|
parsedSettings !== undefined
|
|
750
776
|
? {
|
|
751
777
|
headers: parsedHeaders,
|
|
@@ -756,6 +782,7 @@ export function loadDocxEditorSession(
|
|
|
756
782
|
? { finalSectionProperties: normalizedDocument.finalSectionProperties }
|
|
757
783
|
: {}),
|
|
758
784
|
...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
|
|
785
|
+
...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
|
|
759
786
|
...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
|
|
760
787
|
}
|
|
761
788
|
: undefined;
|
|
@@ -775,6 +802,7 @@ export function loadDocxEditorSession(
|
|
|
775
802
|
content: normalizedDocument.content,
|
|
776
803
|
subParts,
|
|
777
804
|
parsedStyles,
|
|
805
|
+
fontTable: parsedFontTable,
|
|
778
806
|
preservation: {
|
|
779
807
|
...normalizedDocument.preservation,
|
|
780
808
|
packageParts: {
|
|
@@ -1627,6 +1655,13 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1627
1655
|
decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array()),
|
|
1628
1656
|
)
|
|
1629
1657
|
: undefined;
|
|
1658
|
+
const canonicalTheme =
|
|
1659
|
+
parsedTheme !== undefined
|
|
1660
|
+
? materializeCanonicalTheme(
|
|
1661
|
+
parsedTheme,
|
|
1662
|
+
parsedSettings?.clrSchemeMapping ?? {},
|
|
1663
|
+
)
|
|
1664
|
+
: undefined;
|
|
1630
1665
|
const settingsXmlForProtection =
|
|
1631
1666
|
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
1632
1667
|
? decodeUtf8(sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array())
|
|
@@ -1650,6 +1685,21 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1650
1685
|
: parseStylesXml("");
|
|
1651
1686
|
await scheduler.yield();
|
|
1652
1687
|
|
|
1688
|
+
// ---- Parse fontTable.xml for canonical font catalog ----
|
|
1689
|
+
const fontTablePartPath = resolveDocumentRelatedPartPath(
|
|
1690
|
+
sourcePackage,
|
|
1691
|
+
mainDocumentPath,
|
|
1692
|
+
documentPart.relationships,
|
|
1693
|
+
FONT_TABLE_RELATIONSHIP_TYPE,
|
|
1694
|
+
FONT_TABLE_PART_PATH,
|
|
1695
|
+
);
|
|
1696
|
+
const parsedFontTable =
|
|
1697
|
+
fontTablePartPath && sourcePackage.parts.has(fontTablePartPath)
|
|
1698
|
+
? parseFontTable(
|
|
1699
|
+
decodeUtf8(sourcePackage.parts.get(fontTablePartPath)?.bytes ?? new Uint8Array()),
|
|
1700
|
+
)
|
|
1701
|
+
: undefined;
|
|
1702
|
+
|
|
1653
1703
|
const subParts: SubPartsCatalog | undefined =
|
|
1654
1704
|
parsedHeaders.length > 0 ||
|
|
1655
1705
|
parsedFooters.length > 0 ||
|
|
@@ -1657,6 +1707,7 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1657
1707
|
parsedTheme !== undefined ||
|
|
1658
1708
|
normalizedDocument.finalSectionProperties !== undefined ||
|
|
1659
1709
|
resolvedTheme !== undefined ||
|
|
1710
|
+
canonicalTheme !== undefined ||
|
|
1660
1711
|
parsedSettings !== undefined
|
|
1661
1712
|
? {
|
|
1662
1713
|
headers: parsedHeaders,
|
|
@@ -1667,6 +1718,7 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1667
1718
|
? { finalSectionProperties: normalizedDocument.finalSectionProperties }
|
|
1668
1719
|
: {}),
|
|
1669
1720
|
...(resolvedTheme !== undefined ? { resolvedTheme } : {}),
|
|
1721
|
+
...(canonicalTheme !== undefined ? { canonicalTheme } : {}),
|
|
1670
1722
|
...(parsedSettings !== undefined ? { settings: parsedSettings } : {}),
|
|
1671
1723
|
}
|
|
1672
1724
|
: undefined;
|
|
@@ -1686,6 +1738,7 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1686
1738
|
content: normalizedDocument.content,
|
|
1687
1739
|
subParts,
|
|
1688
1740
|
parsedStyles,
|
|
1741
|
+
fontTable: parsedFontTable,
|
|
1689
1742
|
preservation: {
|
|
1690
1743
|
...normalizedDocument.preservation,
|
|
1691
1744
|
packageParts: {
|
|
@@ -2401,6 +2454,7 @@ function createImportedCanonicalDocument(input: {
|
|
|
2401
2454
|
content: CanonicalDocumentEnvelope["content"];
|
|
2402
2455
|
subParts?: SubPartsCatalog;
|
|
2403
2456
|
parsedStyles?: ParseStylesResult;
|
|
2457
|
+
fontTable?: CanonicalDocumentEnvelope["fontTable"];
|
|
2404
2458
|
preservation: CanonicalDocumentEnvelope["preservation"];
|
|
2405
2459
|
diagnostics: CanonicalDocumentEnvelope["diagnostics"];
|
|
2406
2460
|
review: CanonicalDocumentEnvelope["review"];
|
|
@@ -2431,6 +2485,7 @@ function createImportedCanonicalDocument(input: {
|
|
|
2431
2485
|
preservation: input.preservation,
|
|
2432
2486
|
diagnostics: input.diagnostics,
|
|
2433
2487
|
...(input.subParts !== undefined ? { subParts: input.subParts } : {}),
|
|
2488
|
+
...(input.fontTable !== undefined ? { fontTable: input.fontTable } : {}),
|
|
2434
2489
|
};
|
|
2435
2490
|
}
|
|
2436
2491
|
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
BorderSpec,
|
|
4
4
|
CustomXmlNode,
|
|
5
5
|
DocumentRootNode,
|
|
6
|
+
FootnoteProperties,
|
|
6
7
|
InlineNode,
|
|
7
8
|
MediaCatalog,
|
|
8
9
|
ParagraphNode,
|
|
@@ -530,6 +531,8 @@ function serializeTableInlineNode(
|
|
|
530
531
|
case "wordart":
|
|
531
532
|
case "vml_shape":
|
|
532
533
|
return wrapInlineRawXml(node.rawXml);
|
|
534
|
+
case "drawing_frame":
|
|
535
|
+
return serializeDrawingFrameNode(node);
|
|
533
536
|
case "hyperlink": {
|
|
534
537
|
const hyperlinkOpen = node.href.startsWith("#")
|
|
535
538
|
? `<w:hyperlink w:anchor="${escapeXmlAttribute(node.href.slice(1))}">`
|
|
@@ -962,6 +965,21 @@ function serializeInlineNode(
|
|
|
962
965
|
boundaries,
|
|
963
966
|
};
|
|
964
967
|
}
|
|
968
|
+
case "drawing_frame": {
|
|
969
|
+
// CO4 F4.1 — emit preserved rawXml from content (every DrawingFrame
|
|
970
|
+
// variant retains the original w:drawing slice on its content or is
|
|
971
|
+
// a picture that also keeps the slice on its parent). Matches the
|
|
972
|
+
// same "rawXml preservation" contract as shape/chart/smartart above.
|
|
973
|
+
const xml = serializeDrawingFrameNode(node);
|
|
974
|
+
const boundaries = new Map<number, number>();
|
|
975
|
+
boundaries.set(cursor, xmlOffset);
|
|
976
|
+
boundaries.set(cursor + 1, xmlOffset + xml.length);
|
|
977
|
+
return {
|
|
978
|
+
xml,
|
|
979
|
+
cursor: cursor + 1,
|
|
980
|
+
boundaries,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
965
983
|
case "hyperlink": {
|
|
966
984
|
const hyperlinkOpen = node.href.startsWith("#")
|
|
967
985
|
? `<w:hyperlink w:anchor="${escapeXmlAttribute(node.href.slice(1))}">`
|
|
@@ -1125,6 +1143,42 @@ function serializeInlineNode(
|
|
|
1125
1143
|
}
|
|
1126
1144
|
}
|
|
1127
1145
|
|
|
1146
|
+
/**
|
|
1147
|
+
* CO4 F4.1 — serialize a DrawingFrameNode by emitting the preserved rawXml.
|
|
1148
|
+
*
|
|
1149
|
+
* Every DrawingFrame variant carries a lossless original-XML reference:
|
|
1150
|
+
* - shape, chart_preview, smartart_preview, opaque → `content.rawXml`
|
|
1151
|
+
* - picture → the parse layer stores the containing drawing XML on the outer
|
|
1152
|
+
* DrawingFrame's context; for the MVP we reconstruct from the anchor +
|
|
1153
|
+
* blipRef if needed. In practice picture content also originates from a
|
|
1154
|
+
* larger drawingXml slice — the parent `w:drawing` substring — which we
|
|
1155
|
+
* don't retain explicitly yet. When `content.rawXml` is absent we emit a
|
|
1156
|
+
* minimal wp:inline/wp:anchor envelope preserving extent + relationship id.
|
|
1157
|
+
*
|
|
1158
|
+
* For the round-trip v1 contract: if any `rawXml` is present, round-trip is
|
|
1159
|
+
* byte-stable modulo whitespace. If the picture-content path has no rawXml,
|
|
1160
|
+
* a reconstructed minimal drawing is emitted.
|
|
1161
|
+
*/
|
|
1162
|
+
function serializeDrawingFrameNode(
|
|
1163
|
+
node: Extract<InlineNode, { type: "drawing_frame" }>,
|
|
1164
|
+
): string {
|
|
1165
|
+
const content = node.content;
|
|
1166
|
+
if ("rawXml" in content && typeof content.rawXml === "string" && content.rawXml.length > 0) {
|
|
1167
|
+
return wrapInlineRawXml(content.rawXml);
|
|
1168
|
+
}
|
|
1169
|
+
// Picture content with no rawXml — reconstruct a minimal inline image so the
|
|
1170
|
+
// round-trip doesn't silently drop the image. Use the blipRef + anchor extent.
|
|
1171
|
+
if (content.type === "picture") {
|
|
1172
|
+
const { widthEmu, heightEmu } = node.anchor.extent;
|
|
1173
|
+
const embed = escapeXmlAttribute(content.blipRef);
|
|
1174
|
+
const envelope = node.anchor.display === "floating" ? "wp:anchor" : "wp:inline";
|
|
1175
|
+
return `<w:r><w:drawing><${envelope} xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><wp:extent cx="${widthEmu}" cy="${heightEmu}"/><wp:docPr id="1" name=""/><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:nvPicPr><pic:cNvPr id="0" name=""/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${embed}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${widthEmu}" cy="${heightEmu}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></${envelope}></w:drawing></w:r>`;
|
|
1176
|
+
}
|
|
1177
|
+
// Shape/chart/smartart/opaque without rawXml — nothing to emit. Should not
|
|
1178
|
+
// happen in practice since the parser always sets rawXml.
|
|
1179
|
+
return "";
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1128
1182
|
function serializeImageNode(
|
|
1129
1183
|
node: Extract<InlineNode, { type: "image" }>,
|
|
1130
1184
|
state: SerializationState,
|
|
@@ -1482,6 +1536,15 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1482
1536
|
}
|
|
1483
1537
|
}
|
|
1484
1538
|
|
|
1539
|
+
// Per-section footnote + endnote configuration. ECMA-376 §17.6.18 places
|
|
1540
|
+
// these after the header/footer references and before <w:type>.
|
|
1541
|
+
if (props.footnotePr) {
|
|
1542
|
+
children.push(serializeFootnoteLikeProperties("w:footnotePr", props.footnotePr));
|
|
1543
|
+
}
|
|
1544
|
+
if (props.endnotePr) {
|
|
1545
|
+
children.push(serializeFootnoteLikeProperties("w:endnotePr", props.endnotePr));
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1485
1548
|
// Section type
|
|
1486
1549
|
if (props.sectionType) {
|
|
1487
1550
|
children.push(`<w:type w:val="${escapeXmlAttribute(props.sectionType)}"/>`);
|
|
@@ -1623,6 +1686,25 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1623
1686
|
return `<w:sectPr>${children.join("")}</w:sectPr>`;
|
|
1624
1687
|
}
|
|
1625
1688
|
|
|
1689
|
+
/**
|
|
1690
|
+
* Emit `<w:footnotePr>` or `<w:endnotePr>` from the typed
|
|
1691
|
+
* `FootnoteProperties` / `EndnoteProperties` shape. Child order follows
|
|
1692
|
+
* ECMA-376 §17.11.11–.18: pos → numFmt → numStart → numRestart.
|
|
1693
|
+
* Each child is emitted only when its typed field is present.
|
|
1694
|
+
*/
|
|
1695
|
+
function serializeFootnoteLikeProperties(
|
|
1696
|
+
elementName: "w:footnotePr" | "w:endnotePr",
|
|
1697
|
+
props: FootnoteProperties,
|
|
1698
|
+
): string {
|
|
1699
|
+
const parts: string[] = [];
|
|
1700
|
+
if (props.pos) parts.push(`<w:pos w:val="${escapeXmlAttribute(props.pos)}"/>`);
|
|
1701
|
+
if (props.numFmt) parts.push(`<w:numFmt w:val="${escapeXmlAttribute(props.numFmt)}"/>`);
|
|
1702
|
+
if (props.numStart !== undefined) parts.push(`<w:numStart w:val="${twip(props.numStart)}"/>`);
|
|
1703
|
+
if (props.numRestart) parts.push(`<w:numRestart w:val="${escapeXmlAttribute(props.numRestart)}"/>`);
|
|
1704
|
+
if (parts.length === 0) return `<${elementName}/>`;
|
|
1705
|
+
return `<${elementName}>${parts.join("")}</${elementName}>`;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1626
1708
|
function wrapInlineRawXml(rawXml: string): string {
|
|
1627
1709
|
const trimmed = rawXml.trimStart();
|
|
1628
1710
|
return trimmed.startsWith("<w:r") ? rawXml : `<w:r>${rawXml}</w:r>`;
|
|
@@ -67,16 +67,18 @@ function buildParagraphStyleXml(
|
|
|
67
67
|
): string {
|
|
68
68
|
const defaultAttr = style.isDefault ? ` w:default="1"` : "";
|
|
69
69
|
const nameEl = `<w:name w:val="${escXml(style.displayName)}"/>`;
|
|
70
|
+
const aliasesEl = buildAliasesXml(style.aliases);
|
|
70
71
|
const basedOnEl = style.basedOn
|
|
71
72
|
? `<w:basedOn w:val="${escXml(style.basedOn)}"/>`
|
|
72
73
|
: "";
|
|
73
74
|
const nextEl = style.nextStyle
|
|
74
75
|
? `<w:next w:val="${escXml(style.nextStyle)}"/>`
|
|
75
76
|
: "";
|
|
76
|
-
// ECMA-376 §17.7 emit order: name → basedOn → next → link → ...
|
|
77
|
+
// ECMA-376 §17.7 emit order: name → aliases → basedOn → next → link → autoRedefine → ...
|
|
77
78
|
const linkEl = style.linkedStyleId
|
|
78
79
|
? `<w:link w:val="${escXml(style.linkedStyleId)}"/>`
|
|
79
80
|
: "";
|
|
81
|
+
const autoRedefineEl = buildOnOffEl("autoRedefine", style.autoRedefine);
|
|
80
82
|
|
|
81
83
|
// Build pPr: may contain numPr (from numbering) and any canonical formatting.
|
|
82
84
|
// We reconstruct the pPr children in canonical order:
|
|
@@ -97,15 +99,37 @@ function buildParagraphStyleXml(
|
|
|
97
99
|
return (
|
|
98
100
|
`<w:style w:type="paragraph" w:styleId="${escXml(style.styleId)}"${defaultAttr}>` +
|
|
99
101
|
nameEl +
|
|
102
|
+
aliasesEl +
|
|
100
103
|
basedOnEl +
|
|
101
104
|
nextEl +
|
|
102
105
|
linkEl +
|
|
106
|
+
autoRedefineEl +
|
|
103
107
|
pPrBodyXml +
|
|
104
108
|
rPrXml +
|
|
105
109
|
`</w:style>`
|
|
106
110
|
);
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Emit an ST_OnOff element following A.3 discipline: undefined → "",
|
|
115
|
+
* true → `<w:tag/>`, false → `<w:tag w:val="false"/>`.
|
|
116
|
+
*/
|
|
117
|
+
function buildOnOffEl(tag: string, value: boolean | undefined): string {
|
|
118
|
+
if (value === undefined) return "";
|
|
119
|
+
return value ? `<w:${tag}/>` : `<w:${tag} w:val="false"/>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Emit `<w:aliases w:val="A,B"/>` — ECMA-376 §17.7.4.2. Returns empty when
|
|
124
|
+
* `aliases` is undefined or the array is empty. Commas are preserved verbatim;
|
|
125
|
+
* callers are responsible for not embedding commas inside alias names (Word
|
|
126
|
+
* does not escape them either).
|
|
127
|
+
*/
|
|
128
|
+
function buildAliasesXml(aliases: string[] | undefined): string {
|
|
129
|
+
if (!aliases || aliases.length === 0) return "";
|
|
130
|
+
return `<w:aliases w:val="${escXml(aliases.join(","))}"/>`;
|
|
131
|
+
}
|
|
132
|
+
|
|
109
133
|
function buildStyleNumPrXml(
|
|
110
134
|
numbering: NonNullable<StylesCatalog["paragraphs"][string]["numbering"]>,
|
|
111
135
|
): string {
|
|
@@ -172,15 +196,44 @@ function buildParagraphPropertiesXmlWithNumPr(
|
|
|
172
196
|
return body.length > 0 ? `<w:pPr>${body}</w:pPr>` : "";
|
|
173
197
|
}
|
|
174
198
|
|
|
199
|
+
function buildNumberingStyleXml(
|
|
200
|
+
style: NonNullable<StylesCatalog["numberingStyles"]>[string],
|
|
201
|
+
): string {
|
|
202
|
+
const defaultAttr = style.isDefault ? ` w:default="1"` : "";
|
|
203
|
+
const nameEl = `<w:name w:val="${escXml(style.displayName)}"/>`;
|
|
204
|
+
const aliasesEl = buildAliasesXml(style.aliases);
|
|
205
|
+
const basedOnEl = style.basedOn
|
|
206
|
+
? `<w:basedOn w:val="${escXml(style.basedOn)}"/>`
|
|
207
|
+
: "";
|
|
208
|
+
// Emit `<w:pPr><w:numPr><w:numId/></w:numPr></w:pPr>` when an instance ref
|
|
209
|
+
// was captured. Strip canonical "num:" prefix per serialize-numbering pattern.
|
|
210
|
+
let numPrXml = "";
|
|
211
|
+
if (style.numberingInstanceId) {
|
|
212
|
+
const rawId = style.numberingInstanceId.startsWith("num:")
|
|
213
|
+
? style.numberingInstanceId.slice(4)
|
|
214
|
+
: style.numberingInstanceId;
|
|
215
|
+
numPrXml = `<w:pPr><w:numPr><w:numId w:val="${escXml(rawId)}"/></w:numPr></w:pPr>`;
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
`<w:style w:type="numbering" w:styleId="${escXml(style.styleId)}"${defaultAttr}>` +
|
|
219
|
+
nameEl +
|
|
220
|
+
aliasesEl +
|
|
221
|
+
basedOnEl +
|
|
222
|
+
numPrXml +
|
|
223
|
+
`</w:style>`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
175
227
|
function buildCharacterStyleXml(
|
|
176
228
|
style: StylesCatalog["characters"][string],
|
|
177
229
|
): string {
|
|
178
230
|
const defaultAttr = style.isDefault ? ` w:default="1"` : "";
|
|
179
231
|
const nameEl = `<w:name w:val="${escXml(style.displayName)}"/>`;
|
|
232
|
+
const aliasesEl = buildAliasesXml(style.aliases);
|
|
180
233
|
const basedOnEl = style.basedOn
|
|
181
234
|
? `<w:basedOn w:val="${escXml(style.basedOn)}"/>`
|
|
182
235
|
: "";
|
|
183
|
-
// ECMA-376 §17.7 emit order: name → basedOn → link → ... → rPr
|
|
236
|
+
// ECMA-376 §17.7 emit order: name → aliases → basedOn → link → ... → rPr
|
|
184
237
|
const linkEl = style.linkedStyleId
|
|
185
238
|
? `<w:link w:val="${escXml(style.linkedStyleId)}"/>`
|
|
186
239
|
: "";
|
|
@@ -189,6 +242,7 @@ function buildCharacterStyleXml(
|
|
|
189
242
|
return (
|
|
190
243
|
`<w:style w:type="character" w:styleId="${escXml(style.styleId)}"${defaultAttr}>` +
|
|
191
244
|
nameEl +
|
|
245
|
+
aliasesEl +
|
|
192
246
|
basedOnEl +
|
|
193
247
|
linkEl +
|
|
194
248
|
rPrXml +
|
|
@@ -213,7 +267,11 @@ export function serializeStylesXml(catalog: StylesCatalog): string {
|
|
|
213
267
|
.map((style) => buildCharacterStyleXml(style))
|
|
214
268
|
.join("");
|
|
215
269
|
|
|
216
|
-
const
|
|
270
|
+
const numberingStyles = Object.values(catalog.numberingStyles ?? {})
|
|
271
|
+
.map((style) => buildNumberingStyleXml(style))
|
|
272
|
+
.join("");
|
|
273
|
+
|
|
274
|
+
const body = docDefaultsXml + paragraphStyles + characterStyles + numberingStyles;
|
|
217
275
|
|
|
218
276
|
return [
|
|
219
277
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|