@office-kit/pptx-editor 0.1.0 → 0.2.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.
@@ -0,0 +1,28 @@
1
+ import { PresentationData, SlideShapeData } from "@office-kit/pptx";
2
+
3
+ //#region src/core/agent-tools.d.ts
4
+ /** A JSON value: what a tool takes as input and gives back. */
5
+ type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
6
+ readonly [key: string]: JsonValue;
7
+ };
8
+ /** A JSON Schema (draft 2020-12) object. */
9
+ type JsonSchema = {
10
+ readonly [key: string]: JsonValue;
11
+ };
12
+ /**
13
+ * A tool definition in the shape model APIs take: Anthropic's `tools` entries
14
+ * as they are; for OpenAI, `{ type: 'function', function: { name, description,
15
+ * parameters: input_schema } }`.
16
+ */
17
+ interface EditorTool {
18
+ /** The `@office-kit/pptx` export it calls, or a read tool's name. */
19
+ readonly name: string;
20
+ readonly description: string;
21
+ /** The input, as a self-contained JSON Schema (draft 2020-12) object. */
22
+ readonly input_schema: JsonSchema & {
23
+ readonly type: "object";
24
+ };
25
+ }
26
+ //#endregion
27
+ export { JsonSchema as n, JsonValue as r, EditorTool as t };
28
+ //# sourceMappingURL=agent-tools-bDB2Jq41.d.ts.map
@@ -1,4 +1,4 @@
1
- import { strFromU8, strToU8, unzipSync, zipSync } from "fflate";
1
+ import { Inflate, inflateSync, strFromU8, strToU8, zipSync } from "fflate";
2
2
  //#region ../../src/internal/opc/zip.ts
3
3
  const STORE_BY_EXTENSION = new Set([
4
4
  "png",
@@ -36,20 +36,173 @@ const extensionOf = (name) => {
36
36
  * negligible savings.
37
37
  */
38
38
  const shouldStore = (name) => STORE_BY_EXTENSION.has(extensionOf(name));
39
+ const SIG_LOCAL_HEADER = 67324752;
40
+ const SIG_CENTRAL_HEADER = 33639248;
41
+ const SIG_EOCD = 101010256;
42
+ const SIG_ZIP64_EOCD = 101075792;
43
+ const SIG_ZIP64_LOCATOR = 117853008;
44
+ const EOCD_SIZE = 22;
45
+ const MAX_COMMENT_LENGTH = 65535;
46
+ const ZIP64_LOCATOR_SIZE = 20;
47
+ const ZIP64_EOCD_MIN_SIZE = 56;
48
+ const CENTRAL_HEADER_SIZE = 46;
49
+ const LOCAL_HEADER_SIZE = 30;
50
+ const ZIP64_EXTRA_ID = 1;
51
+ const ZIP64_SENTINEL_32 = 4294967295;
52
+ const FLAG_UTF8 = 2048;
53
+ const METHOD_STORE = 0;
54
+ const METHOD_DEFLATE = 8;
55
+ const INFLATE_CHUNK_SIZE = 16 * 1024;
56
+ const MAX_DEFLATE_RATIO = 1032;
57
+ const DEFLATE_RATIO_SLACK = 1024;
58
+ const invalid = (reason) => /* @__PURE__ */ new Error(`invalid ZIP: ${reason}`);
59
+ const u16 = (d, at) => d[at] | d[at + 1] << 8;
60
+ const u32 = (d, at) => (d[at] | d[at + 1] << 8 | d[at + 2] << 16 | d[at + 3] << 24) >>> 0;
61
+ const u64 = (d, at) => {
62
+ const value = u32(d, at) + u32(d, at + 4) * 2 ** 32;
63
+ if (!Number.isSafeInteger(value)) throw invalid("ZIP64 value out of range");
64
+ return value;
65
+ };
66
+ const requireRange = (d, at, length, what) => {
67
+ if (at < 0 || length < 0 || at + length > d.length) throw invalid(`${what} is out of bounds`);
68
+ };
69
+ const findEocd = (d) => {
70
+ const lowest = Math.max(0, d.length - EOCD_SIZE - MAX_COMMENT_LENGTH);
71
+ for (let at = d.length - EOCD_SIZE; at >= lowest; at--) if (u32(d, at) === SIG_EOCD) return at;
72
+ throw invalid("end of central directory not found");
73
+ };
74
+ const readCentralDirectoryLocation = (d) => {
75
+ const eocd = findEocd(d);
76
+ const locator = eocd - ZIP64_LOCATOR_SIZE;
77
+ if (locator >= 0 && u32(d, locator) === SIG_ZIP64_LOCATOR) {
78
+ const zip64Eocd = u32(d, locator + 8) + u32(d, locator + 12) * 2 ** 32;
79
+ if (zip64Eocd + ZIP64_EOCD_MIN_SIZE <= d.length && u32(d, zip64Eocd) === SIG_ZIP64_EOCD) return {
80
+ count: u64(d, zip64Eocd + 32),
81
+ offset: u64(d, zip64Eocd + 48)
82
+ };
83
+ }
84
+ return {
85
+ count: u16(d, eocd + 10),
86
+ offset: u32(d, eocd + 16)
87
+ };
88
+ };
89
+ const resolveZip64Sizes = (d, extraStart, extraEnd, sizes) => {
90
+ const needUncompressed = sizes.uncompressedSize === ZIP64_SENTINEL_32;
91
+ const needCompressed = sizes.compressedSize === ZIP64_SENTINEL_32;
92
+ const needOffset = sizes.localHeaderOffset === ZIP64_SENTINEL_32;
93
+ if (!needUncompressed && !needCompressed && !needOffset) return sizes;
94
+ for (let at = extraStart; at + 4 <= extraEnd; at += 4 + u16(d, at + 2)) {
95
+ if (u16(d, at) !== ZIP64_EXTRA_ID) continue;
96
+ const fieldEnd = at + 4 + u16(d, at + 2);
97
+ if (fieldEnd > extraEnd) break;
98
+ let cursor = at + 4;
99
+ const next = () => {
100
+ if (cursor + 8 > fieldEnd) throw invalid("ZIP64 extra field is truncated");
101
+ const value = u64(d, cursor);
102
+ cursor += 8;
103
+ return value;
104
+ };
105
+ return {
106
+ uncompressedSize: needUncompressed ? next() : sizes.uncompressedSize,
107
+ compressedSize: needCompressed ? next() : sizes.compressedSize,
108
+ localHeaderOffset: needOffset ? next() : sizes.localHeaderOffset
109
+ };
110
+ }
111
+ throw invalid("ZIP64 sizes without a ZIP64 extra field");
112
+ };
113
+ const overflowError = (name, declaredSize) => invalid(`entry "${name}" inflates beyond its declared size of ${declaredSize} bytes`);
114
+ const corruptError = (name, error) => {
115
+ return invalid(`entry "${name}" is not valid DEFLATE data: ${error instanceof Error ? error.message : String(error)}`);
116
+ };
117
+ const inflateSmallEntry = (compressed, declaredSize, name) => {
118
+ if (compressed.length === 0) return new Uint8Array(0);
119
+ let out;
120
+ try {
121
+ out = inflateSync(compressed, { out: new Uint8Array(declaredSize + 1) });
122
+ } catch (error) {
123
+ if (error instanceof RangeError) throw overflowError(name, declaredSize);
124
+ throw corruptError(name, error);
125
+ }
126
+ if (out.length > declaredSize) throw overflowError(name, declaredSize);
127
+ return out;
128
+ };
129
+ const inflateLargeEntry = (compressed, declaredSize, name) => {
130
+ const out = new Uint8Array(declaredSize);
131
+ let overflowed = false;
132
+ let produced = 0;
133
+ const inflater = new Inflate((chunk) => {
134
+ if (produced + chunk.length > declaredSize) {
135
+ overflowed = true;
136
+ throw overflowError(name, declaredSize);
137
+ }
138
+ out.set(chunk, produced);
139
+ produced += chunk.length;
140
+ });
141
+ for (let at = 0; at < compressed.length; at += INFLATE_CHUNK_SIZE) {
142
+ const end = Math.min(at + INFLATE_CHUNK_SIZE, compressed.length);
143
+ try {
144
+ inflater.push(compressed.subarray(at, end), end === compressed.length);
145
+ } catch (error) {
146
+ if (overflowed) throw error;
147
+ throw corruptError(name, error);
148
+ }
149
+ }
150
+ return out.subarray(0, produced);
151
+ };
152
+ const inflateEntry = (compressed, declaredSize, name) => {
153
+ if (declaredSize > compressed.length * MAX_DEFLATE_RATIO + DEFLATE_RATIO_SLACK) throw invalid(`entry "${name}" declares ${declaredSize} bytes, more than ${compressed.length} compressed bytes can hold`);
154
+ const out = compressed.length <= INFLATE_CHUNK_SIZE ? inflateSmallEntry(compressed, declaredSize, name) : inflateLargeEntry(compressed, declaredSize, name);
155
+ if (out.length !== declaredSize) throw invalid(`entry "${name}" inflates to ${out.length} bytes, not its declared ${declaredSize}`);
156
+ return out;
157
+ };
39
158
  /**
40
159
  * Reads a ZIP archive (a `.pptx` file is a ZIP) and returns entries in
41
- * central-directory order. Throws if `input` is not a valid ZIP.
160
+ * central-directory order. Throws if `input` is not a valid ZIP, if two
161
+ * entries share a name, or if an entry's data does not match the size its
162
+ * central-directory record declares.
42
163
  */
43
164
  const readZip = (input) => {
44
- const obj = unzipSync(input);
165
+ const { offset, count } = readCentralDirectoryLocation(input);
45
166
  const entries = [];
46
- for (const name of Object.keys(obj)) {
47
- const data = obj[name];
48
- if (data === void 0) continue;
49
- entries.push({
167
+ const seen = /* @__PURE__ */ new Set();
168
+ let cursor = offset;
169
+ for (let i = 0; i < count; i++) {
170
+ requireRange(input, cursor, CENTRAL_HEADER_SIZE, "central directory");
171
+ if (u32(input, cursor) !== SIG_CENTRAL_HEADER) throw invalid("bad central directory signature");
172
+ const flags = u16(input, cursor + 8);
173
+ const method = u16(input, cursor + 10);
174
+ const nameLength = u16(input, cursor + 28);
175
+ const extraLength = u16(input, cursor + 30);
176
+ const commentLength = u16(input, cursor + 32);
177
+ const nameStart = cursor + CENTRAL_HEADER_SIZE;
178
+ const extraStart = nameStart + nameLength;
179
+ const recordEnd = extraStart + extraLength + commentLength;
180
+ requireRange(input, cursor, recordEnd - cursor, "central directory");
181
+ const name = strFromU8(input.subarray(nameStart, extraStart), (flags & FLAG_UTF8) === 0);
182
+ const { compressedSize, uncompressedSize, localHeaderOffset } = resolveZip64Sizes(input, extraStart, extraStart + extraLength, {
183
+ compressedSize: u32(input, cursor + 20),
184
+ uncompressedSize: u32(input, cursor + 24),
185
+ localHeaderOffset: u32(input, cursor + 42)
186
+ });
187
+ cursor = recordEnd;
188
+ if (seen.has(name)) throw invalid(`duplicate entry "${name}"`);
189
+ seen.add(name);
190
+ requireRange(input, localHeaderOffset, LOCAL_HEADER_SIZE, `local header of "${name}"`);
191
+ if (u32(input, localHeaderOffset) !== SIG_LOCAL_HEADER) throw invalid(`bad local header signature for "${name}"`);
192
+ const dataStart = localHeaderOffset + LOCAL_HEADER_SIZE + u16(input, localHeaderOffset + 26) + u16(input, localHeaderOffset + 28);
193
+ requireRange(input, dataStart, compressedSize, `data of "${name}"`);
194
+ const compressed = input.subarray(dataStart, dataStart + compressedSize);
195
+ if (method === METHOD_STORE) {
196
+ if (compressedSize !== uncompressedSize) throw invalid(`stored entry "${name}" has mismatched sizes`);
197
+ entries.push({
198
+ name,
199
+ data: compressed.slice()
200
+ });
201
+ } else if (method === METHOD_DEFLATE) entries.push({
50
202
  name,
51
- data
203
+ data: inflateEntry(compressed, uncompressedSize, name)
52
204
  });
205
+ else throw invalid(`entry "${name}" uses unsupported compression method ${method}`);
53
206
  }
54
207
  return { entries };
55
208
  };
@@ -102,7 +255,7 @@ const dirname = (name) => {
102
255
  };
103
256
  /**
104
257
  * Resolves a relationship Target (which may be relative) against a base part
105
- * name. Returns the absolute part name (always starts with `/`).
258
+ * name, or against `PACKAGE_ROOT` for a package-root relationship. Returns the absolute part name (always starts with `/`).
106
259
  *
107
260
  * `target` is from a Relationship element's `Target` attribute when
108
261
  * `TargetMode="Internal"`. External targets must not be passed through here.
@@ -110,7 +263,7 @@ const dirname = (name) => {
110
263
  const resolveTarget = (basePart, target) => {
111
264
  if (target === "") throw new Error("relationship target is empty");
112
265
  if (target.startsWith("/")) return partName(target);
113
- const baseDir = dirname(basePart);
266
+ const baseDir = basePart === "/" ? "/" : dirname(basePart);
114
267
  const stack = baseDir === "/" ? [] : baseDir.slice(1).split("/");
115
268
  for (const segment of target.split("/")) {
116
269
  if (segment === "" || segment === ".") continue;
@@ -561,13 +714,46 @@ const rawName = (prefix, localName) => prefix === "" ? localName : `${prefix}:${
561
714
  const writeAttr = (a) => {
562
715
  return ` ${rawName(a.name.prefix, a.name.localName)}="${escapeAttr(a.value)}"`;
563
716
  };
564
- const writeElement = (e, parts) => {
717
+ const DOCUMENT_SCOPE = new Map([["xml", NS.xml]]);
718
+ const missingDecls = (e, scope) => {
719
+ let added = null;
720
+ for (let i = -1; i < e.attrs.length; i++) {
721
+ const name = i < 0 ? e.name : e.attrs[i].name;
722
+ if (i >= 0 && name.prefix === "") continue;
723
+ const own = e.prefixDecls.get(name.prefix) ?? added?.get(name.prefix);
724
+ if (own === void 0) {
725
+ if ((scope.get(name.prefix) ?? "") === name.namespaceURI) continue;
726
+ added ??= /* @__PURE__ */ new Map();
727
+ added.set(name.prefix, name.namespaceURI);
728
+ } else if (own !== name.namespaceURI) throw new Error(`prefix "${name.prefix}" on <${rawName(e.name.prefix, e.name.localName)}> is bound to both ${own} and ${name.namespaceURI}`);
729
+ }
730
+ return added;
731
+ };
732
+ const writeDecls = (decls, parts) => {
733
+ for (const [prefix, uri] of decls) {
734
+ const decl = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
735
+ parts.push(` ${decl}="${escapeAttr(uri)}"`);
736
+ }
737
+ };
738
+ const withDecls = (scope, decls) => {
739
+ if (decls.size === 0) return scope;
740
+ const next = new Map(scope);
741
+ for (const [prefix, uri] of decls) next.set(prefix, uri);
742
+ return next;
743
+ };
744
+ const writeElement = (e, parts, scope) => {
565
745
  const name = rawName(e.name.prefix, e.name.localName);
566
746
  parts.push("<");
567
747
  parts.push(name);
568
- for (const [prefix, uri] of e.prefixDecls) {
569
- const decl = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
570
- parts.push(` ${decl}="${escapeAttr(uri)}"`);
748
+ writeDecls(e.prefixDecls, parts);
749
+ let childScope = scope;
750
+ if (scope) {
751
+ const added = missingDecls(e, scope);
752
+ childScope = withDecls(scope, e.prefixDecls);
753
+ if (added) {
754
+ writeDecls(added, parts);
755
+ childScope = withDecls(childScope, added);
756
+ }
571
757
  }
572
758
  for (const a of e.attrs) parts.push(writeAttr(a));
573
759
  if (e.children.length === 0) {
@@ -575,15 +761,15 @@ const writeElement = (e, parts) => {
575
761
  return;
576
762
  }
577
763
  parts.push(">");
578
- for (const child of e.children) writeNode(child, parts);
764
+ for (const child of e.children) writeNode(child, parts, childScope);
579
765
  parts.push("</");
580
766
  parts.push(name);
581
767
  parts.push(">");
582
768
  };
583
- const writeNode = (n, parts) => {
769
+ const writeNode = (n, parts, scope) => {
584
770
  switch (n.kind) {
585
771
  case "element":
586
- writeElement(n, parts);
772
+ writeElement(n, parts, scope);
587
773
  return;
588
774
  case "text":
589
775
  parts.push(escapeText(n.data));
@@ -609,6 +795,7 @@ const writeNode = (n, parts) => {
609
795
  return;
610
796
  }
611
797
  };
798
+ const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
612
799
  const writeDeclaration = (doc, parts) => {
613
800
  if (!doc.decl) return;
614
801
  parts.push("<?xml version=\"");
@@ -629,15 +816,15 @@ const writeDeclaration = (doc, parts) => {
629
816
  const serializeXml = (doc, options = {}) => {
630
817
  const parts = [];
631
818
  if (doc.decl) writeDeclaration(doc, parts);
632
- else if (options.standaloneDeclaration ?? true) parts.push("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
633
- for (const p of doc.prolog) writeNode(p, parts);
634
- writeElement(doc.root, parts);
635
- for (const e of doc.epilog) writeNode(e, parts);
819
+ else if (options.standaloneDeclaration ?? true) parts.push(XML_DECLARATION);
820
+ for (const p of doc.prolog) writeNode(p, parts, null);
821
+ writeElement(doc.root, parts, DOCUMENT_SCOPE);
822
+ for (const e of doc.epilog) writeNode(e, parts, null);
636
823
  return parts.join("");
637
824
  };
638
825
  const serializeFragment = (element) => {
639
826
  const parts = [];
640
- writeElement(element, parts);
827
+ writeElement(element, parts, null);
641
828
  return parts.join("");
642
829
  };
643
830
  //#endregion
@@ -1301,4 +1488,4 @@ function describeConflict(conflict) {
1301
1488
  //#endregion
1302
1489
  export { sameDeck as i, describeConflict as n, mergeDecks as r, comparablePart as t };
1303
1490
 
1304
- //# sourceMappingURL=deck-merge-XZc2oNxd.js.map
1491
+ //# sourceMappingURL=deck-merge-Dj_XHQQM.js.map