@deckflow/deckuse 1.2.1 → 1.3.0
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.es.md +342 -0
- package/README.fr.md +342 -0
- package/README.ja.md +342 -0
- package/README.ko.md +342 -0
- package/README.md +46 -8
- package/README.pt-BR.md +342 -0
- package/README.ru.md +342 -0
- package/README.zh-CN.md +367 -0
- package/assets/default.docx +0 -0
- package/assets/default.pptx +0 -0
- package/dist/bin.js +183 -19
- package/dist/bin.js.map +1 -1
- package/dist/core/adapter.d.ts +24 -0
- package/dist/core/adapter.d.ts.map +1 -0
- package/dist/core/adapter.js +20 -0
- package/dist/core/adapter.js.map +1 -0
- package/dist/core/executor.d.ts +24 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/core/executor.js +65 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/length.d.ts +27 -0
- package/dist/core/length.d.ts.map +1 -0
- package/dist/core/length.js +63 -0
- package/dist/core/length.js.map +1 -0
- package/dist/core/measure.d.ts +24 -0
- package/dist/core/measure.d.ts.map +1 -0
- package/dist/core/measure.js +79 -0
- package/dist/core/measure.js.map +1 -0
- package/dist/core/not-implemented.d.ts +3 -0
- package/dist/core/not-implemented.d.ts.map +1 -0
- package/dist/core/not-implemented.js +12 -0
- package/dist/core/not-implemented.js.map +1 -0
- package/dist/core/schema.d.ts +3035 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +798 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/default-template.d.ts +8 -0
- package/dist/default-template.d.ts.map +1 -0
- package/dist/default-template.js +29 -0
- package/dist/default-template.js.map +1 -0
- package/dist/docx/adapter.d.ts +29 -0
- package/dist/docx/adapter.d.ts.map +1 -0
- package/dist/docx/adapter.js +442 -0
- package/dist/docx/adapter.js.map +1 -0
- package/dist/docx/addressing.d.ts +18 -0
- package/dist/docx/addressing.d.ts.map +1 -0
- package/dist/docx/addressing.js +111 -0
- package/dist/docx/addressing.js.map +1 -0
- package/dist/docx/blank.d.ts +4 -0
- package/dist/docx/blank.d.ts.map +1 -0
- package/dist/docx/blank.js +75 -0
- package/dist/docx/blank.js.map +1 -0
- package/dist/docx/index-sync.d.ts +7 -0
- package/dist/docx/index-sync.d.ts.map +1 -0
- package/dist/docx/index-sync.js +24 -0
- package/dist/docx/index-sync.js.map +1 -0
- package/dist/docx/index.d.ts +5 -0
- package/dist/docx/index.d.ts.map +1 -0
- package/dist/docx/index.js +4 -0
- package/dist/docx/index.js.map +1 -0
- package/dist/docx/indexer.d.ts +41 -0
- package/dist/docx/indexer.d.ts.map +1 -0
- package/dist/docx/indexer.js +353 -0
- package/dist/docx/indexer.js.map +1 -0
- package/dist/docx/mutations.d.ts +5 -0
- package/dist/docx/mutations.d.ts.map +1 -0
- package/dist/docx/mutations.js +311 -0
- package/dist/docx/mutations.js.map +1 -0
- package/dist/docx/paragraphs.d.ts +26 -0
- package/dist/docx/paragraphs.d.ts.map +1 -0
- package/dist/docx/paragraphs.js +314 -0
- package/dist/docx/paragraphs.js.map +1 -0
- package/dist/docx/properties.d.ts +23 -0
- package/dist/docx/properties.d.ts.map +1 -0
- package/dist/docx/properties.js +414 -0
- package/dist/docx/properties.js.map +1 -0
- package/dist/docx/tables.d.ts +4 -0
- package/dist/docx/tables.d.ts.map +1 -0
- package/dist/docx/tables.js +64 -0
- package/dist/docx/tables.js.map +1 -0
- package/dist/docx/text-spans.d.ts +25 -0
- package/dist/docx/text-spans.d.ts.map +1 -0
- package/dist/docx/text-spans.js +142 -0
- package/dist/docx/text-spans.js.map +1 -0
- package/dist/docx/types.d.ts +27 -0
- package/dist/docx/types.d.ts.map +1 -0
- package/dist/docx/types.js +2 -0
- package/dist/docx/types.js.map +1 -0
- package/dist/docx/workspace.d.ts +20 -0
- package/dist/docx/workspace.d.ts.map +1 -0
- package/dist/docx/workspace.js +145 -0
- package/dist/docx/workspace.js.map +1 -0
- package/dist/docx/xml.d.ts +18 -0
- package/dist/docx/xml.d.ts.map +1 -0
- package/dist/docx/xml.js +87 -0
- package/dist/docx/xml.js.map +1 -0
- package/dist/edition-config/index.d.ts +25 -0
- package/dist/edition-config/index.d.ts.map +1 -0
- package/dist/edition-config/index.js +22 -0
- package/dist/edition-config/index.js.map +1 -0
- package/dist/edition.d.ts +2 -2
- package/dist/edition.d.ts.map +1 -1
- package/dist/edition.js +1 -1
- package/dist/edition.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +124 -32
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/key/index.d.ts +2 -0
- package/dist/key/index.d.ts.map +1 -0
- package/dist/key/index.js +3 -0
- package/dist/key/index.js.map +1 -0
- package/dist/monitor-daemon.d.ts +21 -8
- package/dist/monitor-daemon.d.ts.map +1 -1
- package/dist/monitor-daemon.js +189 -51
- package/dist/monitor-daemon.js.map +1 -1
- package/dist/monitor-registry.d.ts +34 -0
- package/dist/monitor-registry.d.ts.map +1 -0
- package/dist/monitor-registry.js +191 -0
- package/dist/monitor-registry.js.map +1 -0
- package/dist/monitor.js +1 -1
- package/dist/monitor.js.map +1 -1
- package/dist/numbers/index.d.ts +2 -0
- package/dist/numbers/index.d.ts.map +1 -0
- package/dist/numbers/index.js +3 -0
- package/dist/numbers/index.js.map +1 -0
- package/dist/opc/index.d.ts +90 -0
- package/dist/opc/index.d.ts.map +1 -0
- package/dist/opc/index.js +569 -0
- package/dist/opc/index.js.map +1 -0
- package/dist/pptx/adapter.d.ts +167 -0
- package/dist/pptx/adapter.d.ts.map +1 -0
- package/dist/pptx/adapter.js +807 -0
- package/dist/pptx/adapter.js.map +1 -0
- package/dist/pptx/addressing.d.ts +45 -0
- package/dist/pptx/addressing.d.ts.map +1 -0
- package/dist/pptx/addressing.js +428 -0
- package/dist/pptx/addressing.js.map +1 -0
- package/dist/pptx/align.d.ts +24 -0
- package/dist/pptx/align.d.ts.map +1 -0
- package/dist/pptx/align.js +112 -0
- package/dist/pptx/align.js.map +1 -0
- package/dist/pptx/chart-classify.d.ts +10 -0
- package/dist/pptx/chart-classify.d.ts.map +1 -0
- package/dist/pptx/chart-classify.js +58 -0
- package/dist/pptx/chart-classify.js.map +1 -0
- package/dist/pptx/chart.d.ts +42 -0
- package/dist/pptx/chart.d.ts.map +1 -0
- package/dist/pptx/chart.js +614 -0
- package/dist/pptx/chart.js.map +1 -0
- package/dist/pptx/edition-extension.d.ts +25 -0
- package/dist/pptx/edition-extension.d.ts.map +1 -0
- package/dist/pptx/edition-extension.js +10 -0
- package/dist/pptx/edition-extension.js.map +1 -0
- package/dist/pptx/edition.d.ts +20 -0
- package/dist/pptx/edition.d.ts.map +1 -0
- package/dist/pptx/edition.js +69 -0
- package/dist/pptx/edition.js.map +1 -0
- package/dist/pptx/elements.d.ts +12 -0
- package/dist/pptx/elements.d.ts.map +1 -0
- package/dist/pptx/elements.js +224 -0
- package/dist/pptx/elements.js.map +1 -0
- package/dist/pptx/hyperlink.d.ts +7 -0
- package/dist/pptx/hyperlink.d.ts.map +1 -0
- package/dist/pptx/hyperlink.js +81 -0
- package/dist/pptx/hyperlink.js.map +1 -0
- package/dist/pptx/index-sync.d.ts +7 -0
- package/dist/pptx/index-sync.d.ts.map +1 -0
- package/dist/pptx/index-sync.js +24 -0
- package/dist/pptx/index-sync.js.map +1 -0
- package/dist/pptx/index.d.ts +10 -0
- package/dist/pptx/index.d.ts.map +1 -0
- package/dist/pptx/index.js +7 -0
- package/dist/pptx/index.js.map +1 -0
- package/dist/pptx/indexer.d.ts +19 -0
- package/dist/pptx/indexer.d.ts.map +1 -0
- package/dist/pptx/indexer.js +319 -0
- package/dist/pptx/indexer.js.map +1 -0
- package/dist/pptx/layout-ref.d.ts +41 -0
- package/dist/pptx/layout-ref.d.ts.map +1 -0
- package/dist/pptx/layout-ref.js +145 -0
- package/dist/pptx/layout-ref.js.map +1 -0
- package/dist/pptx/media.d.ts +15 -0
- package/dist/pptx/media.d.ts.map +1 -0
- package/dist/pptx/media.js +145 -0
- package/dist/pptx/media.js.map +1 -0
- package/dist/pptx/mutations.d.ts +11 -0
- package/dist/pptx/mutations.d.ts.map +1 -0
- package/dist/pptx/mutations.js +1111 -0
- package/dist/pptx/mutations.js.map +1 -0
- package/dist/pptx/picture.d.ts +25 -0
- package/dist/pptx/picture.d.ts.map +1 -0
- package/dist/pptx/picture.js +118 -0
- package/dist/pptx/picture.js.map +1 -0
- package/dist/pptx/placeholder-role.d.ts +19 -0
- package/dist/pptx/placeholder-role.d.ts.map +1 -0
- package/dist/pptx/placeholder-role.js +71 -0
- package/dist/pptx/placeholder-role.js.map +1 -0
- package/dist/pptx/properties.d.ts +13 -0
- package/dist/pptx/properties.d.ts.map +1 -0
- package/dist/pptx/properties.js +587 -0
- package/dist/pptx/properties.js.map +1 -0
- package/dist/pptx/resolve-properties.d.ts +17 -0
- package/dist/pptx/resolve-properties.d.ts.map +1 -0
- package/dist/pptx/resolve-properties.js +571 -0
- package/dist/pptx/resolve-properties.js.map +1 -0
- package/dist/pptx/slide-size.d.ts +9 -0
- package/dist/pptx/slide-size.d.ts.map +1 -0
- package/dist/pptx/slide-size.js +29 -0
- package/dist/pptx/slide-size.js.map +1 -0
- package/dist/pptx/slides.d.ts +15 -0
- package/dist/pptx/slides.d.ts.map +1 -0
- package/dist/pptx/slides.js +236 -0
- package/dist/pptx/slides.js.map +1 -0
- package/dist/pptx/table-measure.d.ts +43 -0
- package/dist/pptx/table-measure.d.ts.map +1 -0
- package/dist/pptx/table-measure.js +70 -0
- package/dist/pptx/table-measure.js.map +1 -0
- package/dist/pptx/table.d.ts +23 -0
- package/dist/pptx/table.d.ts.map +1 -0
- package/dist/pptx/table.js +529 -0
- package/dist/pptx/table.js.map +1 -0
- package/dist/pptx/types.d.ts +27 -0
- package/dist/pptx/types.d.ts.map +1 -0
- package/dist/pptx/types.js +2 -0
- package/dist/pptx/types.js.map +1 -0
- package/dist/pptx/workspace.d.ts +23 -0
- package/dist/pptx/workspace.d.ts.map +1 -0
- package/dist/pptx/workspace.js +148 -0
- package/dist/pptx/workspace.js.map +1 -0
- package/dist/pptx/xml.d.ts +67 -0
- package/dist/pptx/xml.d.ts.map +1 -0
- package/dist/pptx/xml.js +355 -0
- package/dist/pptx/xml.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +14 -2
- package/dist/render.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace/git.d.ts +7 -0
- package/dist/workspace/git.d.ts.map +1 -0
- package/dist/workspace/git.js +82 -0
- package/dist/workspace/git.js.map +1 -0
- package/dist/workspace/index.d.ts +7 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +7 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/lock.d.ts +3 -0
- package/dist/workspace/lock.d.ts.map +1 -0
- package/dist/workspace/lock.js +35 -0
- package/dist/workspace/lock.js.map +1 -0
- package/dist/workspace/metadata.d.ts +4 -0
- package/dist/workspace/metadata.d.ts.map +1 -0
- package/dist/workspace/metadata.js +21 -0
- package/dist/workspace/metadata.js.map +1 -0
- package/dist/workspace/operations.d.ts +14 -0
- package/dist/workspace/operations.d.ts.map +1 -0
- package/dist/workspace/operations.js +35 -0
- package/dist/workspace/operations.js.map +1 -0
- package/dist/workspace/paths.d.ts +11 -0
- package/dist/workspace/paths.d.ts.map +1 -0
- package/dist/workspace/paths.js +12 -0
- package/dist/workspace/paths.js.map +1 -0
- package/dist/workspace/revision.d.ts +6 -0
- package/dist/workspace/revision.d.ts.map +1 -0
- package/dist/workspace/revision.js +15 -0
- package/dist/workspace/revision.js.map +1 -0
- package/dist/xlsx/index.d.ts +2 -0
- package/dist/xlsx/index.d.ts.map +1 -0
- package/dist/xlsx/index.js +3 -0
- package/dist/xlsx/index.js.map +1 -0
- package/package.json +48 -27
- package/schema/command.schema.json +2399 -0
- package/dist/.tsbuildinfo +0 -1
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
import { err, ok, parseLength, } from '../core/index.js';
|
|
2
|
+
import { resolveTarget, resolveToRef, cNvPrIdOf } from './addressing.js';
|
|
3
|
+
import { computeAlignUpdates, readBBox, writeBBox } from './align.js';
|
|
4
|
+
import { assertWritable } from './edition.js';
|
|
5
|
+
import { addElement, duplicateElement, normalizeTableRows, updateChart } from './elements.js';
|
|
6
|
+
import { findIndexed, matchesSelector, mergeSlides, slidesForItem } from './indexer.js';
|
|
7
|
+
import { detachPictureAndCleanup, loadPictureBytes, replacePictureMedia } from './picture.js';
|
|
8
|
+
import { detachMediaAndCleanup } from './media.js';
|
|
9
|
+
import { applyShapeProperties, assertChartProperties } from './properties.js';
|
|
10
|
+
import { mapDottedProperties } from './resolve-properties.js';
|
|
11
|
+
import { addSlide, duplicateSlide, ensureNotes, removeSlide, setSlideLayout } from './slides.js';
|
|
12
|
+
import { resolveLayoutRef } from './layout-ref.js';
|
|
13
|
+
import { lengthContextFor } from './slide-size.js';
|
|
14
|
+
import { normalizePlaceholderRole } from './placeholder-role.js';
|
|
15
|
+
import { applyTableCellProperties, applyTableLayout, applyTableProperties } from './table.js';
|
|
16
|
+
import { measureTableLayout, tableHeightMayClipDiagnostic } from './table-measure.js';
|
|
17
|
+
import { REL, NS, attr, children, cNvPr, descendants, first, root, setNodeText, setNodeTextBlocks, } from './xml.js';
|
|
18
|
+
const mapTextRun = (run) => {
|
|
19
|
+
const styled = { text: run.text };
|
|
20
|
+
if (run.fontSize !== undefined)
|
|
21
|
+
styled.fontSize = run.fontSize;
|
|
22
|
+
if (run.fontFamily !== undefined)
|
|
23
|
+
styled.fontFamily = run.fontFamily;
|
|
24
|
+
if (run.textColor !== undefined)
|
|
25
|
+
styled.textColor = run.textColor;
|
|
26
|
+
if (run.bold !== undefined)
|
|
27
|
+
styled.bold = run.bold;
|
|
28
|
+
if (run.italic !== undefined)
|
|
29
|
+
styled.italic = run.italic;
|
|
30
|
+
if (run.underline !== undefined)
|
|
31
|
+
styled.underline = run.underline;
|
|
32
|
+
return styled;
|
|
33
|
+
};
|
|
34
|
+
const mapTextBlock = (block) => {
|
|
35
|
+
const styled = {};
|
|
36
|
+
const runs = block['runs'];
|
|
37
|
+
if (Array.isArray(runs)) {
|
|
38
|
+
styled.runs = runs.map((run) => mapTextRun(run));
|
|
39
|
+
}
|
|
40
|
+
else if (typeof block['text'] === 'string') {
|
|
41
|
+
styled.text = block['text'];
|
|
42
|
+
}
|
|
43
|
+
if (typeof block['fontSize'] === 'number')
|
|
44
|
+
styled.fontSize = block['fontSize'];
|
|
45
|
+
if (typeof block['fontFamily'] === 'string')
|
|
46
|
+
styled.fontFamily = block['fontFamily'];
|
|
47
|
+
if (typeof block['textColor'] === 'string')
|
|
48
|
+
styled.textColor = block['textColor'];
|
|
49
|
+
if (typeof block['bold'] === 'boolean')
|
|
50
|
+
styled.bold = block['bold'];
|
|
51
|
+
if (typeof block['italic'] === 'boolean')
|
|
52
|
+
styled.italic = block['italic'];
|
|
53
|
+
if (typeof block['underline'] === 'boolean')
|
|
54
|
+
styled.underline = block['underline'];
|
|
55
|
+
if (typeof block['align'] === 'string')
|
|
56
|
+
styled.align = block['align'];
|
|
57
|
+
return styled;
|
|
58
|
+
};
|
|
59
|
+
const SHAPE_LOCAL_NAMES = new Set(['sp', 'pic', 'graphicFrame', 'cxnSp', 'grpSp']);
|
|
60
|
+
const directChild = (node, localName) => children(node).find((child) => child.localName === localName);
|
|
61
|
+
const resolveLengthField = (archive, value, axis) => {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
return parseLength(value, lengthContextFor(archive, axis));
|
|
65
|
+
};
|
|
66
|
+
const normalizeTransformFields = (archive, fields) => {
|
|
67
|
+
const out = { ...fields };
|
|
68
|
+
for (const key of ['x', 'width']) {
|
|
69
|
+
if (out[key] !== undefined)
|
|
70
|
+
out[key] = resolveLengthField(archive, out[key], 'x');
|
|
71
|
+
}
|
|
72
|
+
for (const key of ['y', 'height']) {
|
|
73
|
+
if (out[key] !== undefined && out[key] !== 'auto')
|
|
74
|
+
out[key] = resolveLengthField(archive, out[key], 'y');
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
};
|
|
78
|
+
export const shapeByCNvPrId = (doc, id) => descendants(doc).find((node) => node.localName != null &&
|
|
79
|
+
SHAPE_LOCAL_NAMES.has(node.localName) &&
|
|
80
|
+
attr(cNvPr(node), 'id') === id);
|
|
81
|
+
export const nodeFor = (doc, item) => {
|
|
82
|
+
if (['slide', 'notes', 'master', 'layout', 'theme'].includes(item.kind))
|
|
83
|
+
return root(doc);
|
|
84
|
+
if (item.kind === 'tableCell') {
|
|
85
|
+
const rawTableId = item.location?.['tableId'], tableId = typeof rawTableId === 'string' ? rawTableId : '';
|
|
86
|
+
// elementId is `${slideId}:${ancestorPath}` — strip slide prefix, then take the leaf cNvPr id.
|
|
87
|
+
const afterSlide = tableId.includes(':') ? tableId.slice(tableId.indexOf(':') + 1) : tableId;
|
|
88
|
+
const tableTail = afterSlide.split('.').at(-1) ?? afterSlide;
|
|
89
|
+
if (!tableTail)
|
|
90
|
+
return undefined;
|
|
91
|
+
const table = shapeByCNvPrId(doc, tableTail);
|
|
92
|
+
if (!table)
|
|
93
|
+
return;
|
|
94
|
+
const row = descendants(table, 'tr')[Number(item.location?.['row'])];
|
|
95
|
+
return row ? descendants(row, 'tc')[Number(item.location?.['column'])] : undefined;
|
|
96
|
+
}
|
|
97
|
+
const id = cNvPrIdOf(item);
|
|
98
|
+
return id ? shapeByCNvPrId(doc, id) : undefined;
|
|
99
|
+
};
|
|
100
|
+
const placeholderOf = (node) => {
|
|
101
|
+
const ph = first(node, 'ph');
|
|
102
|
+
if (!ph)
|
|
103
|
+
return undefined;
|
|
104
|
+
const type = attr(ph, 'type') ?? 'body';
|
|
105
|
+
const idx = attr(ph, 'idx');
|
|
106
|
+
return { type, ...(idx !== undefined ? { idx } : {}) };
|
|
107
|
+
};
|
|
108
|
+
const findPlaceholderXfrm = (doc, type, idx) => {
|
|
109
|
+
for (const shape of descendants(doc)) {
|
|
110
|
+
if (!shape.localName || !SHAPE_LOCAL_NAMES.has(shape.localName))
|
|
111
|
+
continue;
|
|
112
|
+
const ph = placeholderOf(shape);
|
|
113
|
+
if (!ph)
|
|
114
|
+
continue;
|
|
115
|
+
if (idx !== undefined && ph.idx === idx) {
|
|
116
|
+
const xfrm = first(shape, 'xfrm');
|
|
117
|
+
if (xfrm)
|
|
118
|
+
return xfrm;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (ph.type === type && (idx === undefined || ph.idx === undefined)) {
|
|
122
|
+
const xfrm = first(shape, 'xfrm');
|
|
123
|
+
if (xfrm)
|
|
124
|
+
return xfrm;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
};
|
|
129
|
+
/** Layout/master xfrm for placeholders that omit a local transform. */
|
|
130
|
+
const inheritedXfrm = (archive, slidePart, node) => {
|
|
131
|
+
const ph = placeholderOf(node);
|
|
132
|
+
if (!ph)
|
|
133
|
+
return undefined;
|
|
134
|
+
const layoutPart = archive
|
|
135
|
+
.getRelationships(slidePart)
|
|
136
|
+
.find((rel) => rel.type === REL.layout)?.resolvedTarget;
|
|
137
|
+
if (!layoutPart || !archive.getPart(layoutPart))
|
|
138
|
+
return undefined;
|
|
139
|
+
const fromLayout = findPlaceholderXfrm(archive.readXml(layoutPart), ph.type, ph.idx);
|
|
140
|
+
if (fromLayout)
|
|
141
|
+
return fromLayout;
|
|
142
|
+
const masterPart = archive
|
|
143
|
+
.getRelationships(layoutPart)
|
|
144
|
+
.find((rel) => rel.type === REL.slideMaster)?.resolvedTarget;
|
|
145
|
+
if (!masterPart || !archive.getPart(masterPart))
|
|
146
|
+
return undefined;
|
|
147
|
+
return findPlaceholderXfrm(archive.readXml(masterPart), ph.type, ph.idx);
|
|
148
|
+
};
|
|
149
|
+
const copyXfrmInto = (doc, seed, ns, qname) => {
|
|
150
|
+
const x = doc.createElementNS(ns, qname);
|
|
151
|
+
for (const name of ['rot', 'flipH', 'flipV']) {
|
|
152
|
+
const value = seed.getAttribute(name);
|
|
153
|
+
if (value != null)
|
|
154
|
+
x.setAttribute(name, value);
|
|
155
|
+
}
|
|
156
|
+
for (const [local, q] of [
|
|
157
|
+
['off', 'a:off'],
|
|
158
|
+
['ext', 'a:ext'],
|
|
159
|
+
['chOff', 'a:chOff'],
|
|
160
|
+
['chExt', 'a:chExt'],
|
|
161
|
+
]) {
|
|
162
|
+
const src = directChild(seed, local) ?? first(seed, local);
|
|
163
|
+
if (!src)
|
|
164
|
+
continue;
|
|
165
|
+
const child = doc.createElementNS(NS.a, q);
|
|
166
|
+
for (const name of ['x', 'y', 'cx', 'cy']) {
|
|
167
|
+
const value = src.getAttribute(name);
|
|
168
|
+
if (value != null)
|
|
169
|
+
child.setAttribute(name, value);
|
|
170
|
+
}
|
|
171
|
+
x.appendChild(child);
|
|
172
|
+
}
|
|
173
|
+
return x;
|
|
174
|
+
};
|
|
175
|
+
const ensureSpPrForTransform = (node) => {
|
|
176
|
+
const existing = directChild(node, 'spPr');
|
|
177
|
+
if (existing)
|
|
178
|
+
return existing;
|
|
179
|
+
const doc = node.ownerDocument;
|
|
180
|
+
if (!doc)
|
|
181
|
+
throw new Error('Element has no document');
|
|
182
|
+
const prefix = node.prefix ? `${node.prefix}:` : 'p:';
|
|
183
|
+
const spPr = doc.createElementNS(NS.p, `${prefix}spPr`);
|
|
184
|
+
const after = children(node).find((child) => child.localName?.startsWith('nv'));
|
|
185
|
+
if (after?.nextSibling)
|
|
186
|
+
node.insertBefore(spPr, after.nextSibling);
|
|
187
|
+
else if (after)
|
|
188
|
+
node.appendChild(spPr);
|
|
189
|
+
else if (node.firstChild)
|
|
190
|
+
node.insertBefore(spPr, node.firstChild);
|
|
191
|
+
else
|
|
192
|
+
node.appendChild(spPr);
|
|
193
|
+
return spPr;
|
|
194
|
+
};
|
|
195
|
+
const ensureGrpSpPr = (node) => {
|
|
196
|
+
const existing = directChild(node, 'grpSpPr');
|
|
197
|
+
if (existing)
|
|
198
|
+
return existing;
|
|
199
|
+
const doc = node.ownerDocument;
|
|
200
|
+
if (!doc)
|
|
201
|
+
throw new Error('Element has no document');
|
|
202
|
+
const prefix = node.prefix ? `${node.prefix}:` : 'p:';
|
|
203
|
+
const grpSpPr = doc.createElementNS(NS.p, `${prefix}grpSpPr`);
|
|
204
|
+
const after = children(node).find((child) => child.localName?.startsWith('nv'));
|
|
205
|
+
if (after?.nextSibling)
|
|
206
|
+
node.insertBefore(grpSpPr, after.nextSibling);
|
|
207
|
+
else if (after)
|
|
208
|
+
node.appendChild(grpSpPr);
|
|
209
|
+
else if (node.firstChild)
|
|
210
|
+
node.insertBefore(grpSpPr, node.firstChild);
|
|
211
|
+
else
|
|
212
|
+
node.appendChild(grpSpPr);
|
|
213
|
+
return grpSpPr;
|
|
214
|
+
};
|
|
215
|
+
const ensureXfrm = (node, archive, partUri) => {
|
|
216
|
+
const existing = (() => {
|
|
217
|
+
if (node.localName === 'graphicFrame')
|
|
218
|
+
return directChild(node, 'xfrm');
|
|
219
|
+
if (node.localName === 'grpSp') {
|
|
220
|
+
const grpSpPr = directChild(node, 'grpSpPr');
|
|
221
|
+
return grpSpPr ? directChild(grpSpPr, 'xfrm') : undefined;
|
|
222
|
+
}
|
|
223
|
+
const spPr = directChild(node, 'spPr');
|
|
224
|
+
return spPr ? directChild(spPr, 'xfrm') : undefined;
|
|
225
|
+
})();
|
|
226
|
+
if (existing)
|
|
227
|
+
return existing;
|
|
228
|
+
const doc = node.ownerDocument;
|
|
229
|
+
if (!doc)
|
|
230
|
+
throw new Error('Element has no document');
|
|
231
|
+
const seed = archive && partUri ? inheritedXfrm(archive, partUri, node) : undefined;
|
|
232
|
+
const usePresentationXfrm = node.localName === 'graphicFrame';
|
|
233
|
+
const ns = usePresentationXfrm ? NS.p : NS.a;
|
|
234
|
+
const qname = usePresentationXfrm ? 'p:xfrm' : 'a:xfrm';
|
|
235
|
+
const x = seed ? copyXfrmInto(doc, seed, ns, qname) : doc.createElementNS(ns, qname);
|
|
236
|
+
if (node.localName === 'graphicFrame') {
|
|
237
|
+
const after = children(node).find((child) => child.localName?.startsWith('nv'));
|
|
238
|
+
if (after?.nextSibling)
|
|
239
|
+
node.insertBefore(x, after.nextSibling);
|
|
240
|
+
else if (after)
|
|
241
|
+
node.appendChild(x);
|
|
242
|
+
else if (node.firstChild)
|
|
243
|
+
node.insertBefore(x, node.firstChild);
|
|
244
|
+
else
|
|
245
|
+
node.appendChild(x);
|
|
246
|
+
return x;
|
|
247
|
+
}
|
|
248
|
+
const container = node.localName === 'grpSp' ? ensureGrpSpPr(node) : ensureSpPrForTransform(node);
|
|
249
|
+
if (container.firstChild)
|
|
250
|
+
container.insertBefore(x, container.firstChild);
|
|
251
|
+
else
|
|
252
|
+
container.appendChild(x);
|
|
253
|
+
return x;
|
|
254
|
+
};
|
|
255
|
+
const ensureChild = (parent, localName, qname, beforeLocalNames) => {
|
|
256
|
+
const existing = directChild(parent, localName);
|
|
257
|
+
if (existing)
|
|
258
|
+
return existing;
|
|
259
|
+
const doc = parent.ownerDocument;
|
|
260
|
+
if (!doc)
|
|
261
|
+
throw new Error('Element has no document');
|
|
262
|
+
const child = doc.createElementNS(NS.a, qname);
|
|
263
|
+
const before = children(parent).find((item) => item.localName != null && beforeLocalNames.includes(item.localName));
|
|
264
|
+
if (before)
|
|
265
|
+
parent.insertBefore(child, before);
|
|
266
|
+
else
|
|
267
|
+
parent.appendChild(child);
|
|
268
|
+
return child;
|
|
269
|
+
};
|
|
270
|
+
const transform = (node, t, opts) => {
|
|
271
|
+
const x = ensureXfrm(node, opts?.archive, opts?.partUri);
|
|
272
|
+
const needsOff = typeof t['x'] === 'number' || typeof t['y'] === 'number';
|
|
273
|
+
const needsExt = typeof t['width'] === 'number' || typeof t['height'] === 'number';
|
|
274
|
+
const off = needsOff
|
|
275
|
+
? ensureChild(x, 'off', 'a:off', ['ext', 'chOff', 'chExt'])
|
|
276
|
+
: (directChild(x, 'off') ?? first(x, 'off'));
|
|
277
|
+
const ext = needsExt
|
|
278
|
+
? ensureChild(x, 'ext', 'a:ext', ['chOff', 'chExt'])
|
|
279
|
+
: (directChild(x, 'ext') ?? first(x, 'ext'));
|
|
280
|
+
if (needsOff && off) {
|
|
281
|
+
if (typeof t['x'] === 'number')
|
|
282
|
+
off.setAttribute('x', String(Math.round(t['x'])));
|
|
283
|
+
else if (!off.hasAttribute('x'))
|
|
284
|
+
off.setAttribute('x', '0');
|
|
285
|
+
if (typeof t['y'] === 'number')
|
|
286
|
+
off.setAttribute('y', String(Math.round(t['y'])));
|
|
287
|
+
else if (!off.hasAttribute('y'))
|
|
288
|
+
off.setAttribute('y', '0');
|
|
289
|
+
}
|
|
290
|
+
if (needsExt && ext) {
|
|
291
|
+
if (typeof t['width'] === 'number')
|
|
292
|
+
ext.setAttribute('cx', String(Math.round(t['width'])));
|
|
293
|
+
else if (!ext.hasAttribute('cx'))
|
|
294
|
+
ext.setAttribute('cx', '0');
|
|
295
|
+
if (typeof t['height'] === 'number')
|
|
296
|
+
ext.setAttribute('cy', String(Math.round(t['height'])));
|
|
297
|
+
else if (!ext.hasAttribute('cy'))
|
|
298
|
+
ext.setAttribute('cy', '0');
|
|
299
|
+
}
|
|
300
|
+
if (typeof t['rotation'] === 'number')
|
|
301
|
+
x.setAttribute('rot', String(Math.round(t['rotation'] * 60000)));
|
|
302
|
+
if (typeof t['flipHorizontal'] === 'boolean' || typeof t['flipX'] === 'boolean') {
|
|
303
|
+
const flip = Boolean(t['flipHorizontal'] ?? t['flipX']);
|
|
304
|
+
if (flip)
|
|
305
|
+
x.setAttribute('flipH', '1');
|
|
306
|
+
else
|
|
307
|
+
x.removeAttribute('flipH');
|
|
308
|
+
}
|
|
309
|
+
if (typeof t['flipVertical'] === 'boolean' || typeof t['flipY'] === 'boolean') {
|
|
310
|
+
const flip = Boolean(t['flipVertical'] ?? t['flipY']);
|
|
311
|
+
if (flip)
|
|
312
|
+
x.setAttribute('flipV', '1');
|
|
313
|
+
else
|
|
314
|
+
x.removeAttribute('flipV');
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const indexSlidePartForNotes = (archive, item) => {
|
|
318
|
+
if (!item.slideId)
|
|
319
|
+
return undefined;
|
|
320
|
+
const presentation = archive.readXml('/ppt/presentation.xml');
|
|
321
|
+
const rels = archive.getRelationships('/ppt/presentation.xml');
|
|
322
|
+
for (const sld of descendants(presentation, 'sldId')) {
|
|
323
|
+
if (sld.namespaceURI && sld.namespaceURI !== NS.p)
|
|
324
|
+
continue;
|
|
325
|
+
const id = attr(sld, 'id');
|
|
326
|
+
if (id !== item.slideId)
|
|
327
|
+
continue;
|
|
328
|
+
const rid = sld.getAttributeNS(NS.r, 'id') ?? attr(sld, 'r:id');
|
|
329
|
+
const rel = rels.find((r) => r.id === rid);
|
|
330
|
+
return rel?.resolvedTarget;
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
};
|
|
334
|
+
const materializeNotes = (archive, item) => {
|
|
335
|
+
if (item.kind !== 'notes')
|
|
336
|
+
return ok(item);
|
|
337
|
+
if (item.partUri && archive.getPart(item.partUri))
|
|
338
|
+
return ok(item);
|
|
339
|
+
const slidePart = typeof item.location?.['slidePart'] === 'string'
|
|
340
|
+
? item.location['slidePart']
|
|
341
|
+
: indexSlidePartForNotes(archive, item);
|
|
342
|
+
if (!slidePart)
|
|
343
|
+
return err('ELEMENT_NOT_FOUND', `Cannot locate slide for notes ${item.ref.elementId ?? ''}`);
|
|
344
|
+
const partUri = ensureNotes(archive, slidePart);
|
|
345
|
+
return ok({
|
|
346
|
+
...item,
|
|
347
|
+
partUri,
|
|
348
|
+
ref: { ...item.ref, path: partUri },
|
|
349
|
+
location: { ...item.location, partUri, slidePart, region: 'speakerNotes' },
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
const writeText = (archive, item, text, diagnostics) => {
|
|
353
|
+
const gated = assertWritable(item, archive);
|
|
354
|
+
if (!gated.ok)
|
|
355
|
+
return gated;
|
|
356
|
+
if (item.kind === 'chart' && typeof item.payload?.['chartPart'] === 'string') {
|
|
357
|
+
const result = updateChart(archive, item.payload['chartPart'], { title: text });
|
|
358
|
+
if (result.workbook)
|
|
359
|
+
diagnostics.push({
|
|
360
|
+
severity: 'warning',
|
|
361
|
+
code: 'EMBEDDED_WORKBOOK_NOT_SYNCHRONIZED',
|
|
362
|
+
message: 'Chart cache changed; embedded workbook was not modified',
|
|
363
|
+
});
|
|
364
|
+
return ok(undefined, diagnostics);
|
|
365
|
+
}
|
|
366
|
+
const notesItem = materializeNotes(archive, item);
|
|
367
|
+
if (!notesItem.ok)
|
|
368
|
+
return notesItem;
|
|
369
|
+
const target = notesItem.value;
|
|
370
|
+
const doc = archive.readXml(target.partUri), node = nodeFor(doc, target);
|
|
371
|
+
if (!node)
|
|
372
|
+
return err('ELEMENT_NOT_FOUND', `Element XML node was not found: ${target.ref.elementId ?? target.ref.path ?? ''}`);
|
|
373
|
+
setNodeText(node, text);
|
|
374
|
+
archive.writeXml(target.partUri, doc);
|
|
375
|
+
return ok(undefined, diagnostics);
|
|
376
|
+
};
|
|
377
|
+
const isDescendantOf = (item, ancestorId) => {
|
|
378
|
+
if (item.parentId === ancestorId)
|
|
379
|
+
return true;
|
|
380
|
+
const elementId = item.ref.elementId;
|
|
381
|
+
return Boolean(elementId?.startsWith(`${ancestorId}.`));
|
|
382
|
+
};
|
|
383
|
+
/** Without a selector, skip ancestor elements whose text aggregates descendant matches. */
|
|
384
|
+
const narrowReplaceTextCandidates = (candidates) => {
|
|
385
|
+
if (candidates.length <= 1)
|
|
386
|
+
return candidates;
|
|
387
|
+
return candidates.filter((item) => {
|
|
388
|
+
const id = item.ref.elementId;
|
|
389
|
+
if (!id)
|
|
390
|
+
return true;
|
|
391
|
+
return !candidates.some((other) => other.ref.elementId !== id && isDescendantOf(other, id));
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
const applyReplaceText = (command, archive, index) => {
|
|
395
|
+
if (command.regex) {
|
|
396
|
+
try {
|
|
397
|
+
new RegExp(command.find, 'u');
|
|
398
|
+
}
|
|
399
|
+
catch (cause) {
|
|
400
|
+
return err('INVALID_COMMAND', cause instanceof Error ? cause.message : 'Invalid replaceText regex');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const matches = (text) => command.regex ? new RegExp(command.find, 'u').test(text) : text.includes(command.find);
|
|
404
|
+
const replaced = (text) => command.regex
|
|
405
|
+
? text.replace(new RegExp(command.find, 'gu'), command.replace)
|
|
406
|
+
: text.split(command.find).join(command.replace);
|
|
407
|
+
let candidates = index.elements.filter((item) => Boolean(item.text?.length) &&
|
|
408
|
+
(!command.selector || matchesSelector(item, command.selector)) &&
|
|
409
|
+
matches(item.text ?? ''));
|
|
410
|
+
if (!command.selector)
|
|
411
|
+
candidates = narrowReplaceTextCandidates(candidates);
|
|
412
|
+
const limit = command.limit ?? 1000;
|
|
413
|
+
const selected = candidates.slice(0, limit);
|
|
414
|
+
const diagnostics = [];
|
|
415
|
+
const refs = [];
|
|
416
|
+
const slidePages = [];
|
|
417
|
+
for (const item of selected) {
|
|
418
|
+
const next = replaced(item.text ?? '');
|
|
419
|
+
if (next === item.text)
|
|
420
|
+
continue;
|
|
421
|
+
const written = writeText(archive, item, next, diagnostics);
|
|
422
|
+
if (!written.ok)
|
|
423
|
+
return written;
|
|
424
|
+
refs.push(item.ref);
|
|
425
|
+
slidePages.push(...slidesForItem(index, item));
|
|
426
|
+
}
|
|
427
|
+
return ok({
|
|
428
|
+
changed: refs.length > 0,
|
|
429
|
+
matched: refs.length,
|
|
430
|
+
refs,
|
|
431
|
+
slides: mergeSlides(slidePages),
|
|
432
|
+
diagnostics,
|
|
433
|
+
}, diagnostics);
|
|
434
|
+
};
|
|
435
|
+
const resolveCommandRef = (command, index) => {
|
|
436
|
+
if (command.type === 'replaceText')
|
|
437
|
+
return err('INVALID_COMMAND', 'replaceText does not resolve a single ref');
|
|
438
|
+
if (command.type === 'addSlide' || command.type === 'addShape')
|
|
439
|
+
return err('INVALID_COMMAND', 'addSlide/addShape resolve separately');
|
|
440
|
+
if (command.type === 'xfrmSet') {
|
|
441
|
+
const target = command.target ??
|
|
442
|
+
(command.slide !== undefined && command.shape !== undefined
|
|
443
|
+
? `slide:${command.slide}/shape:${command.shape}`
|
|
444
|
+
: undefined);
|
|
445
|
+
if (!target)
|
|
446
|
+
return err('INVALID_COMMAND', 'xfrmSet requires target or slide+shape');
|
|
447
|
+
const resolved = resolveTarget(index, target);
|
|
448
|
+
if (!resolved.ok)
|
|
449
|
+
return resolved;
|
|
450
|
+
return {
|
|
451
|
+
ok: true,
|
|
452
|
+
value: {
|
|
453
|
+
ref: resolved.value.item.ref,
|
|
454
|
+
item: resolved.value.item,
|
|
455
|
+
target: resolved.value.target,
|
|
456
|
+
parsed: resolved.value.parsed,
|
|
457
|
+
},
|
|
458
|
+
diagnostics: [],
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (command.type === 'zMove' || command.type === 'set' || command.type === 'setTableLayout') {
|
|
462
|
+
const resolved = resolveTarget(index, command.target);
|
|
463
|
+
if (!resolved.ok)
|
|
464
|
+
return resolved;
|
|
465
|
+
return {
|
|
466
|
+
ok: true,
|
|
467
|
+
value: {
|
|
468
|
+
ref: resolved.value.item.ref,
|
|
469
|
+
item: resolved.value.item,
|
|
470
|
+
target: resolved.value.target,
|
|
471
|
+
parsed: resolved.value.parsed,
|
|
472
|
+
},
|
|
473
|
+
diagnostics: [],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (command.type === 'add') {
|
|
477
|
+
const got = resolveToRef(index, {
|
|
478
|
+
...(command.parent !== undefined ? { ref: command.parent } : {}),
|
|
479
|
+
...(command.target !== undefined ? { target: command.target } : {}),
|
|
480
|
+
});
|
|
481
|
+
if (!got.ok)
|
|
482
|
+
return got;
|
|
483
|
+
const item = got.value.resolved?.item ?? findIndexed(index, got.value.ref);
|
|
484
|
+
if (!item)
|
|
485
|
+
return err('ELEMENT_NOT_FOUND', 'Element reference was not found');
|
|
486
|
+
return {
|
|
487
|
+
ok: true,
|
|
488
|
+
value: {
|
|
489
|
+
ref: item.ref,
|
|
490
|
+
item,
|
|
491
|
+
...(got.value.resolved?.target !== undefined ? { target: got.value.resolved.target } : {}),
|
|
492
|
+
...(got.value.resolved?.parsed !== undefined ? { parsed: got.value.resolved.parsed } : {}),
|
|
493
|
+
},
|
|
494
|
+
diagnostics: [],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const got = resolveToRef(index, {
|
|
498
|
+
...('ref' in command && command.ref !== undefined ? { ref: command.ref } : {}),
|
|
499
|
+
...('target' in command && command.target !== undefined ? { target: command.target } : {}),
|
|
500
|
+
});
|
|
501
|
+
if (!got.ok)
|
|
502
|
+
return got;
|
|
503
|
+
// Prefer the item from target resolution so synthetic notes (not yet in the index) work.
|
|
504
|
+
const item = got.value.resolved?.item ?? findIndexed(index, got.value.ref);
|
|
505
|
+
if (!item)
|
|
506
|
+
return err('ELEMENT_NOT_FOUND', 'Element reference was not found');
|
|
507
|
+
return {
|
|
508
|
+
ok: true,
|
|
509
|
+
value: {
|
|
510
|
+
ref: item.ref,
|
|
511
|
+
item,
|
|
512
|
+
...(got.value.resolved?.target !== undefined ? { target: got.value.resolved.target } : {}),
|
|
513
|
+
...(got.value.resolved?.parsed !== undefined ? { parsed: got.value.resolved.parsed } : {}),
|
|
514
|
+
},
|
|
515
|
+
diagnostics: [],
|
|
516
|
+
};
|
|
517
|
+
};
|
|
518
|
+
/** Narrow a shape node to paragraph or run when the target path includes those segments. */
|
|
519
|
+
const focusMutationNode = (shape, parsed) => {
|
|
520
|
+
if (!parsed?.focus)
|
|
521
|
+
return ok(shape);
|
|
522
|
+
if (parsed.focus === 'paragraph' && parsed.paragraph !== undefined) {
|
|
523
|
+
const paragraphs = descendants(shape, 'p');
|
|
524
|
+
const p = paragraphs[parsed.paragraph];
|
|
525
|
+
if (!p)
|
|
526
|
+
return err('TARGET_NOT_FOUND', `paragraph:${parsed.paragraph} not found on ${parsed.raw}`, [], { target: parsed.raw });
|
|
527
|
+
return ok(p);
|
|
528
|
+
}
|
|
529
|
+
if (parsed.focus === 'run' && parsed.run !== undefined) {
|
|
530
|
+
const runs = descendants(shape, 'r');
|
|
531
|
+
const run = runs[parsed.run];
|
|
532
|
+
if (!run)
|
|
533
|
+
return err('TARGET_NOT_FOUND', `run:${parsed.run} not found on ${parsed.raw}`, [], {
|
|
534
|
+
target: parsed.raw,
|
|
535
|
+
});
|
|
536
|
+
return ok(run);
|
|
537
|
+
}
|
|
538
|
+
return ok(shape);
|
|
539
|
+
};
|
|
540
|
+
const shapeTypeToElement = (shapeType, fields) => {
|
|
541
|
+
const base = { ...fields };
|
|
542
|
+
switch (shapeType) {
|
|
543
|
+
case 'text':
|
|
544
|
+
return {
|
|
545
|
+
...base,
|
|
546
|
+
kind: 'textbox',
|
|
547
|
+
type: 'textbox',
|
|
548
|
+
preset: 'rect',
|
|
549
|
+
text: typeof fields['text'] === 'string' ? fields['text'] : '',
|
|
550
|
+
};
|
|
551
|
+
case 'rect':
|
|
552
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'rect' };
|
|
553
|
+
case 'rounded-rect':
|
|
554
|
+
return {
|
|
555
|
+
...base,
|
|
556
|
+
kind: 'shape',
|
|
557
|
+
type: 'shape',
|
|
558
|
+
preset: 'roundRect',
|
|
559
|
+
...(typeof fields['cornerRadius'] === 'number'
|
|
560
|
+
? { cornerRadius: fields['cornerRadius'] }
|
|
561
|
+
: {}),
|
|
562
|
+
};
|
|
563
|
+
case 'ellipse':
|
|
564
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'ellipse' };
|
|
565
|
+
case 'line':
|
|
566
|
+
case 'connector':
|
|
567
|
+
return { ...base, kind: 'connector', type: 'connector', preset: 'line' };
|
|
568
|
+
case 'elbow':
|
|
569
|
+
case 'elbow-connector':
|
|
570
|
+
return { ...base, kind: 'connector', type: 'connector', preset: 'bentConnector3' };
|
|
571
|
+
case 'curved-connector':
|
|
572
|
+
return { ...base, kind: 'connector', type: 'connector', preset: 'curvedConnector3' };
|
|
573
|
+
case 'arrow':
|
|
574
|
+
case 'right-arrow':
|
|
575
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'rightArrow', txBox: false };
|
|
576
|
+
case 'left-arrow':
|
|
577
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'leftArrow', txBox: false };
|
|
578
|
+
case 'up-arrow':
|
|
579
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'upArrow', txBox: false };
|
|
580
|
+
case 'down-arrow':
|
|
581
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'downArrow', txBox: false };
|
|
582
|
+
case 'chevron':
|
|
583
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'chevron', txBox: false };
|
|
584
|
+
case 'pentagon':
|
|
585
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'homePlate', txBox: false };
|
|
586
|
+
case 'trapezoid':
|
|
587
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'trapezoid', txBox: false };
|
|
588
|
+
case 'triangle':
|
|
589
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'triangle', txBox: false };
|
|
590
|
+
case 'rt-triangle':
|
|
591
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'rtTriangle', txBox: false };
|
|
592
|
+
case 'circular-arrow':
|
|
593
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'circularArrow', txBox: false };
|
|
594
|
+
case 'curved-right-arrow':
|
|
595
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'curvedRightArrow', txBox: false };
|
|
596
|
+
case 'curved-left-arrow':
|
|
597
|
+
return { ...base, kind: 'shape', type: 'shape', preset: 'curvedLeftArrow', txBox: false };
|
|
598
|
+
case 'image':
|
|
599
|
+
return {
|
|
600
|
+
...base,
|
|
601
|
+
kind: 'picture',
|
|
602
|
+
type: 'picture',
|
|
603
|
+
...(typeof fields['file'] === 'string' ? { path: fields['file'] } : {}),
|
|
604
|
+
};
|
|
605
|
+
case 'group':
|
|
606
|
+
return { ...base, kind: 'group', type: 'group' };
|
|
607
|
+
case 'table':
|
|
608
|
+
return {
|
|
609
|
+
...base,
|
|
610
|
+
kind: 'table',
|
|
611
|
+
type: 'table',
|
|
612
|
+
rows: Array.isArray(fields['rows']) ? fields['rows'] : [['']],
|
|
613
|
+
};
|
|
614
|
+
case 'chart':
|
|
615
|
+
return {
|
|
616
|
+
...base,
|
|
617
|
+
kind: 'chart',
|
|
618
|
+
type: 'chart',
|
|
619
|
+
...(typeof fields['chartType'] === 'string' ? { chartType: fields['chartType'] } : {}),
|
|
620
|
+
...(fields['data'] !== undefined ? { data: fields['data'] } : {}),
|
|
621
|
+
};
|
|
622
|
+
case 'video':
|
|
623
|
+
return {
|
|
624
|
+
...base,
|
|
625
|
+
kind: 'video',
|
|
626
|
+
type: 'video',
|
|
627
|
+
...(typeof fields['file'] === 'string' ? { path: fields['file'] } : {}),
|
|
628
|
+
};
|
|
629
|
+
case 'audio':
|
|
630
|
+
return {
|
|
631
|
+
...base,
|
|
632
|
+
kind: 'audio',
|
|
633
|
+
type: 'audio',
|
|
634
|
+
...(typeof fields['file'] === 'string' ? { path: fields['file'] } : {}),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
const applyZMove = (doc, node, command, index) => {
|
|
639
|
+
const parent = node.parentNode;
|
|
640
|
+
if (!parent)
|
|
641
|
+
return err('INVALID_COMMAND', 'Shape has no parent for z-order move');
|
|
642
|
+
const siblings = children(parent).filter((child) => child.localName != null && SHAPE_LOCAL_NAMES.has(child.localName));
|
|
643
|
+
if (!siblings.includes(node))
|
|
644
|
+
return err('INVALID_COMMAND', 'Shape is not a direct child of the shape tree');
|
|
645
|
+
const findSibling = (target) => {
|
|
646
|
+
const resolved = resolveTarget(index, target);
|
|
647
|
+
if (!resolved.ok)
|
|
648
|
+
return resolved;
|
|
649
|
+
const other = nodeFor(doc, resolved.value.item);
|
|
650
|
+
if (!other || !siblings.includes(other))
|
|
651
|
+
return err('TARGET_NOT_FOUND', `z-order reference not on same parent: ${target}`, [], {
|
|
652
|
+
target,
|
|
653
|
+
});
|
|
654
|
+
return { ok: true, value: other, diagnostics: [] };
|
|
655
|
+
};
|
|
656
|
+
parent.removeChild(node);
|
|
657
|
+
if (command.toFront) {
|
|
658
|
+
parent.appendChild(node);
|
|
659
|
+
return ok(undefined);
|
|
660
|
+
}
|
|
661
|
+
if (command.toBack) {
|
|
662
|
+
const firstShape = children(parent).find((child) => child.localName != null && SHAPE_LOCAL_NAMES.has(child.localName));
|
|
663
|
+
if (firstShape)
|
|
664
|
+
parent.insertBefore(node, firstShape);
|
|
665
|
+
else
|
|
666
|
+
parent.appendChild(node);
|
|
667
|
+
return ok(undefined);
|
|
668
|
+
}
|
|
669
|
+
if (command.above) {
|
|
670
|
+
const other = findSibling(command.above);
|
|
671
|
+
if (!other.ok)
|
|
672
|
+
return other;
|
|
673
|
+
if (other.value.nextSibling)
|
|
674
|
+
parent.insertBefore(node, other.value.nextSibling);
|
|
675
|
+
else
|
|
676
|
+
parent.appendChild(node);
|
|
677
|
+
return ok(undefined);
|
|
678
|
+
}
|
|
679
|
+
if (command.below) {
|
|
680
|
+
const other = findSibling(command.below);
|
|
681
|
+
if (!other.ok)
|
|
682
|
+
return other;
|
|
683
|
+
parent.insertBefore(node, other.value);
|
|
684
|
+
return ok(undefined);
|
|
685
|
+
}
|
|
686
|
+
return err('INVALID_COMMAND', 'zMove requires --above, --below, --to-front, or --to-back');
|
|
687
|
+
};
|
|
688
|
+
export async function mutate(command, archive, index) {
|
|
689
|
+
if (command.type === 'replaceText')
|
|
690
|
+
return applyReplaceText(command, archive, index);
|
|
691
|
+
if (command.type === 'addSlide') {
|
|
692
|
+
const slides = index.elements.filter((item) => item.kind === 'slide');
|
|
693
|
+
let layout;
|
|
694
|
+
if (command.layout) {
|
|
695
|
+
const resolved = resolveLayoutRef(archive, index, command.layout, {
|
|
696
|
+
allowBlankFallback: true,
|
|
697
|
+
});
|
|
698
|
+
if (!resolved.ok)
|
|
699
|
+
return resolved;
|
|
700
|
+
layout = resolved.value;
|
|
701
|
+
}
|
|
702
|
+
// Prefer an explicit layout; otherwise inherit the layout of the anchor slide
|
|
703
|
+
// (or the first slide) so blank pages still bind to a slideLayout.
|
|
704
|
+
if (!layout) {
|
|
705
|
+
const anchor = command.after !== undefined && command.after > 0 ? slides[command.after - 1] : slides[0];
|
|
706
|
+
if (anchor) {
|
|
707
|
+
const rels = archive.getRelationships(anchor.partUri);
|
|
708
|
+
const layoutRel = rels.find((r) => r.type === REL.layout);
|
|
709
|
+
layout = layoutRel?.resolvedTarget;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const partUri = addSlide(archive, undefined, layout, command.after);
|
|
713
|
+
return ok({
|
|
714
|
+
changed: true,
|
|
715
|
+
partUri,
|
|
716
|
+
slides: [],
|
|
717
|
+
changedParts: [partUri, '/ppt/presentation.xml'],
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (command.type === 'setSlideLayout') {
|
|
721
|
+
const slides = index.elements.filter((item) => item.kind === 'slide');
|
|
722
|
+
const slide = slides[command.slide - 1];
|
|
723
|
+
if (!slide)
|
|
724
|
+
return err('TARGET_NOT_FOUND', `slide:${command.slide} does not exist`, [], {
|
|
725
|
+
target: `slide:${command.slide}`,
|
|
726
|
+
hint: 'Run deckuse list slides --json.',
|
|
727
|
+
});
|
|
728
|
+
const resolved = resolveLayoutRef(archive, index, command.layout);
|
|
729
|
+
if (!resolved.ok)
|
|
730
|
+
return resolved;
|
|
731
|
+
const layoutPart = resolved.value;
|
|
732
|
+
if (!layoutPart)
|
|
733
|
+
return err('TARGET_NOT_FOUND', `Layout not found: ${command.layout}`, [], {
|
|
734
|
+
target: command.layout,
|
|
735
|
+
});
|
|
736
|
+
const changed = setSlideLayout(archive, slide.partUri, layoutPart);
|
|
737
|
+
return ok({
|
|
738
|
+
changed,
|
|
739
|
+
slides: [command.slide],
|
|
740
|
+
changedTargets: [`slide:${command.slide}`],
|
|
741
|
+
changedParts: changed ? [slide.partUri] : [],
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (command.type === 'addShape') {
|
|
745
|
+
const slides = index.elements.filter((item) => item.kind === 'slide');
|
|
746
|
+
const slide = slides[command.slide - 1];
|
|
747
|
+
if (!slide)
|
|
748
|
+
return err('TARGET_NOT_FOUND', `slide:${command.slide} does not exist`, [], {
|
|
749
|
+
target: `slide:${command.slide}`,
|
|
750
|
+
});
|
|
751
|
+
let role;
|
|
752
|
+
if (command.role !== undefined) {
|
|
753
|
+
const normalized = normalizePlaceholderRole(command.role);
|
|
754
|
+
if (!normalized.ok)
|
|
755
|
+
return err('INVALID_COMMAND', normalized.message);
|
|
756
|
+
role = normalized.type;
|
|
757
|
+
}
|
|
758
|
+
const doc = archive.readXml(slide.partUri);
|
|
759
|
+
const parent = first(doc, 'spTree') ?? root(doc);
|
|
760
|
+
const geom = normalizeTransformFields(archive, {
|
|
761
|
+
...(command.x !== undefined ? { x: command.x } : {}),
|
|
762
|
+
...(command.y !== undefined ? { y: command.y } : {}),
|
|
763
|
+
...(command.width !== undefined ? { width: command.width } : {}),
|
|
764
|
+
...(command.height !== undefined ? { height: command.height } : {}),
|
|
765
|
+
});
|
|
766
|
+
const element = shapeTypeToElement(command.shapeType, {
|
|
767
|
+
...(command.name !== undefined ? { name: command.name } : {}),
|
|
768
|
+
...(role !== undefined ? { role } : {}),
|
|
769
|
+
...geom,
|
|
770
|
+
...(command.file !== undefined ? { file: command.file } : {}),
|
|
771
|
+
...(command.blocks === undefined && command.text !== undefined ? { text: command.text } : {}),
|
|
772
|
+
...(command.cornerRadius !== undefined ? { cornerRadius: command.cornerRadius } : {}),
|
|
773
|
+
...(command.rows !== undefined ? { rows: command.rows } : {}),
|
|
774
|
+
...(command.theme !== undefined ? { theme: command.theme } : {}),
|
|
775
|
+
...(command.alignColumns !== undefined ? { alignColumns: command.alignColumns } : {}),
|
|
776
|
+
...(command.chartType !== undefined ? { chartType: command.chartType } : {}),
|
|
777
|
+
...(command.data !== undefined ? { data: command.data } : {}),
|
|
778
|
+
...(command.showDataLabels !== undefined ? { showDataLabels: command.showDataLabels } : {}),
|
|
779
|
+
});
|
|
780
|
+
const created = await addElement(archive, slide.partUri, doc, parent, element);
|
|
781
|
+
const diagnostics = [];
|
|
782
|
+
if (command.shapeType === 'table' && command.rows) {
|
|
783
|
+
const rows = normalizeTableRows(command.rows);
|
|
784
|
+
const cols = Math.max(1, ...rows.map((r) => r.length));
|
|
785
|
+
const widthEmu = typeof geom['width'] === 'number'
|
|
786
|
+
? geom['width']
|
|
787
|
+
: parseLength(String(geom['width'] ?? 914400 * cols), {
|
|
788
|
+
...lengthContextFor(archive, 'x'),
|
|
789
|
+
axis: 'x',
|
|
790
|
+
});
|
|
791
|
+
const layout = measureTableLayout({ rows, widthEmu, fontPt: 11 });
|
|
792
|
+
if (command.height !== undefined &&
|
|
793
|
+
command.height !== 'auto' &&
|
|
794
|
+
typeof geom['height'] === 'number' &&
|
|
795
|
+
geom['height'] < layout.totalHeightEmu) {
|
|
796
|
+
diagnostics.push(tableHeightMayClipDiagnostic({
|
|
797
|
+
estimatedEmu: layout.totalHeightEmu,
|
|
798
|
+
givenEmu: geom['height'],
|
|
799
|
+
target: `slide:${command.slide}/shape:${attr(cNvPr(created), 'id') ?? '?'}`,
|
|
800
|
+
}));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (command.chartType === 'combo') {
|
|
804
|
+
diagnostics.push({
|
|
805
|
+
severity: 'warning',
|
|
806
|
+
code: 'COMBO_CHART_RENDER_LIMITED',
|
|
807
|
+
message: 'combo charts write into PPTX, but community deckuse render may show an Advanced Chart placeholder; prefer column/bar/line/pie for visual QA',
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
if (command.fill !== undefined || command.stroke !== undefined) {
|
|
811
|
+
const styleProps = {};
|
|
812
|
+
if (command.fill !== undefined)
|
|
813
|
+
styleProps['fill'] = command.fill;
|
|
814
|
+
if (command.stroke !== undefined)
|
|
815
|
+
styleProps['stroke'] = command.stroke;
|
|
816
|
+
const styled = applyShapeProperties(created, styleProps, {
|
|
817
|
+
archive,
|
|
818
|
+
partUri: slide.partUri,
|
|
819
|
+
});
|
|
820
|
+
if (!styled.ok)
|
|
821
|
+
return styled;
|
|
822
|
+
diagnostics.push(...styled.diagnostics);
|
|
823
|
+
}
|
|
824
|
+
if (command.anchor !== undefined || command.wrap !== undefined) {
|
|
825
|
+
const body = applyShapeProperties(created, {
|
|
826
|
+
...(command.anchor !== undefined ? { anchor: command.anchor } : {}),
|
|
827
|
+
...(command.wrap !== undefined ? { wrap: command.wrap } : {}),
|
|
828
|
+
}, { archive, partUri: slide.partUri });
|
|
829
|
+
if (!body.ok)
|
|
830
|
+
return body;
|
|
831
|
+
diagnostics.push(...body.diagnostics);
|
|
832
|
+
}
|
|
833
|
+
if (command.blocks !== undefined) {
|
|
834
|
+
setNodeTextBlocks(created, command.blocks.map((block) => mapTextBlock(block)));
|
|
835
|
+
}
|
|
836
|
+
archive.writeXml(slide.partUri, doc);
|
|
837
|
+
const id = attr(cNvPr(created), 'id') ?? '?';
|
|
838
|
+
return ok({
|
|
839
|
+
changed: true,
|
|
840
|
+
slides: [command.slide],
|
|
841
|
+
changedTargets: [`slide:${command.slide}/shape:${id}`],
|
|
842
|
+
changedParts: [slide.partUri],
|
|
843
|
+
diagnostics,
|
|
844
|
+
}, diagnostics);
|
|
845
|
+
}
|
|
846
|
+
if (command.type === 'alignElements') {
|
|
847
|
+
const slides = index.elements.filter((item) => item.kind === 'slide');
|
|
848
|
+
const slide = slides[command.slide - 1];
|
|
849
|
+
if (!slide)
|
|
850
|
+
return err('TARGET_NOT_FOUND', `slide:${command.slide} does not exist`, [], {
|
|
851
|
+
target: `slide:${command.slide}`,
|
|
852
|
+
});
|
|
853
|
+
const doc = archive.readXml(slide.partUri);
|
|
854
|
+
const boxes = [];
|
|
855
|
+
for (const target of command.targets) {
|
|
856
|
+
const resolved = resolveTarget(index, target);
|
|
857
|
+
if (!resolved.ok)
|
|
858
|
+
return resolved;
|
|
859
|
+
const node = nodeFor(doc, resolved.value.item);
|
|
860
|
+
if (!node)
|
|
861
|
+
return err('ELEMENT_NOT_FOUND', `Element XML node was not found: ${target}`, [], {
|
|
862
|
+
target,
|
|
863
|
+
});
|
|
864
|
+
const box = readBBox(node);
|
|
865
|
+
if (!box)
|
|
866
|
+
return err('INVALID_COMMAND', `Target has no geometry (xfrm): ${target}`, [], { target });
|
|
867
|
+
boxes.push(box);
|
|
868
|
+
}
|
|
869
|
+
const updates = computeAlignUpdates(boxes, command.mode, command.gap, lengthContextFor(archive, command.mode.includes('v') ? 'y' : 'x'));
|
|
870
|
+
for (const update of updates)
|
|
871
|
+
writeBBox(update.node, update);
|
|
872
|
+
archive.writeXml(slide.partUri, doc);
|
|
873
|
+
return ok({
|
|
874
|
+
changed: true,
|
|
875
|
+
slides: [command.slide],
|
|
876
|
+
changedTargets: [...command.targets],
|
|
877
|
+
changedParts: [slide.partUri],
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
const resolved = resolveCommandRef(command, index);
|
|
881
|
+
if (!resolved.ok)
|
|
882
|
+
return resolved;
|
|
883
|
+
const { item, target, parsed } = resolved.value;
|
|
884
|
+
if (item.ref.revision && item.ref.revision !== index.revision)
|
|
885
|
+
return err('TRANSACTION_CONFLICT', `Expected revision ${item.ref.revision}, current ${index.revision}`);
|
|
886
|
+
// Edition gate (master/layout/theme/advanced chart): hard-deny unless
|
|
887
|
+
// a registered PptxEditionExtension allows the target.
|
|
888
|
+
// Slide add/remove/duplicate, addSlide layout binding, and setSlideLayout
|
|
889
|
+
// rebind are not gated here.
|
|
890
|
+
if (item.kind !== 'slide') {
|
|
891
|
+
const gated = assertWritable(item, archive);
|
|
892
|
+
if (!gated.ok)
|
|
893
|
+
return gated;
|
|
894
|
+
}
|
|
895
|
+
const diagnostics = [];
|
|
896
|
+
if (item.kind === 'slide') {
|
|
897
|
+
if (command.type === 'remove') {
|
|
898
|
+
removeSlide(archive, item.partUri);
|
|
899
|
+
return ok({
|
|
900
|
+
changed: true,
|
|
901
|
+
slides: slidesForItem(index, item),
|
|
902
|
+
...(target ? { changedTargets: [target] } : {}),
|
|
903
|
+
changedParts: ['/ppt/presentation.xml'],
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
if (command.type === 'duplicate') {
|
|
907
|
+
return ok({
|
|
908
|
+
changed: true,
|
|
909
|
+
partUri: duplicateSlide(archive, item.partUri),
|
|
910
|
+
slides: slidesForItem(index, item),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
if (command.type === 'add') {
|
|
914
|
+
const rawKind = command.element['kind'] ?? command.element['type'];
|
|
915
|
+
if (rawKind === 'slide') {
|
|
916
|
+
const template = typeof command.element['templatePart'] === 'string'
|
|
917
|
+
? command.element['templatePart']
|
|
918
|
+
: undefined, layout = typeof command.element['layoutPart'] === 'string'
|
|
919
|
+
? command.element['layoutPart']
|
|
920
|
+
: undefined;
|
|
921
|
+
return ok({ changed: true, partUri: addSlide(archive, template, layout), slides: [] });
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (command.type === 'set' || command.type === 'setProperties') {
|
|
925
|
+
// slide-level name/hidden not fully modeled; treat as unsupported for now except no-op properties
|
|
926
|
+
return err('UNSUPPORTED_CAPABILITY', 'Slide property writes beyond add/remove are limited in Phase 1a', [], target !== undefined ? { target } : {});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const notesReady = materializeNotes(archive, item);
|
|
930
|
+
if (!notesReady.ok)
|
|
931
|
+
return notesReady;
|
|
932
|
+
const liveItem = notesReady.value;
|
|
933
|
+
const doc = archive.readXml(liveItem.partUri), shapeNode = nodeFor(doc, liveItem);
|
|
934
|
+
if (!shapeNode)
|
|
935
|
+
return err('ELEMENT_NOT_FOUND', `Element XML node was not found: ${liveItem.ref.elementId ?? liveItem.ref.path ?? ''}`);
|
|
936
|
+
const focused = focusMutationNode(shapeNode, parsed);
|
|
937
|
+
if (!focused.ok)
|
|
938
|
+
return focused;
|
|
939
|
+
const node = focused.value;
|
|
940
|
+
const chartPart = liveItem.kind === 'chart' && typeof liveItem.payload?.['chartPart'] === 'string'
|
|
941
|
+
? liveItem.payload['chartPart']
|
|
942
|
+
: undefined;
|
|
943
|
+
let chartMutated = false;
|
|
944
|
+
if (command.type === 'setText') {
|
|
945
|
+
if (command.blocks !== undefined) {
|
|
946
|
+
if (chartPart)
|
|
947
|
+
return err('INVALID_COMMAND', 'Chart titles do not support rich text blocks');
|
|
948
|
+
setNodeTextBlocks(node, command.blocks.map((block) => mapTextBlock(block)));
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
const text = command.text ?? command.value;
|
|
952
|
+
if (text === undefined)
|
|
953
|
+
return err('INVALID_COMMAND', 'setText requires text, value, or blocks');
|
|
954
|
+
if (chartPart) {
|
|
955
|
+
const result = updateChart(archive, chartPart, { title: text });
|
|
956
|
+
chartMutated = true;
|
|
957
|
+
if (result.workbook)
|
|
958
|
+
diagnostics.push({
|
|
959
|
+
severity: 'warning',
|
|
960
|
+
code: 'EMBEDDED_WORKBOOK_NOT_SYNCHRONIZED',
|
|
961
|
+
message: 'Chart cache changed; embedded workbook was not modified',
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
else
|
|
965
|
+
setNodeText(node, text);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
else if (command.type === 'setTransform') {
|
|
969
|
+
transform(shapeNode, normalizeTransformFields(archive, command.transform), {
|
|
970
|
+
archive,
|
|
971
|
+
partUri: liveItem.partUri,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
else if (command.type === 'xfrmSet') {
|
|
975
|
+
const geom = normalizeTransformFields(archive, {
|
|
976
|
+
...(command.x !== undefined ? { x: command.x } : {}),
|
|
977
|
+
...(command.y !== undefined ? { y: command.y } : {}),
|
|
978
|
+
...(command.width !== undefined ? { width: command.width } : {}),
|
|
979
|
+
...(command.height !== undefined ? { height: command.height } : {}),
|
|
980
|
+
});
|
|
981
|
+
transform(shapeNode, {
|
|
982
|
+
...geom,
|
|
983
|
+
...(command.rotation !== undefined ? { rotation: command.rotation } : {}),
|
|
984
|
+
...(command.flipX !== undefined ? { flipX: command.flipX } : {}),
|
|
985
|
+
...(command.flipY !== undefined ? { flipY: command.flipY } : {}),
|
|
986
|
+
}, { archive, partUri: liveItem.partUri });
|
|
987
|
+
}
|
|
988
|
+
else if (command.type === 'setTableLayout') {
|
|
989
|
+
if (liveItem.kind !== 'table')
|
|
990
|
+
return err('INVALID_COMMAND', 'setTableLayout requires a table target', [], {
|
|
991
|
+
target: command.target,
|
|
992
|
+
});
|
|
993
|
+
const heightOpt = command.height === undefined
|
|
994
|
+
? undefined
|
|
995
|
+
: command.height === 'auto'
|
|
996
|
+
? 'auto'
|
|
997
|
+
: resolveLengthField(archive, command.height, 'y');
|
|
998
|
+
const laid = applyTableLayout(shapeNode, {
|
|
999
|
+
...(heightOpt !== undefined ? { height: heightOpt } : {}),
|
|
1000
|
+
...(command.redistribute !== undefined ? { redistribute: command.redistribute } : {}),
|
|
1001
|
+
});
|
|
1002
|
+
if (!laid.ok)
|
|
1003
|
+
return laid;
|
|
1004
|
+
if (laid.value.estimatedEmu !== undefined &&
|
|
1005
|
+
typeof heightOpt === 'number' &&
|
|
1006
|
+
heightOpt < laid.value.estimatedEmu) {
|
|
1007
|
+
diagnostics.push(tableHeightMayClipDiagnostic({
|
|
1008
|
+
estimatedEmu: laid.value.estimatedEmu,
|
|
1009
|
+
givenEmu: heightOpt,
|
|
1010
|
+
target: command.target,
|
|
1011
|
+
}));
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
else if (command.type === 'set' || command.type === 'setProperties') {
|
|
1015
|
+
if (command.type === 'set' && command.scope && command.scope !== 'local')
|
|
1016
|
+
return err('UNSUPPORTED_CAPABILITY', `Write scope "${command.scope}" is not enabled in Phase 1a; use --scope local`, [], target !== undefined ? { target } : {});
|
|
1017
|
+
const properties = mapDottedProperties(command.properties);
|
|
1018
|
+
if (chartPart) {
|
|
1019
|
+
const checked = assertChartProperties(properties);
|
|
1020
|
+
if (!checked.ok)
|
|
1021
|
+
return checked;
|
|
1022
|
+
const chartProps = { ...properties };
|
|
1023
|
+
if (typeof chartProps['text'] === 'string' && chartProps['title'] === undefined)
|
|
1024
|
+
chartProps['title'] = chartProps['text'];
|
|
1025
|
+
const result = updateChart(archive, chartPart, chartProps);
|
|
1026
|
+
chartMutated = true;
|
|
1027
|
+
if (result.workbook)
|
|
1028
|
+
diagnostics.push({
|
|
1029
|
+
severity: 'warning',
|
|
1030
|
+
code: 'EMBEDDED_WORKBOOK_NOT_SYNCHRONIZED',
|
|
1031
|
+
message: 'Chart cache changed; embedded workbook was not modified',
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
else if (liveItem.kind === 'table') {
|
|
1035
|
+
const applied = applyTableProperties(shapeNode, properties, {
|
|
1036
|
+
archive,
|
|
1037
|
+
partUri: liveItem.partUri,
|
|
1038
|
+
});
|
|
1039
|
+
if (!applied.ok)
|
|
1040
|
+
return applied;
|
|
1041
|
+
if (applied.value.applied.length === 0)
|
|
1042
|
+
return err('INVALID_COMMAND', 'set requires at least one supported property');
|
|
1043
|
+
}
|
|
1044
|
+
else if (liveItem.kind === 'tableCell') {
|
|
1045
|
+
const applied = applyTableCellProperties(node, properties);
|
|
1046
|
+
if (!applied.ok)
|
|
1047
|
+
return applied;
|
|
1048
|
+
if (applied.value.applied.length === 0)
|
|
1049
|
+
return err('INVALID_COMMAND', 'set requires at least one supported property');
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
const applied = applyShapeProperties(node, properties, {
|
|
1053
|
+
archive,
|
|
1054
|
+
partUri: liveItem.partUri,
|
|
1055
|
+
});
|
|
1056
|
+
if (!applied.ok)
|
|
1057
|
+
return applied;
|
|
1058
|
+
if (applied.value.applied.length === 0)
|
|
1059
|
+
return err('INVALID_COMMAND', 'set requires at least one supported property');
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
else if (command.type === 'zMove') {
|
|
1063
|
+
const moved = applyZMove(doc, shapeNode, command, index);
|
|
1064
|
+
if (!moved.ok)
|
|
1065
|
+
return moved;
|
|
1066
|
+
}
|
|
1067
|
+
else if (command.type === 'replacePicture') {
|
|
1068
|
+
if (liveItem.kind !== 'picture')
|
|
1069
|
+
return err('INVALID_COMMAND', 'replacePicture requires a picture element');
|
|
1070
|
+
const loaded = await loadPictureBytes({
|
|
1071
|
+
...(command.path !== undefined ? { path: command.path } : {}),
|
|
1072
|
+
...(command.base64 !== undefined ? { base64: command.base64 } : {}),
|
|
1073
|
+
});
|
|
1074
|
+
if (!loaded.ok)
|
|
1075
|
+
return loaded;
|
|
1076
|
+
const replaced = replacePictureMedia(archive, liveItem.partUri, shapeNode, loaded.value.data, loaded.value.ext);
|
|
1077
|
+
if (!replaced.ok)
|
|
1078
|
+
return replaced;
|
|
1079
|
+
}
|
|
1080
|
+
else if (command.type === 'remove') {
|
|
1081
|
+
if (liveItem.kind === 'picture')
|
|
1082
|
+
detachPictureAndCleanup(archive, liveItem.partUri, doc, shapeNode);
|
|
1083
|
+
else if (liveItem.kind === 'video' || liveItem.kind === 'audio')
|
|
1084
|
+
detachMediaAndCleanup(archive, liveItem.partUri, doc, shapeNode);
|
|
1085
|
+
else
|
|
1086
|
+
shapeNode.parentNode?.removeChild(shapeNode);
|
|
1087
|
+
}
|
|
1088
|
+
else if (command.type === 'duplicate')
|
|
1089
|
+
duplicateElement(doc, shapeNode);
|
|
1090
|
+
else if (command.type === 'add') {
|
|
1091
|
+
const parent = liveItem.kind === 'slide' ? (first(doc, 'spTree') ?? shapeNode) : shapeNode;
|
|
1092
|
+
await addElement(archive, liveItem.partUri, doc, parent, command.element);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
return err('INVALID_COMMAND', `Unhandled mutation type`);
|
|
1096
|
+
}
|
|
1097
|
+
archive.writeXml(liveItem.partUri, doc);
|
|
1098
|
+
const changedParts = [liveItem.partUri];
|
|
1099
|
+
if (chartMutated && chartPart)
|
|
1100
|
+
changedParts.push(chartPart);
|
|
1101
|
+
if (liveItem.kind === 'notes' && typeof liveItem.location?.['slidePart'] === 'string')
|
|
1102
|
+
changedParts.push(liveItem.location['slidePart']);
|
|
1103
|
+
return ok({
|
|
1104
|
+
changed: true,
|
|
1105
|
+
slides: slidesForItem(index, liveItem),
|
|
1106
|
+
diagnostics,
|
|
1107
|
+
...(target ? { changedTargets: [target] } : {}),
|
|
1108
|
+
changedParts,
|
|
1109
|
+
}, diagnostics);
|
|
1110
|
+
}
|
|
1111
|
+
//# sourceMappingURL=mutations.js.map
|