@office-kit/pptx-dev 0.11.0 → 0.13.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.
@@ -1,1741 +0,0 @@
1
- import { n as renderDeck } from "./build-cpmt-r9Q.mjs";
2
- import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
- import { Worker } from "node:worker_threads";
4
- import { basename, dirname, join, resolve, sep } from "node:path";
5
- import { mkdtempSync } from "node:fs";
6
- import { tmpdir } from "node:os";
7
- import { context, transform } from "esbuild";
8
- import { fileURLToPath, pathToFileURL } from "node:url";
9
- import { createHash, randomUUID } from "node:crypto";
10
- import { strFromU8, strToU8, unzipSync, zipSync } from "fflate";
11
- import { getShapeId, getShapeKind, getShapeName, getShapePlaceholderIdx, getShapePlaceholderType, getSlideLayoutName, getSlideLayoutPartName, getSlideLayoutType, getSlideLayouts, getSlidePartName, getSlideShapes, getSlideTitle, getSlides, loadPresentation } from "@office-kit/pptx";
12
- //#region src/bundle.ts
13
- const resolvePackage = (name) => fileURLToPath(import.meta.resolve(name));
14
- async function createDeckCompiler(entry) {
15
- const transformedSources = /* @__PURE__ */ new Map();
16
- const compiler = await context({
17
- entryPoints: [resolve(entry)],
18
- outfile: "deck.mjs",
19
- write: false,
20
- bundle: true,
21
- platform: "node",
22
- format: "esm",
23
- target: "node22",
24
- jsx: "automatic",
25
- jsxImportSource: "@office-kit/pptx-dsl",
26
- sourcemap: "inline",
27
- sourceRoot: pathToFileURL(process.cwd() + sep).href,
28
- metafile: true,
29
- logLevel: "silent",
30
- plugins: [{
31
- name: "source-location",
32
- setup(builder) {
33
- builder.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async ({ path }) => {
34
- const source = await readFile(path, "utf8");
35
- const cached = transformedSources.get(path);
36
- if (cached?.source === source) return {
37
- contents: cached.code,
38
- loader: "js"
39
- };
40
- const transformed = await transform(source, {
41
- loader: path.endsWith(".tsx") ? "tsx" : path.endsWith(".ts") || path.endsWith(".mts") || path.endsWith(".cts") ? "ts" : path.endsWith(".jsx") ? "jsx" : "js",
42
- sourcefile: path,
43
- sourcemap: "inline",
44
- jsx: "automatic",
45
- jsxDev: true,
46
- jsxImportSource: "@office-kit/pptx-dsl",
47
- define: { "import.meta.url": JSON.stringify(pathToFileURL(path).href) }
48
- });
49
- transformedSources.set(path, {
50
- source,
51
- code: transformed.code
52
- });
53
- return {
54
- contents: transformed.code,
55
- loader: "js"
56
- };
57
- });
58
- }
59
- }, {
60
- name: "shared-office-kit",
61
- setup(builder) {
62
- builder.onResolve({ filter: /^@office-kit\/pptx(?:-dsl)?(?:\/.*)?$/ }, ({ path }) => ({
63
- path: resolvePackage(path),
64
- external: true
65
- }));
66
- }
67
- }]
68
- });
69
- return {
70
- async build(output) {
71
- const result = await compiler.rebuild();
72
- await writeFile(output, result.outputFiles[0].contents);
73
- const dependencies = Object.keys(result.metafile.inputs).map((path) => resolve(path));
74
- const used = new Set(dependencies);
75
- for (const path of transformedSources.keys()) if (!used.has(path)) transformedSources.delete(path);
76
- return dependencies;
77
- },
78
- cancel: () => compiler.cancel(),
79
- close: () => compiler.dispose()
80
- };
81
- }
82
- //#endregion
83
- //#region src/build-runner.ts
84
- const BUILD_TIMEOUT_MS = 6e4;
85
- function prepareWorker() {
86
- const directory = mkdtempSync(join(tmpdir(), "office-pptx-"));
87
- const worker = new Worker(new URL("./worker.mjs", import.meta.url), {
88
- workerData: { directory },
89
- execArgv: ["--enable-source-maps"]
90
- });
91
- const result = new Promise((resolveBuild, reject) => {
92
- worker.once("message", resolveBuild);
93
- worker.once("error", reject);
94
- worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`Build worker exited without a result (code ${code}).`)));
95
- });
96
- result.catch(() => {});
97
- return {
98
- worker,
99
- result,
100
- directory
101
- };
102
- }
103
- /** Internal watch session. Preload libraries, but never reuse a user module cache. */
104
- function createDeckBuilder(entry, keepWarm = true) {
105
- const compiler = createDeckCompiler(entry);
106
- compiler.catch(() => {});
107
- let prepared = prepareWorker();
108
- let cache;
109
- let closed = false;
110
- let active = false;
111
- return {
112
- async build() {
113
- if (closed || active) throw new Error("Deck builder is closed or already building.");
114
- active = true;
115
- const current = prepared ?? prepareWorker();
116
- prepared = current;
117
- let timer;
118
- try {
119
- const built = await Promise.race([(async () => {
120
- const dependencies = await (await compiler).build(join(current.directory, "deck.mjs"));
121
- current.worker.postMessage({
122
- dependencies,
123
- cache
124
- });
125
- return current.result;
126
- })(), new Promise((_, reject) => {
127
- timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Build exceeded 60 seconds. Check the deck for loops or unresolved promises.")), BUILD_TIMEOUT_MS);
128
- })]);
129
- cache = built.cache;
130
- return built.result;
131
- } finally {
132
- clearTimeout(timer);
133
- await compiler.then((compiler) => compiler.cancel(), () => {});
134
- await current.worker.terminate();
135
- await rm(current.directory, {
136
- recursive: true,
137
- force: true
138
- });
139
- active = false;
140
- prepared = !closed && keepWarm ? prepareWorker() : void 0;
141
- }
142
- },
143
- async cancel() {
144
- if (!active) return;
145
- const current = prepared;
146
- await compiler.then((compiler) => compiler.cancel(), () => {});
147
- await current?.worker.terminate();
148
- },
149
- async close() {
150
- closed = true;
151
- const current = prepared;
152
- await compiler.then((compiler) => compiler.cancel(), () => {});
153
- await current?.worker.terminate();
154
- await compiler.then((compiler) => compiler.close(), () => {});
155
- if (current) await rm(current.directory, {
156
- recursive: true,
157
- force: true
158
- });
159
- }
160
- };
161
- }
162
- //#endregion
163
- //#region ../../src/internal/opc/zip.ts
164
- const STORE_BY_EXTENSION = new Set([
165
- "png",
166
- "jpg",
167
- "jpeg",
168
- "gif",
169
- "bmp",
170
- "tif",
171
- "tiff",
172
- "webp",
173
- "mp3",
174
- "mp4",
175
- "m4a",
176
- "m4v",
177
- "wav",
178
- "wmv",
179
- "mov",
180
- "avi",
181
- "webm",
182
- "ogg",
183
- "wma",
184
- "pdf",
185
- "zip",
186
- "gz",
187
- "7z",
188
- "rar"
189
- ]);
190
- const extensionOf = (name) => {
191
- const dot = name.lastIndexOf(".");
192
- return dot < 0 ? "" : name.slice(dot + 1).toLowerCase();
193
- };
194
- /**
195
- * Returns true if the entry should be stored uncompressed. PPTX writers
196
- * conventionally STORE already-compressed payloads to avoid wasting CPU on
197
- * negligible savings.
198
- */
199
- const shouldStore = (name) => STORE_BY_EXTENSION.has(extensionOf(name));
200
- /**
201
- * Reads a ZIP archive (a `.pptx` file is a ZIP) and returns entries in
202
- * central-directory order. Throws if `input` is not a valid ZIP.
203
- */
204
- const readZip = (input) => {
205
- const obj = unzipSync(input);
206
- const entries = [];
207
- for (const name of Object.keys(obj)) {
208
- const data = obj[name];
209
- if (data === void 0) continue;
210
- entries.push({
211
- name,
212
- data
213
- });
214
- }
215
- return { entries };
216
- };
217
- const writeZip = (entries, options = {}) => {
218
- const defaultLevel = options.level ?? 6;
219
- const obj = {};
220
- for (const e of entries) {
221
- const level = shouldStore(e.name) ? 0 : defaultLevel;
222
- obj[e.name] = [e.data, { level }];
223
- }
224
- return zipSync(obj);
225
- };
226
- //#endregion
227
- //#region ../../src/internal/opc/part-name.ts
228
- /**
229
- * Asserts the given string is a syntactically valid OPC part name and returns
230
- * it as a branded `PartName`. Throws otherwise.
231
- *
232
- * Rules enforced (subset; see ECMA-376 Part 2 §9.1.1.1-9.1.1.6):
233
- *
234
- * - Must start with `/`.
235
- * - Must not end with `/`.
236
- * - Must not contain `//` (empty segments).
237
- * - Each segment must be non-empty and must not be `.` or `..`.
238
- */
239
- const partName = (raw) => {
240
- if (raw === "") throw new Error("part name is empty");
241
- if (!raw.startsWith("/")) throw new Error(`part name must start with "/": ${raw}`);
242
- if (raw.length > 1 && raw.endsWith("/")) throw new Error(`part name must not end with "/": ${raw}`);
243
- const segments = raw.slice(1).split("/");
244
- for (const s of segments) {
245
- if (s === "") throw new Error(`part name contains empty segment: ${raw}`);
246
- if (s === "." || s === "..") throw new Error(`part name contains "." or ".." segment: ${raw}`);
247
- }
248
- return raw;
249
- };
250
- /** Convert a part name to the corresponding ZIP entry path (no leading slash). */
251
- const toZipPath = (name) => name.slice(1);
252
- /** Convert a ZIP entry path to a part name (adds leading slash). */
253
- const fromZipPath = (zipPath) => partName(`/${zipPath}`);
254
- /**
255
- * Returns the directory portion of a part name (everything before the final
256
- * `/`). The result keeps the leading slash but has no trailing slash, except
257
- * for the root which returns `/`.
258
- */
259
- const dirname$1 = (name) => {
260
- const idx = name.lastIndexOf("/");
261
- if (idx <= 0) return "/";
262
- return name.slice(0, idx);
263
- };
264
- /**
265
- * Resolves a relationship Target (which may be relative) against a base part
266
- * name. Returns the absolute part name (always starts with `/`).
267
- *
268
- * `target` is from a Relationship element's `Target` attribute when
269
- * `TargetMode="Internal"`. External targets must not be passed through here.
270
- */
271
- const resolveTarget = (basePart, target) => {
272
- if (target === "") throw new Error("relationship target is empty");
273
- if (target.startsWith("/")) return partName(target);
274
- const baseDir = dirname$1(basePart);
275
- const stack = baseDir === "/" ? [] : baseDir.slice(1).split("/");
276
- for (const segment of target.split("/")) {
277
- if (segment === "" || segment === ".") continue;
278
- if (segment === "..") {
279
- if (stack.length === 0) throw new Error(`relationship target "${target}" escapes the package root`);
280
- stack.pop();
281
- continue;
282
- }
283
- stack.push(segment);
284
- }
285
- return partName(`/${stack.join("/")}`);
286
- };
287
- //#endregion
288
- //#region ../../src/internal/xml/ast.ts
289
- const qname = (prefix, localName, namespaceURI) => ({
290
- prefix,
291
- localName,
292
- namespaceURI
293
- });
294
- const attr = (name, value) => ({
295
- name,
296
- value
297
- });
298
- const elem = (name, init = {}) => ({
299
- kind: "element",
300
- name,
301
- attrs: init.attrs ?? [],
302
- prefixDecls: init.prefixDecls ?? /* @__PURE__ */ new Map(),
303
- children: init.children ?? []
304
- });
305
- const text = (data) => ({
306
- kind: "text",
307
- data
308
- });
309
- const cdata = (data) => ({
310
- kind: "cdata",
311
- data
312
- });
313
- const comment = (data) => ({
314
- kind: "comment",
315
- data
316
- });
317
- const pi = (target, data) => ({
318
- kind: "pi",
319
- target,
320
- data
321
- });
322
- //#endregion
323
- //#region ../../src/internal/xml/namespaces.ts
324
- const NS = {
325
- contentTypes: "http://schemas.openxmlformats.org/package/2006/content-types",
326
- relationships: "http://schemas.openxmlformats.org/package/2006/relationships",
327
- officeDocRels: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
328
- pml: "http://schemas.openxmlformats.org/presentationml/2006/main",
329
- dml: "http://schemas.openxmlformats.org/drawingml/2006/main",
330
- diagram: "http://schemas.openxmlformats.org/drawingml/2006/diagram",
331
- chart: "http://schemas.openxmlformats.org/drawingml/2006/chart",
332
- chartDrawing: "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
333
- sml: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
334
- wml: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
335
- mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
336
- a14: "http://schemas.microsoft.com/office/drawing/2010/main",
337
- a15: "http://schemas.microsoft.com/office/drawing/2012/main",
338
- a16: "http://schemas.microsoft.com/office/drawing/2014/main",
339
- p14: "http://schemas.microsoft.com/office/powerpoint/2010/main",
340
- p15: "http://schemas.microsoft.com/office/powerpoint/2012/main",
341
- p16: "http://schemas.microsoft.com/office/powerpoint/2015/main",
342
- xml: "http://www.w3.org/XML/1998/namespace",
343
- xmlns: "http://www.w3.org/2000/xmlns/"
344
- };
345
- NS.contentTypes, NS.relationships, NS.officeDocRels, NS.pml, NS.dml, NS.diagram, NS.chart, NS.chartDrawing, NS.sml, NS.wml, NS.mc, NS.a14, NS.a15, NS.a16, NS.p14, NS.p15, NS.p16, NS.xml;
346
- //#endregion
347
- //#region ../../src/internal/xml/parse.ts
348
- var XmlParseError = class extends Error {
349
- offset;
350
- line;
351
- column;
352
- constructor(message, offset, line, column) {
353
- super(`${message} at line ${line}:${column} (offset ${offset})`);
354
- this.offset = offset;
355
- this.line = line;
356
- this.column = column;
357
- this.name = "XmlParseError";
358
- }
359
- };
360
- const isNameStartChar = (c) => {
361
- const code = c.charCodeAt(0);
362
- return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 58;
363
- };
364
- const isNameChar = (c) => {
365
- if (isNameStartChar(c)) return true;
366
- const code = c.charCodeAt(0);
367
- return code >= 48 && code <= 57 || code === 45 || code === 46;
368
- };
369
- const isWhitespace = (c) => c === " " || c === " " || c === "\n" || c === "\r";
370
- const lineColOf = (src, pos) => {
371
- let line = 1;
372
- let column = 1;
373
- for (let i = 0; i < pos && i < src.length; i++) if (src.charCodeAt(i) === 10) {
374
- line++;
375
- column = 1;
376
- } else column++;
377
- return {
378
- line,
379
- column
380
- };
381
- };
382
- const fail = (cur, message) => {
383
- const { line, column } = lineColOf(cur.src, cur.pos);
384
- throw new XmlParseError(message, cur.pos, line, column);
385
- };
386
- const expect = (cur, literal) => {
387
- if (cur.src.slice(cur.pos, cur.pos + literal.length) !== literal) fail(cur, `expected "${literal}"`);
388
- cur.pos += literal.length;
389
- };
390
- const accept = (cur, literal) => {
391
- if (cur.src.slice(cur.pos, cur.pos + literal.length) !== literal) return false;
392
- cur.pos += literal.length;
393
- return true;
394
- };
395
- const skipWhitespace = (cur) => {
396
- while (cur.pos < cur.src.length && isWhitespace(cur.src[cur.pos] ?? "")) cur.pos++;
397
- };
398
- const readName = (cur) => {
399
- const start = cur.pos;
400
- if (cur.pos >= cur.src.length || !isNameStartChar(cur.src[cur.pos] ?? "")) fail(cur, "expected XML name");
401
- cur.pos++;
402
- while (cur.pos < cur.src.length && isNameChar(cur.src[cur.pos] ?? "")) cur.pos++;
403
- return cur.src.slice(start, cur.pos);
404
- };
405
- const splitQName = (raw) => {
406
- const idx = raw.indexOf(":");
407
- if (idx < 0) return {
408
- prefix: "",
409
- localName: raw
410
- };
411
- return {
412
- prefix: raw.slice(0, idx),
413
- localName: raw.slice(idx + 1)
414
- };
415
- };
416
- const decodeRefs = (cur, raw) => {
417
- let out = "";
418
- let i = 0;
419
- while (i < raw.length) {
420
- if (raw.charCodeAt(i) !== 38) {
421
- out += raw[i];
422
- i++;
423
- continue;
424
- }
425
- const end = raw.indexOf(";", i + 1);
426
- if (end < 0) {
427
- cur.pos -= raw.length - i;
428
- fail(cur, "unterminated entity reference");
429
- }
430
- const ref = raw.slice(i + 1, end);
431
- if (ref === "amp") out += "&";
432
- else if (ref === "lt") out += "<";
433
- else if (ref === "gt") out += ">";
434
- else if (ref === "quot") out += "\"";
435
- else if (ref === "apos") out += "'";
436
- else if (ref.startsWith("#x") || ref.startsWith("#X")) {
437
- const code = Number.parseInt(ref.slice(2), 16);
438
- if (!Number.isFinite(code)) fail(cur, `invalid hex numeric reference: &${ref};`);
439
- out += String.fromCodePoint(code);
440
- } else if (ref.startsWith("#")) {
441
- const code = Number.parseInt(ref.slice(1), 10);
442
- if (!Number.isFinite(code)) fail(cur, `invalid decimal numeric reference: &${ref};`);
443
- out += String.fromCodePoint(code);
444
- } else fail(cur, `unknown entity reference: &${ref};`);
445
- i = end + 1;
446
- }
447
- return out;
448
- };
449
- const readAttrValue = (cur) => {
450
- const quote = cur.src[cur.pos];
451
- if (quote !== "\"" && quote !== "'") fail(cur, "expected attribute value");
452
- cur.pos++;
453
- const start = cur.pos;
454
- while (cur.pos < cur.src.length && cur.src[cur.pos] !== quote) {
455
- if (cur.src.charCodeAt(cur.pos) === 60) fail(cur, "\"<\" is not allowed inside an attribute value");
456
- cur.pos++;
457
- }
458
- if (cur.pos >= cur.src.length) fail(cur, "unterminated attribute value");
459
- const raw = cur.src.slice(start, cur.pos);
460
- cur.pos++;
461
- return decodeRefs(cur, raw);
462
- };
463
- const xmlScope = {
464
- parent: null,
465
- bindings: new Map([["xml", NS.xml]])
466
- };
467
- const resolvePrefix = (scope, prefix) => {
468
- let cursor = scope;
469
- while (cursor) {
470
- const found = cursor.bindings.get(prefix);
471
- if (found !== void 0) return found;
472
- cursor = cursor.parent;
473
- }
474
- return null;
475
- };
476
- const resolveQName = (cur, scope, raw, isAttribute) => {
477
- const { prefix, localName } = splitQName(raw);
478
- let namespaceURI = "";
479
- if (prefix !== "") {
480
- const found = resolvePrefix(scope, prefix);
481
- if (found === null) return fail(cur, `unbound namespace prefix "${prefix}"`);
482
- namespaceURI = found;
483
- } else if (!isAttribute) namespaceURI = resolvePrefix(scope, "") ?? "";
484
- return qname(prefix, localName, namespaceURI);
485
- };
486
- const parseDeclaration = (cur) => {
487
- if (!accept(cur, "<?xml")) return null;
488
- const end = cur.src.indexOf("?>", cur.pos);
489
- if (end < 0) fail(cur, "unterminated XML declaration");
490
- const body = cur.src.slice(cur.pos, end);
491
- cur.pos = end + 2;
492
- const get = (key) => {
493
- return body.match(new RegExp(`\\b${key}\\s*=\\s*(['"])(.*?)\\1`))?.[2];
494
- };
495
- const version = get("version");
496
- if (version === void 0) return fail(cur, "XML declaration missing \"version\"");
497
- const encoding = get("encoding");
498
- const standaloneRaw = get("standalone");
499
- let standalone;
500
- if (standaloneRaw === "yes" || standaloneRaw === "no") standalone = standaloneRaw;
501
- else if (standaloneRaw !== void 0) return fail(cur, `invalid standalone value "${standaloneRaw}"`);
502
- return {
503
- version,
504
- ...encoding !== void 0 ? { encoding } : {},
505
- ...standalone !== void 0 ? { standalone } : {}
506
- };
507
- };
508
- const parseComment = (cur) => {
509
- expect(cur, "<!--");
510
- const end = cur.src.indexOf("-->", cur.pos);
511
- if (end < 0) fail(cur, "unterminated comment");
512
- const data = cur.src.slice(cur.pos, end);
513
- if (data.includes("--")) fail(cur, "\"--\" is not allowed inside an XML comment");
514
- cur.pos = end + 3;
515
- return comment(data);
516
- };
517
- const parsePI = (cur) => {
518
- expect(cur, "<?");
519
- const targetStart = cur.pos;
520
- while (cur.pos < cur.src.length && !isWhitespace(cur.src[cur.pos] ?? "") && cur.src.slice(cur.pos, cur.pos + 2) !== "?>") cur.pos++;
521
- const target = cur.src.slice(targetStart, cur.pos);
522
- if (target.toLowerCase() === "xml") fail(cur, "PI target may not be \"xml\"");
523
- skipWhitespace(cur);
524
- const end = cur.src.indexOf("?>", cur.pos);
525
- if (end < 0) fail(cur, "unterminated processing instruction");
526
- const data = cur.src.slice(cur.pos, end);
527
- cur.pos = end + 2;
528
- return pi(target, data);
529
- };
530
- const parseCData = (cur) => {
531
- expect(cur, "<![CDATA[");
532
- const end = cur.src.indexOf("]]>", cur.pos);
533
- if (end < 0) fail(cur, "unterminated CDATA section");
534
- const data = cur.src.slice(cur.pos, end);
535
- cur.pos = end + 3;
536
- return cdata(data);
537
- };
538
- const skipDoctype = (cur) => {
539
- expect(cur, "<!DOCTYPE");
540
- while (cur.pos < cur.src.length) {
541
- const c = cur.src[cur.pos];
542
- if (c === "[") fail(cur, "DOCTYPE internal subset is not supported");
543
- if (c === ">") {
544
- cur.pos++;
545
- return;
546
- }
547
- cur.pos++;
548
- }
549
- fail(cur, "unterminated DOCTYPE");
550
- };
551
- const parseStartOrEmptyTag = (cur, parentScope) => {
552
- expect(cur, "<");
553
- const rawName = readName(cur);
554
- const ownAttrs = [];
555
- const prefixDecls = /* @__PURE__ */ new Map();
556
- let empty = false;
557
- while (true) {
558
- const wsStart = cur.pos;
559
- skipWhitespace(cur);
560
- if (cur.pos === wsStart && cur.src[cur.pos] !== "/" && cur.src[cur.pos] !== ">") fail(cur, "expected whitespace before next attribute");
561
- if (accept(cur, "/>")) {
562
- empty = true;
563
- break;
564
- }
565
- if (accept(cur, ">")) break;
566
- const attrRawName = readName(cur);
567
- skipWhitespace(cur);
568
- expect(cur, "=");
569
- skipWhitespace(cur);
570
- const value = readAttrValue(cur);
571
- if (attrRawName === "xmlns") prefixDecls.set("", value);
572
- else if (attrRawName.startsWith("xmlns:")) {
573
- const p = attrRawName.slice(6);
574
- if (p === "") fail(cur, "empty namespace prefix in xmlns:");
575
- if (p === "xmlns") fail(cur, "prefix \"xmlns\" cannot be redeclared");
576
- prefixDecls.set(p, value);
577
- } else ownAttrs.push({
578
- raw: attrRawName,
579
- rawValue: value
580
- });
581
- }
582
- const scope = {
583
- parent: parentScope,
584
- bindings: new Map(prefixDecls)
585
- };
586
- return {
587
- element: elem(resolveQName(cur, scope, rawName, false), {
588
- attrs: ownAttrs.map(({ raw, rawValue }) => attr(resolveQName(cur, scope, raw, true), rawValue)),
589
- prefixDecls,
590
- children: []
591
- }),
592
- scope,
593
- empty
594
- };
595
- };
596
- const parseEndTag = (cur) => {
597
- expect(cur, "</");
598
- const name = readName(cur);
599
- skipWhitespace(cur);
600
- expect(cur, ">");
601
- return name;
602
- };
603
- const parseTextContent = (cur) => {
604
- const start = cur.pos;
605
- while (cur.pos < cur.src.length && cur.src.charCodeAt(cur.pos) !== 60) cur.pos++;
606
- return decodeRefs(cur, cur.src.slice(start, cur.pos));
607
- };
608
- const parseElement = (cur, parentScope) => {
609
- const { element, scope, empty } = parseStartOrEmptyTag(cur, parentScope);
610
- if (empty) return element;
611
- while (cur.pos < cur.src.length) if (cur.src.charCodeAt(cur.pos) === 60) {
612
- if (cur.src.startsWith("</", cur.pos)) {
613
- const closingRaw = parseEndTag(cur);
614
- const expectedRaw = element.name.prefix === "" ? element.name.localName : `${element.name.prefix}:${element.name.localName}`;
615
- if (closingRaw !== expectedRaw) fail(cur, `mismatched closing tag: expected </${expectedRaw}>, got </${closingRaw}>`);
616
- return element;
617
- }
618
- if (cur.src.startsWith("<![CDATA[", cur.pos)) element.children.push(parseCData(cur));
619
- else if (cur.src.startsWith("<!--", cur.pos)) element.children.push(parseComment(cur));
620
- else if (cur.src.startsWith("<?", cur.pos)) element.children.push(parsePI(cur));
621
- else element.children.push(parseElement(cur, scope));
622
- } else {
623
- const t = parseTextContent(cur);
624
- if (t.length > 0) element.children.push(text(t));
625
- }
626
- return fail(cur, `unterminated element <${element.name.localName}>`);
627
- };
628
- const parseXml = (src) => {
629
- const cur = {
630
- src: (src.charCodeAt(0) === 65279 ? src.slice(1) : src).replace(/\r\n?/g, "\n"),
631
- pos: 0
632
- };
633
- const decl = parseDeclaration(cur);
634
- skipWhitespace(cur);
635
- const prolog = [];
636
- while (cur.pos < cur.src.length) {
637
- skipWhitespace(cur);
638
- if (cur.src.startsWith("<!--", cur.pos)) prolog.push(parseComment(cur));
639
- else if (cur.src.startsWith("<!DOCTYPE", cur.pos)) skipDoctype(cur);
640
- else if (cur.src.startsWith("<?", cur.pos)) prolog.push(parsePI(cur));
641
- else break;
642
- }
643
- skipWhitespace(cur);
644
- if (cur.pos >= cur.src.length || cur.src[cur.pos] !== "<") fail(cur, "expected root element");
645
- const root = parseElement(cur, xmlScope);
646
- const epilog = [];
647
- while (cur.pos < cur.src.length) {
648
- skipWhitespace(cur);
649
- if (cur.pos >= cur.src.length) break;
650
- if (cur.src.startsWith("<!--", cur.pos)) epilog.push(parseComment(cur));
651
- else if (cur.src.startsWith("<?", cur.pos)) epilog.push(parsePI(cur));
652
- else fail(cur, "unexpected content after root element");
653
- }
654
- return {
655
- kind: "document",
656
- decl,
657
- prolog,
658
- root,
659
- epilog
660
- };
661
- };
662
- //#endregion
663
- //#region ../../src/internal/xml/query.ts
664
- const isElement = (n) => n.kind === "element";
665
- /**
666
- * Walks the element tree depth-first, yielding every element including the
667
- * root. The visitor returns `false` to skip descending into the current
668
- * element's children. Returning nothing (or `true`) descends as usual.
669
- */
670
- const walkElements = (root, visit) => {
671
- const stack = [root];
672
- while (stack.length > 0) {
673
- const node = stack.pop();
674
- if (!node) continue;
675
- if (visit(node) === false) continue;
676
- for (let i = node.children.length - 1; i >= 0; i--) {
677
- const c = node.children[i];
678
- if (c && isElement(c)) stack.push(c);
679
- }
680
- }
681
- };
682
- //#endregion
683
- //#region ../../src/internal/xml/serialize.ts
684
- const rejectForbiddenChar = (c) => {
685
- const control = c < 32 && c !== 9 && c !== 10 && c !== 13;
686
- if (control || c >= 55296 && c <= 57343 || c === 65534 || c === 65535) {
687
- const hex = c.toString(16).toUpperCase().padStart(4, "0");
688
- throw new Error(`XML-illegal ${control ? "control character" : "character"} U+${hex} in text; strip ${control ? "control" : "illegal"} characters before authoring`);
689
- }
690
- };
691
- const escapeAttr = (s) => {
692
- let out = "";
693
- for (const char of s) {
694
- const c = char.codePointAt(0);
695
- rejectForbiddenChar(c);
696
- if (c === 38) out += "&amp;";
697
- else if (c === 60) out += "&lt;";
698
- else if (c === 62) out += "&gt;";
699
- else if (c === 34) out += "&quot;";
700
- else if (c === 9) out += "&#9;";
701
- else if (c === 10) out += "&#10;";
702
- else if (c === 13) out += "&#13;";
703
- else out += char;
704
- }
705
- return out;
706
- };
707
- const escapeText = (s) => {
708
- let out = "";
709
- for (const char of s) {
710
- const c = char.codePointAt(0);
711
- rejectForbiddenChar(c);
712
- if (c === 38) out += "&amp;";
713
- else if (c === 60) out += "&lt;";
714
- else if (c === 62) out += "&gt;";
715
- else if (c === 13) out += "&#13;";
716
- else out += char;
717
- }
718
- return out;
719
- };
720
- const rawName = (prefix, localName) => prefix === "" ? localName : `${prefix}:${localName}`;
721
- const writeAttr = (a) => {
722
- return ` ${rawName(a.name.prefix, a.name.localName)}="${escapeAttr(a.value)}"`;
723
- };
724
- const writeElement = (e, parts) => {
725
- const name = rawName(e.name.prefix, e.name.localName);
726
- parts.push("<");
727
- parts.push(name);
728
- for (const [prefix, uri] of e.prefixDecls) {
729
- const decl = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
730
- parts.push(` ${decl}="${escapeAttr(uri)}"`);
731
- }
732
- for (const a of e.attrs) parts.push(writeAttr(a));
733
- if (e.children.length === 0) {
734
- parts.push("/>");
735
- return;
736
- }
737
- parts.push(">");
738
- for (const child of e.children) writeNode(child, parts);
739
- parts.push("</");
740
- parts.push(name);
741
- parts.push(">");
742
- };
743
- const writeNode = (n, parts) => {
744
- switch (n.kind) {
745
- case "element":
746
- writeElement(n, parts);
747
- return;
748
- case "text":
749
- parts.push(escapeText(n.data));
750
- return;
751
- case "cdata":
752
- parts.push("<![CDATA[");
753
- parts.push(n.data.split("]]>").join("]]]]><![CDATA[>"));
754
- parts.push("]]>");
755
- return;
756
- case "comment":
757
- parts.push("<!--");
758
- parts.push(n.data);
759
- parts.push("-->");
760
- return;
761
- case "pi":
762
- parts.push("<?");
763
- parts.push(n.target);
764
- if (n.data.length > 0) {
765
- parts.push(" ");
766
- parts.push(n.data);
767
- }
768
- parts.push("?>");
769
- return;
770
- }
771
- };
772
- const writeDeclaration = (doc, parts) => {
773
- if (!doc.decl) return;
774
- parts.push("<?xml version=\"");
775
- parts.push(escapeAttr(doc.decl.version));
776
- parts.push("\"");
777
- if (doc.decl.encoding !== void 0) {
778
- parts.push(" encoding=\"");
779
- parts.push(escapeAttr(doc.decl.encoding));
780
- parts.push("\"");
781
- }
782
- if (doc.decl.standalone !== void 0) {
783
- parts.push(" standalone=\"");
784
- parts.push(doc.decl.standalone);
785
- parts.push("\"");
786
- }
787
- parts.push("?>");
788
- };
789
- const serializeXml = (doc, options = {}) => {
790
- const parts = [];
791
- if (doc.decl) writeDeclaration(doc, parts);
792
- else if (options.standaloneDeclaration ?? true) parts.push("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
793
- for (const p of doc.prolog) writeNode(p, parts);
794
- writeElement(doc.root, parts);
795
- for (const e of doc.epilog) writeNode(e, parts);
796
- return parts.join("");
797
- };
798
- const serializeFragment = (element) => {
799
- const parts = [];
800
- writeElement(element, parts);
801
- return parts.join("");
802
- };
803
- //#endregion
804
- //#region ../../src/internal/opc/content-types.ts
805
- const CT_NS = NS.contentTypes;
806
- const NAME_TYPES = qname("", "Types", CT_NS);
807
- const NAME_DEFAULT = qname("", "Default", CT_NS);
808
- const NAME_OVERRIDE = qname("", "Override", CT_NS);
809
- const ATTR_EXTENSION = qname("", "Extension", "");
810
- const ATTR_CONTENT_TYPE = qname("", "ContentType", "");
811
- const ATTR_PART_NAME = qname("", "PartName", "");
812
- const getAttrValueRaw = (e, local) => {
813
- for (const a of e.attrs) if (a.name.localName === local) return a.value;
814
- return null;
815
- };
816
- const parseContentTypes = (xml) => {
817
- const doc = parseXml(xml);
818
- if (doc.root.name.namespaceURI !== CT_NS || doc.root.name.localName !== "Types") throw new Error(`expected root <Types xmlns="${CT_NS}">, got <${doc.root.name.localName}>`);
819
- const defaults = [];
820
- const overrides = [];
821
- for (const child of doc.root.children) {
822
- if (child.kind !== "element") continue;
823
- if (child.name.namespaceURI !== CT_NS) continue;
824
- if (child.name.localName === "Default") {
825
- const ext = getAttrValueRaw(child, "Extension");
826
- const ct = getAttrValueRaw(child, "ContentType");
827
- if (ext === null || ct === null) throw new Error("Content_Types Default missing Extension or ContentType");
828
- defaults.push({
829
- extension: ext.toLowerCase(),
830
- contentType: ct
831
- });
832
- } else if (child.name.localName === "Override") {
833
- const pn = getAttrValueRaw(child, "PartName");
834
- const ct = getAttrValueRaw(child, "ContentType");
835
- if (pn === null || ct === null) throw new Error("Content_Types Override missing PartName or ContentType");
836
- overrides.push({
837
- partName: partName(pn),
838
- contentType: ct
839
- });
840
- }
841
- }
842
- return {
843
- defaults,
844
- overrides
845
- };
846
- };
847
- const serializeContentTypes = (ct) => {
848
- const children = [...ct.defaults.map((d) => elem(NAME_DEFAULT, { attrs: [{
849
- name: ATTR_EXTENSION,
850
- value: d.extension
851
- }, {
852
- name: ATTR_CONTENT_TYPE,
853
- value: d.contentType
854
- }] })), ...ct.overrides.map((o) => elem(NAME_OVERRIDE, { attrs: [{
855
- name: ATTR_PART_NAME,
856
- value: o.partName
857
- }, {
858
- name: ATTR_CONTENT_TYPE,
859
- value: o.contentType
860
- }] }))];
861
- return serializeXml({
862
- kind: "document",
863
- decl: {
864
- version: "1.0",
865
- encoding: "UTF-8",
866
- standalone: "yes"
867
- },
868
- prolog: [],
869
- root: elem(NAME_TYPES, {
870
- prefixDecls: new Map([["", CT_NS]]),
871
- children
872
- }),
873
- epilog: []
874
- });
875
- };
876
- //#endregion
877
- //#region ../../src/internal/opc/rels.ts
878
- const REL_NS = NS.relationships;
879
- const NAME_RELATIONSHIPS = qname("", "Relationships", REL_NS);
880
- const NAME_RELATIONSHIP = qname("", "Relationship", REL_NS);
881
- const ATTR_ID = qname("", "Id", "");
882
- const ATTR_TYPE = qname("", "Type", "");
883
- const ATTR_TARGET = qname("", "Target", "");
884
- const ATTR_TARGET_MODE = qname("", "TargetMode", "");
885
- const findAttr = (e, local) => {
886
- for (const a of e.attrs) if (a.name.localName === local) return a.value;
887
- return null;
888
- };
889
- const parseRels = (xml) => {
890
- const doc = parseXml(xml);
891
- if (doc.root.name.namespaceURI !== REL_NS || doc.root.name.localName !== "Relationships") throw new Error(`expected root <Relationships xmlns="${REL_NS}">, got <${doc.root.name.localName}>`);
892
- const items = [];
893
- for (const child of doc.root.children) {
894
- if (child.kind !== "element") continue;
895
- if (child.name.namespaceURI !== REL_NS || child.name.localName !== "Relationship") continue;
896
- const id = findAttr(child, "Id");
897
- const type = findAttr(child, "Type");
898
- const target = findAttr(child, "Target");
899
- if (id === null || type === null || target === null) throw new Error("Relationship missing Id, Type, or Target");
900
- const modeRaw = findAttr(child, "TargetMode");
901
- let targetMode = "Internal";
902
- if (modeRaw !== null) {
903
- if (modeRaw !== "Internal" && modeRaw !== "External") throw new Error(`invalid TargetMode "${modeRaw}"`);
904
- targetMode = modeRaw;
905
- }
906
- items.push({
907
- id,
908
- type,
909
- target,
910
- targetMode
911
- });
912
- }
913
- return { items };
914
- };
915
- const serializeRels = (rels) => {
916
- const children = rels.items.map((r) => {
917
- const attrs = [
918
- {
919
- name: ATTR_ID,
920
- value: r.id
921
- },
922
- {
923
- name: ATTR_TYPE,
924
- value: r.type
925
- },
926
- {
927
- name: ATTR_TARGET,
928
- value: r.target
929
- }
930
- ];
931
- if (r.targetMode === "External") attrs.push({
932
- name: ATTR_TARGET_MODE,
933
- value: "External"
934
- });
935
- return elem(NAME_RELATIONSHIP, { attrs });
936
- });
937
- return serializeXml({
938
- kind: "document",
939
- decl: {
940
- version: "1.0",
941
- encoding: "UTF-8",
942
- standalone: "yes"
943
- },
944
- prolog: [],
945
- root: elem(NAME_RELATIONSHIPS, {
946
- prefixDecls: new Map([["", REL_NS]]),
947
- children
948
- }),
949
- epilog: []
950
- });
951
- };
952
- /**
953
- * Convenience: an empty Relationships value with no items.
954
- */
955
- const emptyRels = () => ({ items: [] });
956
- //#endregion
957
- //#region src/fingerprint.ts
958
- const CORE_PROPERTIES = "docProps/core.xml";
959
- const CORE_TIMESTAMPS = /<(dcterms:(?:created|modified))\b[^>]*>[\s\S]*?<\/\1>/g;
960
- /** Every build stamps fresh creation and modification times into the core properties. */
961
- function comparablePart(name, bytes) {
962
- if (name !== CORE_PROPERTIES) return bytes;
963
- return strToU8(strFromU8(bytes).replace(CORE_TIMESTAMPS, ""));
964
- }
965
- /** ZIP container dates and generated core timestamps are not source edits. */
966
- function sourceFingerprint(bytes) {
967
- const parts = unzipSync(bytes);
968
- const hash = createHash("sha256");
969
- for (const name of Object.keys(parts).sort()) {
970
- const content = comparablePart(name, parts[name]);
971
- hash.update(JSON.stringify([name, createHash("sha256").update(content).digest("hex")]));
972
- }
973
- return hash.digest("hex");
974
- }
975
- //#endregion
976
- //#region src/deck-merge.ts
977
- const CONTENT_TYPES = "[Content_Types].xml";
978
- const PRESENTATION = "ppt/presentation.xml";
979
- const SHAPE_TREE_PART = /^ppt\/(?:slides|slideLayouts|slideMasters|notesSlides|notesMasters|handoutMasters)\/[^/]+\.xml$/;
980
- const RELATIONSHIPS_PART = /(?:^|\/)_rels\/[^/]*\.rels$/;
981
- const SLIDE_RELATIONSHIP = `${NS.officeDocRels}/slide`;
982
- const NOTES_RELATIONSHIP = `${NS.officeDocRels}/notesSlide`;
983
- const SHAPE_TREE_PROPERTIES = new Set([
984
- "nvGrpSpPr",
985
- "grpSpPr",
986
- "extLst"
987
- ]);
988
- function sameBytes(a, b) {
989
- if (a.length !== b.length) return false;
990
- for (let index = 0; index < a.length; index++) if (a[index] !== b[index]) return false;
991
- return true;
992
- }
993
- /**
994
- * Three-way merge of keyed items. An item changed on one side only takes that
995
- * side; an item changed differently on both sides is a conflict.
996
- */
997
- function mergeEntries(base, ours, theirs, options) {
998
- const byKey = (entries) => new Map(entries.map((entry) => [entry.key, entry]));
999
- const [b, o, t] = [
1000
- byKey(base),
1001
- byKey(ours),
1002
- byKey(theirs)
1003
- ];
1004
- const keys = new Set([
1005
- ...t.keys(),
1006
- ...o.keys(),
1007
- ...b.keys()
1008
- ]);
1009
- const chosen = /* @__PURE__ */ new Map();
1010
- const conflicts = [];
1011
- const same = (x, y) => x?.value === y?.value;
1012
- for (const key of keys) {
1013
- const [be, oe, te] = [
1014
- b.get(key),
1015
- o.get(key),
1016
- t.get(key)
1017
- ];
1018
- let pick;
1019
- if (same(oe, be)) pick = te;
1020
- else if (same(te, be) || same(oe, te)) pick = oe;
1021
- else {
1022
- const refined = be && oe && te ? options.refine?.(key, be.item, oe.item, te.item) : null;
1023
- if (refined?.ok) chosen.set(key, refined.value);
1024
- else if (refined) conflicts.push(...refined.conflicts);
1025
- else {
1026
- const reason = !be ? "both-added" : !oe || !te ? "deleted-and-changed" : "both-changed";
1027
- conflicts.push(options.conflict(key, reason, (oe ?? te ?? be).item));
1028
- }
1029
- continue;
1030
- }
1031
- if (pick) chosen.set(key, pick.item);
1032
- }
1033
- if (conflicts.length) return {
1034
- ok: false,
1035
- conflicts
1036
- };
1037
- const order = mergeOrder(base.map((entry) => entry.key), ours.map((entry) => entry.key), theirs.map((entry) => entry.key), new Set(chosen.keys()), options.ordering);
1038
- if (!order) return {
1039
- ok: false,
1040
- conflicts: [options.conflict("", "both-changed")]
1041
- };
1042
- return {
1043
- ok: true,
1044
- value: order.map((key) => chosen.get(key))
1045
- };
1046
- }
1047
- /** True when the keys both lists share appear in the same order in each. */
1048
- function sameRelativeOrder(a, b) {
1049
- const [inA, inB] = [new Set(a), new Set(b)];
1050
- const sharedA = a.filter((key) => inB.has(key));
1051
- const sharedB = b.filter((key) => inA.has(key));
1052
- return sharedA.every((key, index) => key === sharedB[index]);
1053
- }
1054
- /**
1055
- * Orders the kept keys. The side that reordered (or the source when neither
1056
- * did) gives the order; the other side's additions follow their nearest
1057
- * preceding sibling. `null` means the order cannot be decided.
1058
- */
1059
- function mergeOrder(base, ours, theirs, keep, ordering) {
1060
- const oursMoved = ordering !== "none" && !sameRelativeOrder(base, ours);
1061
- const theirsMoved = ordering !== "none" && !sameRelativeOrder(base, theirs);
1062
- if (oursMoved && theirsMoved && !sameRelativeOrder(ours, theirs)) return null;
1063
- const [primary, secondary] = oursMoved ? [ours, theirs] : [theirs, ours];
1064
- const kept = primary.filter((key) => keep.has(key));
1065
- const placed = new Set(kept);
1066
- const inSecondary = new Set(secondary);
1067
- const next = new Map(kept.map((key, index) => [index === 0 ? null : kept[index - 1], key]));
1068
- if (kept.length) next.set(kept.at(-1), void 0);
1069
- const after = /* @__PURE__ */ new Map();
1070
- let anchor = null;
1071
- let chained = false;
1072
- for (const key of secondary) {
1073
- if (!keep.has(key)) continue;
1074
- if (placed.has(key)) {
1075
- anchor = key;
1076
- chained = false;
1077
- continue;
1078
- }
1079
- if (!chained) {
1080
- const following = next.get(anchor);
1081
- if (ordering === "schema" && following !== void 0 && !inSecondary.has(following)) return null;
1082
- }
1083
- after.set(anchor, key);
1084
- anchor = key;
1085
- chained = true;
1086
- }
1087
- const result = [];
1088
- const emit = (key) => {
1089
- for (let at = key; at !== void 0; at = after.get(at)) if (at !== null) result.push(at);
1090
- };
1091
- emit(null);
1092
- for (const key of kept) emit(key);
1093
- return result;
1094
- }
1095
- const partConflict = (context, reason, shape = null) => ({
1096
- part: context.part,
1097
- slide: null,
1098
- shape,
1099
- reason
1100
- });
1101
- function shapeIdentity(element) {
1102
- let found;
1103
- walkElements(element, (child) => {
1104
- if (found) return false;
1105
- if (child.name.localName === "cNvPr") found = child;
1106
- });
1107
- if (!found) return null;
1108
- const value = (name) => found.attrs.find((item) => item.name.localName === name)?.value;
1109
- const id = value("id");
1110
- return id === void 0 ? null : {
1111
- id,
1112
- name: value("name") ?? ""
1113
- };
1114
- }
1115
- const elementKey = (element) => `${element.name.namespaceURI} ${element.name.localName}`;
1116
- /** Keys an element's children, or `null` when they cannot be told apart. */
1117
- function childEntries(element, kind) {
1118
- const entries = [];
1119
- const keys = /* @__PURE__ */ new Set();
1120
- const occurrences = /* @__PURE__ */ new Map();
1121
- for (const child of element.children) {
1122
- if (child.kind !== "element") {
1123
- if (child.kind === "text" && !child.data.trim()) continue;
1124
- return null;
1125
- }
1126
- let key;
1127
- if (kind === "spTree" && !(child.name.namespaceURI === NS.pml && SHAPE_TREE_PROPERTIES.has(child.name.localName))) {
1128
- const identity = shapeIdentity(child);
1129
- if (!identity) return null;
1130
- key = `shape ${identity.id}`;
1131
- } else {
1132
- const name = elementKey(child);
1133
- const occurrence = occurrences.get(name) ?? 0;
1134
- occurrences.set(name, occurrence + 1);
1135
- key = `${name} ${occurrence}`;
1136
- }
1137
- if (keys.has(key)) return null;
1138
- keys.add(key);
1139
- entries.push({
1140
- key,
1141
- value: serializeFragment(child),
1142
- item: child
1143
- });
1144
- }
1145
- return entries;
1146
- }
1147
- const attributesValue = (attrs) => JSON.stringify(attrs.map((item) => [
1148
- item.name.namespaceURI,
1149
- item.name.localName,
1150
- item.value
1151
- ]));
1152
- const NESTED = {
1153
- root: {
1154
- localName: "cSld",
1155
- kind: "cSld"
1156
- },
1157
- cSld: {
1158
- localName: "spTree",
1159
- kind: "spTree"
1160
- }
1161
- };
1162
- /** Merges a slide-like element: its attributes as one unit, its children by key. */
1163
- function mergeContainer(context, base, ours, theirs, kind) {
1164
- const conflicts = [];
1165
- const [ba, oa, ta] = [
1166
- base.attrs,
1167
- ours.attrs,
1168
- theirs.attrs
1169
- ].map(attributesValue);
1170
- let attrs = theirs.attrs;
1171
- if (oa !== ba && (ta === ba || oa === ta)) attrs = ours.attrs;
1172
- else if (oa !== ba) conflicts.push(partConflict(context, "both-changed"));
1173
- const prefixDecls = new Map(theirs.prefixDecls);
1174
- for (const [prefix, uri] of ours.prefixDecls) {
1175
- const existing = prefixDecls.get(prefix);
1176
- if (existing === void 0) prefixDecls.set(prefix, uri);
1177
- else if (existing !== uri) conflicts.push(partConflict(context, "both-changed"));
1178
- }
1179
- const entries = [
1180
- base,
1181
- ours,
1182
- theirs
1183
- ].map((element) => childEntries(element, kind));
1184
- if (entries.some((list) => !list)) conflicts.push(partConflict(context, "both-changed"));
1185
- if (conflicts.length) return {
1186
- ok: false,
1187
- conflicts
1188
- };
1189
- const nested = NESTED[kind];
1190
- const children = mergeEntries(entries[0], entries[1], entries[2], {
1191
- ordering: kind === "spTree" ? "free" : "schema",
1192
- refine: (_key, b, o, t) => nested && t.name.namespaceURI === NS.pml && t.name.localName === nested.localName ? mergeContainer(context, b, o, t, nested.kind) : null,
1193
- conflict: (key, reason, sample) => partConflict(context, reason, sample && key.startsWith("shape ") ? shapeIdentity(sample) : null)
1194
- });
1195
- if (!children.ok) return children;
1196
- return {
1197
- ok: true,
1198
- value: elem(theirs.name, {
1199
- attrs,
1200
- prefixDecls,
1201
- children: children.value
1202
- })
1203
- };
1204
- }
1205
- function mergeShapeTreePart(context, base, ours, theirs) {
1206
- const [b, o, t] = [
1207
- base,
1208
- ours,
1209
- theirs
1210
- ].map((bytes) => parseXml(strFromU8(bytes)));
1211
- const root = mergeContainer(context, b.root, o.root, t.root, "root");
1212
- if (!root.ok) return root;
1213
- return {
1214
- ok: true,
1215
- value: strToU8(serializeXml({
1216
- ...t,
1217
- root: root.value
1218
- }))
1219
- };
1220
- }
1221
- function relationshipEntries(bytes) {
1222
- return (bytes ? parseRels(strFromU8(bytes)) : emptyRels()).items.map((item) => ({
1223
- key: item.id,
1224
- value: JSON.stringify([
1225
- item.type,
1226
- item.target,
1227
- item.targetMode
1228
- ]),
1229
- item
1230
- }));
1231
- }
1232
- function mergeRelationshipsPart(context, base, ours, theirs) {
1233
- const items = mergeEntries(relationshipEntries(base), relationshipEntries(ours), relationshipEntries(theirs), {
1234
- ordering: "none",
1235
- conflict: (_key, reason) => partConflict(context, reason)
1236
- });
1237
- if (!items.ok) return items;
1238
- return {
1239
- ok: true,
1240
- value: strToU8(serializeRels({ items: items.value }))
1241
- };
1242
- }
1243
- /**
1244
- * Content types are declarations about parts, so both sides' entries are kept
1245
- * and only overrides for parts the merged package no longer has are dropped.
1246
- */
1247
- function mergeContentTypes(ours, theirs, parts) {
1248
- const [o, t] = [ours, theirs].map((bytes) => parseContentTypes(strFromU8(bytes)));
1249
- const merged = {
1250
- defaults: [],
1251
- overrides: []
1252
- };
1253
- const defaults = /* @__PURE__ */ new Map();
1254
- const overrides = /* @__PURE__ */ new Map();
1255
- const conflicts = [];
1256
- const context = { part: CONTENT_TYPES };
1257
- for (const types of [t, o]) {
1258
- for (const item of types.defaults) {
1259
- const existing = defaults.get(item.extension);
1260
- if (existing === void 0) {
1261
- defaults.set(item.extension, item.contentType);
1262
- merged.defaults.push(item);
1263
- } else if (existing !== item.contentType) conflicts.push(partConflict(context, "both-changed"));
1264
- }
1265
- for (const item of types.overrides) {
1266
- const key = item.partName.toLowerCase();
1267
- if (!parts.has(key)) continue;
1268
- const existing = overrides.get(key);
1269
- if (existing === void 0) {
1270
- overrides.set(key, item.contentType);
1271
- merged.overrides.push(item);
1272
- } else if (existing !== item.contentType) conflicts.push(partConflict(context, "both-changed"));
1273
- }
1274
- }
1275
- if (conflicts.length) return {
1276
- ok: false,
1277
- conflicts
1278
- };
1279
- return {
1280
- ok: true,
1281
- value: strToU8(serializeContentTypes(merged))
1282
- };
1283
- }
1284
- /** Maps each slide's parts (the slide, its notes and their relationships) to its number. */
1285
- function slideNumbers(parts) {
1286
- const numbers = /* @__PURE__ */ new Map();
1287
- const presentation = parts.get(PRESENTATION);
1288
- const presentationRels = parts.get("ppt/_rels/presentation.xml.rels");
1289
- if (!presentation || !presentationRels) return numbers;
1290
- const targets = new Map(parseRels(strFromU8(presentationRels)).items.filter((item) => item.type === SLIDE_RELATIONSHIP).map((item) => [item.id, toZipPath(resolveTarget(fromZipPath(PRESENTATION), item.target))]));
1291
- let number = 0;
1292
- walkElements(parseXml(strFromU8(presentation)).root, (element) => {
1293
- if (element.name.localName !== "sldId") return void 0;
1294
- number++;
1295
- const id = element.attrs.find((item) => item.name.namespaceURI === NS.officeDocRels && item.name.localName === "id")?.value;
1296
- const slide = id === void 0 ? void 0 : targets.get(id);
1297
- if (!slide) return false;
1298
- const relsPart = relsName(slide);
1299
- numbers.set(slide, number).set(relsPart, number);
1300
- const rels = parts.get(relsPart);
1301
- if (!rels) return false;
1302
- for (const item of parseRels(strFromU8(rels)).items) {
1303
- if (item.type !== NOTES_RELATIONSHIP) continue;
1304
- const notes = toZipPath(resolveTarget(fromZipPath(slide), item.target));
1305
- numbers.set(notes, number).set(relsName(notes), number);
1306
- }
1307
- return false;
1308
- });
1309
- return numbers;
1310
- }
1311
- const relsName = (part) => {
1312
- const slash = part.lastIndexOf("/");
1313
- return `${part.slice(0, slash + 1)}_rels/${part.slice(slash + 1)}.rels`;
1314
- };
1315
- /**
1316
- * Both sides allocate the next free relationship ID, so a relationship the
1317
- * editor added and a different one the source added often share an ID. IDs
1318
- * are private to a part and its relationships, so the editor's is renamed
1319
- * (in its relationships and in the part referring to it) instead of
1320
- * reporting a conflict.
1321
- */
1322
- function renumberAddedRelationships(base, ours, theirs) {
1323
- for (const [name, bytes] of ours) {
1324
- const owner = relationshipsOwner(name);
1325
- const theirsBytes = theirs.get(name);
1326
- const ownerBytes = owner && ours.get(owner);
1327
- if (!ownerBytes || !theirsBytes) continue;
1328
- const parse = (value) => value ? parseRels(strFromU8(value)) : emptyRels();
1329
- const [b, o, t] = [
1330
- parse(base.get(name)),
1331
- parse(bytes),
1332
- parse(theirsBytes)
1333
- ];
1334
- const inBase = new Set(b.items.map((item) => item.id));
1335
- const theirsById = new Map(t.items.map((item) => [item.id, item]));
1336
- const used = new Set([
1337
- ...inBase,
1338
- ...theirsById.keys(),
1339
- ...o.items.map((item) => item.id)
1340
- ]);
1341
- const renamed = /* @__PURE__ */ new Map();
1342
- let next = 1;
1343
- for (const item of o.items) {
1344
- const other = theirsById.get(item.id);
1345
- if (inBase.has(item.id) || !other || sameRelationship(item, other)) continue;
1346
- while (used.has(`rId${next}`)) next++;
1347
- renamed.set(item.id, `rId${next}`);
1348
- used.add(`rId${next}`);
1349
- item.id = `rId${next}`;
1350
- }
1351
- if (!renamed.size) continue;
1352
- ours.set(name, strToU8(serializeRels(o)));
1353
- const document = parseXml(strFromU8(ownerBytes));
1354
- walkElements(document.root, (element) => {
1355
- element.attrs = element.attrs.map((item) => item.name.namespaceURI === NS.officeDocRels && renamed.has(item.value) ? {
1356
- name: item.name,
1357
- value: renamed.get(item.value)
1358
- } : item);
1359
- });
1360
- ours.set(owner, strToU8(serializeXml(document)));
1361
- }
1362
- }
1363
- const sameRelationship = (a, b) => a.type === b.type && a.target === b.target && a.targetMode === b.targetMode;
1364
- /** The part whose relationships `name` holds; `null` for the package's own. */
1365
- function relationshipsOwner(name) {
1366
- const match = /^(.*?)_rels\/([^/]+)\.rels$/.exec(name);
1367
- return match ? `${match[1]}${match[2]}` : null;
1368
- }
1369
- function unzip(bytes) {
1370
- return new Map(readZip(bytes).entries.map((entry) => [entry.name, entry.data]));
1371
- }
1372
- /**
1373
- * Three-way merge of presentation packages: `base` is the source build the
1374
- * editor's copy was made from, `ours` the editor's copy and `theirs` the new
1375
- * source build. Changes on different parts, shapes or relationships combine;
1376
- * the same item changed differently on both sides is reported as a conflict.
1377
- */
1378
- function mergeDecks(base, ours, theirs) {
1379
- const [b, o, t] = [
1380
- unzip(base),
1381
- unzip(ours),
1382
- unzip(theirs)
1383
- ];
1384
- renumberAddedRelationships(b, o, t);
1385
- const names = new Set([
1386
- ...t.keys(),
1387
- ...o.keys(),
1388
- ...b.keys()
1389
- ]);
1390
- const result = /* @__PURE__ */ new Map();
1391
- const conflicts = [];
1392
- const same = (name, x, y) => x === void 0 || y === void 0 ? x === y : sameBytes(comparablePart(name, x), comparablePart(name, y));
1393
- for (const name of names) {
1394
- if (name === CONTENT_TYPES) continue;
1395
- const [bp, op, tp] = [
1396
- b.get(name),
1397
- o.get(name),
1398
- t.get(name)
1399
- ];
1400
- let pick;
1401
- if (same(name, op, bp)) pick = tp;
1402
- else if (same(name, tp, bp) || same(name, op, tp)) pick = op;
1403
- else {
1404
- const context = { part: name };
1405
- let merged = null;
1406
- if (op && tp && RELATIONSHIPS_PART.test(name)) merged = mergeRelationshipsPart(context, bp, op, tp);
1407
- else if (bp && op && tp && SHAPE_TREE_PART.test(name)) merged = mergeShapeTreePart(context, bp, op, tp);
1408
- if (merged?.ok) result.set(name, merged.value);
1409
- else if (merged) conflicts.push(...merged.conflicts);
1410
- else conflicts.push(partConflict(context, !bp ? "both-added" : !op || !tp ? "deleted-and-changed" : "both-changed"));
1411
- continue;
1412
- }
1413
- if (pick) result.set(name, pick);
1414
- }
1415
- const [bt, ot, tt] = [
1416
- b,
1417
- o,
1418
- t
1419
- ].map((parts) => parts.get(CONTENT_TYPES));
1420
- let contentTypes;
1421
- if (same(CONTENT_TYPES, ot, bt)) contentTypes = tt;
1422
- else if (same(CONTENT_TYPES, tt, bt) || same(CONTENT_TYPES, ot, tt)) contentTypes = ot;
1423
- else {
1424
- const merged = mergeContentTypes(ot, tt, new Set([...result.keys()].map((name) => `/${name}`.toLowerCase())));
1425
- if (merged.ok) contentTypes = merged.value;
1426
- else conflicts.push(...merged.conflicts);
1427
- }
1428
- if (conflicts.length) {
1429
- const numbers = [
1430
- b,
1431
- t,
1432
- o
1433
- ].reduce((all, parts) => new Map([...all, ...slideNumbers(parts)]), /* @__PURE__ */ new Map());
1434
- return {
1435
- ok: false,
1436
- conflicts: conflicts.map((conflict) => ({
1437
- ...conflict,
1438
- slide: numbers.get(conflict.part) ?? null
1439
- }))
1440
- };
1441
- }
1442
- return {
1443
- ok: true,
1444
- bytes: writeZip([{
1445
- name: CONTENT_TYPES,
1446
- data: contentTypes
1447
- }, ...[...result].map(([name, data]) => ({
1448
- name,
1449
- data
1450
- }))])
1451
- };
1452
- }
1453
- /** One line per conflict, for the CLI and server messages. */
1454
- function describeConflict(conflict) {
1455
- const where = conflict.slide === null ? conflict.part : `Slide ${conflict.slide}`;
1456
- const what = conflict.shape ? `${where}: ${conflict.shape.name || `shape ${conflict.shape.id}`}` : where;
1457
- return conflict.reason === "deleted-and-changed" ? `${what} was deleted on one side and changed on the other.` : `${what} was changed both in the editor and in the source.`;
1458
- }
1459
- //#endregion
1460
- //#region src/editor-store.ts
1461
- const STORE_VERSION = 2;
1462
- /** One atomic sidecar keeps the saved deck and its source basis in agreement. */
1463
- function editorStore(entry) {
1464
- const path = join(dirname(resolve(entry)), ".office-kit", `${basename(entry)}.editor.zip`);
1465
- return {
1466
- path,
1467
- async read() {
1468
- let bytes;
1469
- try {
1470
- bytes = await readFile(path);
1471
- } catch (cause) {
1472
- if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") return;
1473
- throw cause;
1474
- }
1475
- const parts = unzipSync(bytes);
1476
- if (!parts["state.json"] || !parts["document.pptx"]) throw new Error(`Invalid editor file: ${path}`);
1477
- const state = JSON.parse(strFromU8(parts["state.json"]));
1478
- if (!state || typeof state !== "object" || !("version" in state) || state.version !== 1 && state.version !== STORE_VERSION || !("sourceHash" in state) || typeof state.sourceHash !== "string" || !/^[a-f0-9]{64}$/.test(state.sourceHash)) throw new Error(`Invalid editor metadata: ${path}`);
1479
- const base = parts["base.pptx"];
1480
- if (state.version === STORE_VERSION && !base) throw new Error(`Invalid editor file: ${path}`);
1481
- return {
1482
- sourceHash: state.sourceHash,
1483
- bytes: parts["document.pptx"],
1484
- base: base ?? null
1485
- };
1486
- },
1487
- async write(edits) {
1488
- await mkdir(dirname(path), { recursive: true });
1489
- const temporary = `${path}.${randomUUID()}.tmp`;
1490
- try {
1491
- await writeFile(temporary, zipSync({
1492
- "state.json": strToU8(JSON.stringify({
1493
- version: STORE_VERSION,
1494
- sourceHash: edits.sourceHash
1495
- })),
1496
- "document.pptx": edits.bytes,
1497
- "base.pptx": edits.base
1498
- }), { flag: "wx" });
1499
- await rename(temporary, path);
1500
- } finally {
1501
- await rm(temporary, { force: true });
1502
- }
1503
- },
1504
- async clear() {
1505
- await rm(path, { force: true });
1506
- }
1507
- };
1508
- }
1509
- async function applySavedEdits(entry, source) {
1510
- const edits = await editorStore(entry).read();
1511
- if (!edits) return source;
1512
- let bytes = edits.bytes;
1513
- if (edits.sourceHash !== sourceFingerprint(source.bytes)) {
1514
- const merged = edits.base ? mergeDecks(edits.base, edits.bytes, source.bytes) : null;
1515
- if (!merged?.ok) throw new Error(["Source changed since the last editor save. Open office-pptx dev and resolve the editor conflict before exporting.", ...merged?.conflicts.map(describeConflict) ?? []].join("\n"));
1516
- bytes = merged.bytes;
1517
- }
1518
- return (await renderDeck(bytes, source.dependencies)).result;
1519
- }
1520
- //#endregion
1521
- //#region src/init.ts
1522
- const starter = `import { Presentation } from '@office-kit/pptx-dsl';
1523
- import { Cover } from './slides/cover.tsx';
1524
-
1525
- export default (
1526
- <Presentation>
1527
- <Cover />
1528
- </Presentation>
1529
- );
1530
- `;
1531
- const theme = `export const theme = {
1532
- background: '#15171C',
1533
- accent: '#D6336C',
1534
- text: '#FFFFFF',
1535
- muted: '#B4B9C4',
1536
- } as const;
1537
- `;
1538
- const cover = `import { Slide, Text, Shape } from '@office-kit/pptx-dsl';
1539
- import { theme } from '../theme.ts';
1540
-
1541
- export function Cover() {
1542
- return (
1543
- <Slide background={theme.background}>
1544
- <Shape preset="rect" x={0.9} y={2.55} width={0.14} height={1.75} fill={theme.accent} stroke={false} />
1545
- <Text x={1.25} y={2.4} width={10} height={1.1} size={48} bold color={theme.text}>
1546
- Your next presentation
1547
- </Text>
1548
- <Text x={1.25} y={3.55} width={10} height={0.6} size={22} color={theme.muted}>
1549
- Edit this TSX and save to update the preview.
1550
- </Text>
1551
- </Slide>
1552
- );
1553
- }
1554
- `;
1555
- const guide = `# Writing this presentation
1556
-
1557
- Use deck.tsx for slide order, slides/*.tsx for individual slides, and theme.ts
1558
- for shared design values. The JSX runtime is
1559
- @office-kit/pptx-dsl, with no React or Vue dependency.
1560
-
1561
- - Run npm run dev, then open its local URL to preview. Saving source files
1562
- updates the preview. A failed build retains the last successful output.
1563
- - Select an area in the preview to request a focused AI edit, or use Edit text
1564
- for a direct text save. Changes are persisted in TSX. Use the thumbnails, zoom
1565
- and Present to review slides. After edits, inspect every screenshot supplied
1566
- by the preview and correct clipping, overlap, spacing and contrast.
1567
- - For a local revision, locate the slide through deck.tsx or a text search and
1568
- read only the relevant source and dependencies. Patch the requested text, data
1569
- or props; do not regenerate the deck or reformat unrelated code. Keep descriptive
1570
- filenames stable when reordering slides. Preserve existing project structures.
1571
- - Change shared theme values/components only for changes intended for all their
1572
- consumers; use a local prop override for a one-slide exception.
1573
- - Keep the preview running during revisions and inspect affected slides after
1574
- saving. Review all affected consumers for shared changes. Do not run a separate
1575
- export for every intermediate edit. Before delivery, run npm run check and
1576
- npm run build to export deck.pptx, then review the whole deck.
1577
- - Coordinates and dimensions are inches; font sizes and stroke widths are points.
1578
- The default 16:9 canvas is 13.333 × 7.5 inches.
1579
- - Use native Text, Shape, Image, Table and Chart elements. Use JS functions,
1580
- arrays and map to compose elements; do not import React.
1581
- - Chart takes the core ChartSpec in its spec prop. Text accepts rich ParagraphSpec
1582
- arrays through paragraphs. Raw is an escape hatch for public core APIs.
1583
- - For templates: readFile(new URL('./template.pptx', import.meta.url)), then use
1584
- Presentation source={bytes} mode="edit" and Slide target={{index: 0}}.
1585
- Fill target={{name: 'Title 1'}} changes an existing shape. Indices are zero-based.
1586
- Use office-pptx inspect template.pptx to discover names, layouts and placeholders.
1587
- - Use mode="compose" and Slide from={{index: 0}} to build a new sequence from a
1588
- source deck. This intentionally removes the original slide sequence.
1589
- - Keep assets inside this project so the dev server sees changes. Use source-relative
1590
- new URL('./image.png', import.meta.url) when loading local assets.
1591
- - Preserve existing slides and formatting unless the request calls for changes.
1592
- Never rasterize a chart/table as a shortcut. Raw-only features are not DSL coverage.
1593
- - Preview is a rendering aid, not a guarantee of identical PowerPoint rendering.
1594
- Check the exported file in the target application for final delivery.
1595
-
1596
- VSCode uses tsconfig.json for completion and diagnostics. Start the
1597
- "Preview presentation" task, then run "Simple Browser: Show" with the printed URL
1598
- and move that editor to a side group. Type errors appear in the Problems panel.
1599
- `;
1600
- /** Creates a new directory; existing projects are never overwritten. */
1601
- async function initProject(directory) {
1602
- const root = resolve(directory);
1603
- const packageLocations = [
1604
- import.meta.resolve("@office-kit/pptx/package.json"),
1605
- import.meta.resolve("@office-kit/pptx-dsl/package.json"),
1606
- new URL("../package.json", import.meta.url)
1607
- ];
1608
- const versions = await Promise.all(packageLocations.map(async (location) => {
1609
- return `^${JSON.parse(await readFile(new URL(location), "utf8")).version}`;
1610
- }));
1611
- await mkdir(root);
1612
- const files = {
1613
- "package.json": JSON.stringify({
1614
- name: "my-presentation",
1615
- private: true,
1616
- type: "module",
1617
- scripts: {
1618
- dev: "office-pptx dev deck.tsx",
1619
- build: "office-pptx build deck.tsx --out deck.pptx",
1620
- check: "tsc --noEmit"
1621
- },
1622
- dependencies: {
1623
- "@office-kit/pptx": versions[0],
1624
- "@office-kit/pptx-dsl": versions[1]
1625
- },
1626
- devDependencies: {
1627
- "@office-kit/pptx-dev": versions[2],
1628
- "@types/node": "^24.13.2",
1629
- typescript: "^6.0.3"
1630
- }
1631
- }, null, 2) + "\n",
1632
- "tsconfig.json": JSON.stringify({
1633
- compilerOptions: {
1634
- target: "ES2022",
1635
- module: "ESNext",
1636
- moduleResolution: "Bundler",
1637
- jsx: "react-jsx",
1638
- jsxImportSource: "@office-kit/pptx-dsl",
1639
- strict: true,
1640
- exactOptionalPropertyTypes: true,
1641
- noEmit: true,
1642
- allowImportingTsExtensions: true,
1643
- types: ["node"]
1644
- },
1645
- include: ["**/*.ts", "**/*.tsx"],
1646
- exclude: ["node_modules"]
1647
- }, null, 2) + "\n",
1648
- "deck.tsx": starter,
1649
- "theme.ts": theme,
1650
- "slides/cover.tsx": cover,
1651
- "CLAUDE.md": guide,
1652
- "AGENTS.md": guide,
1653
- "README.md": "# Your slides\n\nInstall once with `npm install`, then run `npm run dev` (or `npx office-pptx dev deck.tsx`). Open the printed URL.\n\nEdit `slides/cover.tsx`, select an area for an AI instruction, or choose Edit text in the preview. Start Claude Code in an agent pane before sending instructions. An installed Chrome enables automatic screenshot review.\n\nRun `npm run check` and `npm run build` to check and export `deck.pptx`.\n",
1654
- ".gitignore": "node_modules/\ndeck.pptx\n.office-kit/\n",
1655
- ".vscode/tasks.json": JSON.stringify({
1656
- version: "2.0.0",
1657
- tasks: [
1658
- {
1659
- label: "Preview presentation",
1660
- type: "shell",
1661
- command: "npm run dev",
1662
- isBackground: true,
1663
- problemMatcher: {
1664
- pattern: {
1665
- regexp: "^NEVER_MATCH$",
1666
- file: 1,
1667
- message: 2
1668
- },
1669
- background: {
1670
- activeOnStart: true,
1671
- beginsPattern: "^.*office-pptx dev",
1672
- endsPattern: "^Preview: "
1673
- }
1674
- }
1675
- },
1676
- {
1677
- label: "Check presentation",
1678
- type: "shell",
1679
- command: "npm run check",
1680
- problemMatcher: "$tsc"
1681
- },
1682
- {
1683
- label: "Export presentation",
1684
- type: "shell",
1685
- command: "npm run build",
1686
- problemMatcher: []
1687
- }
1688
- ]
1689
- }, null, 2) + "\n"
1690
- };
1691
- await Promise.all([".vscode", "slides"].map((directory) => mkdir(join(root, directory))));
1692
- await Promise.all(Object.entries(files).map(([name, content]) => writeFile(join(root, name), content, { flag: "wx" })));
1693
- return root;
1694
- }
1695
- //#endregion
1696
- //#region src/inspect.ts
1697
- /** Source references usable directly in Slide, Fill and Remove props. */
1698
- async function inspectTemplate(file) {
1699
- const presentation = await loadPresentation(await readFile(file));
1700
- return {
1701
- slides: getSlides(presentation).map((slide, index) => ({
1702
- index,
1703
- part: getSlidePartName(slide),
1704
- title: getSlideTitle(slide),
1705
- shapes: getSlideShapes(slide).map((shape) => ({
1706
- id: getShapeId(shape),
1707
- name: getShapeName(shape),
1708
- kind: getShapeKind(shape),
1709
- placeholder: {
1710
- type: getShapePlaceholderType(shape),
1711
- idx: getShapePlaceholderIdx(shape)
1712
- }
1713
- }))
1714
- })),
1715
- layouts: getSlideLayouts(presentation).map((layout) => ({
1716
- part: getSlideLayoutPartName(layout),
1717
- name: getSlideLayoutName(layout),
1718
- type: getSlideLayoutType(layout)
1719
- }))
1720
- };
1721
- }
1722
- //#endregion
1723
- //#region src/index.ts
1724
- /** Each evaluation gets a fresh module cache and releases it when finished. */
1725
- async function buildDeck(entry) {
1726
- const builder = createDeckBuilder(entry, false);
1727
- try {
1728
- return await applySavedEdits(entry, await builder.build());
1729
- } finally {
1730
- await builder.close();
1731
- }
1732
- }
1733
- async function exportDeck(entry, output) {
1734
- const result = await buildDeck(entry);
1735
- await writeFile(output, result.bytes);
1736
- return result;
1737
- }
1738
- //#endregion
1739
- export { editorStore as a, createDeckBuilder as c, initProject as i, exportDeck as n, mergeDecks as o, inspectTemplate as r, sourceFingerprint as s, buildDeck as t };
1740
-
1741
- //# sourceMappingURL=src-CI73YbYS.mjs.map