@shbernal/ts-pptx 3.1.0 → 3.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.
Files changed (73) hide show
  1. package/README.md +55 -20
  2. package/dist/{autopage-QQyfRtoK.js → autopage-D1sqK6j7.js} +16 -15
  3. package/dist/{autopage-QQyfRtoK.js.map → autopage-D1sqK6j7.js.map} +1 -1
  4. package/dist/browser.d.ts +6 -6
  5. package/dist/browser.js +5 -6
  6. package/dist/browser.js.map +1 -1
  7. package/dist/{diagnostics-PMINsVP6.d.ts → diagnostics-B2yWpcjB.d.ts} +31 -14
  8. package/dist/{diagnostics-PMINsVP6.d.ts.map → diagnostics-B2yWpcjB.d.ts.map} +1 -1
  9. package/dist/{diagnostics-DYQC7xLV.js → diagnostics-H1QzgRSw.js} +0 -0
  10. package/dist/{diagnostics-DYQC7xLV.js.map → diagnostics-H1QzgRSw.js.map} +1 -1
  11. package/dist/{enums-CoXnxiCI.d.ts → enums-CvRYniUA.d.ts} +13 -3
  12. package/dist/{enums-CoXnxiCI.d.ts.map → enums-CvRYniUA.d.ts.map} +1 -1
  13. package/dist/{errors-BeUEkNWL.d.ts → errors-fB1KDJKQ.d.ts} +5 -5
  14. package/dist/{errors-BeUEkNWL.d.ts.map → errors-fB1KDJKQ.d.ts.map} +1 -1
  15. package/dist/{fit-DhMbQG5O.js → fit-CYJ1jkQI.js} +0 -0
  16. package/dist/fit-CYJ1jkQI.js.map +1 -0
  17. package/dist/{html-dom-DajBuobe.js → html-dom-CzewEdE1.js} +4 -5
  18. package/dist/{html-dom-DajBuobe.js.map → html-dom-CzewEdE1.js.map} +1 -1
  19. package/dist/html.d.ts +3 -3
  20. package/dist/html.js +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.js +5 -6
  23. package/dist/index.js.map +1 -1
  24. package/dist/inspect.d.ts +1 -1
  25. package/dist/inspect.js +4 -4
  26. package/dist/math.d.ts +1 -1
  27. package/dist/measure.d.ts +96 -6
  28. package/dist/measure.d.ts.map +1 -1
  29. package/dist/measure.js +2 -2
  30. package/dist/node.d.ts +6 -6
  31. package/dist/node.js +4 -5
  32. package/dist/node.js.map +1 -1
  33. package/dist/notes-SCvpJz0l.js +1646 -0
  34. package/dist/notes-SCvpJz0l.js.map +1 -0
  35. package/dist/{pres-BgvxhQ_m.d.ts → pres-Cb-1xDne.d.ts} +4 -4
  36. package/dist/{pres-BgvxhQ_m.d.ts.map → pres-Cb-1xDne.d.ts.map} +1 -1
  37. package/dist/{presentation-BP1B8PCF.js → presentation-DeKTJGHf.js} +1096 -440
  38. package/dist/presentation-DeKTJGHf.js.map +1 -0
  39. package/dist/{presentation-DyIRDKkp.d.ts → presentation-types-B50aOUFI.d.ts} +804 -39
  40. package/dist/presentation-types-B50aOUFI.d.ts.map +1 -0
  41. package/dist/read.d.ts +4 -5
  42. package/dist/read.d.ts.map +1 -1
  43. package/dist/read.js +2 -2
  44. package/dist/script.d.ts +32 -4
  45. package/dist/script.d.ts.map +1 -1
  46. package/dist/script.js +0 -0
  47. package/dist/script.js.map +1 -1
  48. package/dist/{shapes-DMThLsZN.js → shapes-BXVgsk0F.js} +81 -63
  49. package/dist/shapes-BXVgsk0F.js.map +1 -0
  50. package/dist/{slide-BRAtT8Dl.d.ts → slide-DWw9UoI6.d.ts} +130 -11
  51. package/dist/slide-DWw9UoI6.d.ts.map +1 -0
  52. package/dist/{st-enums-evQG2A7Z.js → st-enums-bVnezkMJ.js} +33 -12
  53. package/dist/{st-enums-evQG2A7Z.js.map → st-enums-bVnezkMJ.js.map} +1 -1
  54. package/dist/{text-B6PbMFih.js → text-uM-zGton.js} +242 -1611
  55. package/dist/text-uM-zGton.js.map +1 -0
  56. package/dist/{units-internal-CR8s8YQa.js → units-internal-CGf4VrXy.js} +156 -4
  57. package/dist/units-internal-CGf4VrXy.js.map +1 -0
  58. package/dist/zip.d.ts +2 -2
  59. package/package.json +40 -28
  60. package/skills/ts-pptx-upstream/SKILL.md +107 -14
  61. package/dist/embedded-fonts-MaynAHa9.js +0 -94
  62. package/dist/embedded-fonts-MaynAHa9.js.map +0 -1
  63. package/dist/fit-DhMbQG5O.js.map +0 -1
  64. package/dist/presentation-BP1B8PCF.js.map +0 -1
  65. package/dist/presentation-DyIRDKkp.d.ts.map +0 -1
  66. package/dist/presentation-types-Dj4riuDs.d.ts +0 -538
  67. package/dist/presentation-types-Dj4riuDs.d.ts.map +0 -1
  68. package/dist/shapes-DMThLsZN.js.map +0 -1
  69. package/dist/slide-BRAtT8Dl.d.ts.map +0 -1
  70. package/dist/text-B6PbMFih.js.map +0 -1
  71. package/dist/units-C7QM1KgH.js +0 -144
  72. package/dist/units-C7QM1KgH.js.map +0 -1
  73. package/dist/units-internal-CR8s8YQa.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { i as PackageReadError, n as InvalidOptionError, o as UnsupportedFeatureError, t as InternalError } from "./errors-Bvln0H-7.js";
2
- import { d as emuToInches } from "./units-C7QM1KgH.js";
3
- import { _ as SLIDE_LAYOUT_REL, b as THEME_REL, d as IMAGE_REL, f as NOTES_MASTER_REL, m as OFFICE_DOCUMENT_REL, p as NOTES_SLIDE_REL, v as SLIDE_MASTER_REL, y as SLIDE_REL } from "./st-enums-evQG2A7Z.js";
4
- import { i as FONT_REL_TYPE, n as FONT_DATA_CONTENT_TYPE, r as FONT_DATA_EXTENSION, t as EMBEDDED_FONT_SLOTS } from "./embedded-fonts-MaynAHa9.js";
2
+ import { ft as emuToInches } from "./units-internal-CGf4VrXy.js";
3
+ import { E as voidEl, T as raw, _ as FONT_DATA_EXTENSION, g as FONT_DATA_CONTENT_TYPE, h as EMBEDDED_FONT_SLOTS, n as makeXmlNotesMaster, o as makeXmlNotesSlideSkeleton, v as FONT_REL_TYPE, w as el } from "./notes-SCvpJz0l.js";
4
+ import { _ as SLIDE_LAYOUT_REL, b as THEME_REL, d as IMAGE_REL, f as NOTES_MASTER_REL, h as OFFICE_REL, m as OFFICE_DOCUMENT_REL, p as NOTES_SLIDE_REL, v as SLIDE_MASTER_REL, y as SLIDE_REL } from "./st-enums-bVnezkMJ.js";
5
5
  import { ZipWriter, readZip } from "./zip.js";
6
- import { $ as resolveColor, A as resolveColorElement, At as getOrAddChild, B as placeholderInheritedColor, Ct as childElements, D as readPatternFill, Dt as firstChild, E as spPrXfrmEmu, Et as escapeXmlAttribute, F as resolveSlideThemeParts, Ft as removeAttr, G as slideDefinesColor, H as placeholderInheritedRunProps, It as removeChildrenByQName, J as fmtEntry, K as slideDefinesProp, L as LST_STYLE_LEVELS, Lt as replaceInParent, M as resolveMasterColorContext, Mt as intValue, N as resolveNotesColorContext, Nt as ownerDocumentOf, O as readPictureFill, Ot as firstChildElement, P as resolveSlideColorContext, Pt as parseXml, Q as parseClrScheme, R as RUN_PROP_NAMES, Rt as serializeXml, St as boolValue, T as TextFrame, Tt as descendantsByTag, U as placeholderInheritedXfrm, V as placeholderInheritedListStyles, W as placeholderOf, X as isA, Y as intAttr, Z as parseClrMap, _t as resolveRelativePartName, at as RPR_FILL_AFTER, b as Picture, bt as OOXML_NS, c as wrapShapeElement, ct as SPPR_EFFECT_AFTER, dt as SPPR_SCENE3D_AFTER, et as resolveSchemeToken, ft as SPPR_SP3D_AFTER, gt as relsPartNameFor, ht as relativePartName, it as FILL_CHOICES, j as resolveLayoutColorContext, jt as insertInOrder, k as readGradientFill, kt as getElements, l as GraphicFrame, lt as SPPR_FILL_AFTER, mt as partNameToZipPath, n as buildShapes, nt as styleRefLine, ot as SHAPE_AFTER_SPPR, pt as partNameExtension, q as SCHEME_SLOTS, rt as substitutePhClr, st as SPPR_AFTER_XFRM, t as GroupShape, tt as styleRefFill, ut as SPPR_LN_AFTER, vt as resolveSingleRel, wt as createElement, x as AutoShape, xt as attr, yt as zipPathToPartName, z as placeholderInheritedAnchor, zt as setAttr } from "./shapes-DMThLsZN.js";
6
+ import { $ as parseClrScheme, A as readGradientFill, At as getElements, B as placeholderInheritedAnchor, Bt as setAttr, Ct as boolValue, D as spPrXfrmEmu, Dt as escapeXmlAttribute, E as TextFrame, Et as descendantsByTag, F as resolveSlideColorContext, Ft as parseXml, G as placeholderOf, H as placeholderInheritedListStyles, I as resolveSlideThemeParts, It as removeAttr, J as SCHEME_SLOTS, K as slideDefinesColor, Lt as removeChildrenByQName, M as resolveLayoutColorContext, Mt as insertInOrder, N as resolveMasterColorContext, Nt as intValue, O as readPatternFill, Ot as firstChild, P as resolveNotesColorContext, Pt as ownerDocumentOf, Q as parseClrMap, R as LST_STYLE_LEVELS, Rt as replaceInParent, S as AutoShape, St as attr, Tt as createElement, U as placeholderInheritedRunProps, V as placeholderInheritedColor, W as placeholderInheritedXfrm, X as intAttr, Y as fmtEntry, Z as isA, _t as relsPartNameFor, at as FILL_CHOICES, bt as zipPathToPartName, ct as SPPR_AFTER_XFRM, dt as SPPR_LN_AFTER, et as resolveColor, ft as SPPR_SCENE3D_AFTER, gt as relativePartName, ht as partNameToZipPath, it as substitutePhClr, j as resolveColorElement, jt as getOrAddChild, k as readPictureFill, kt as firstChildElement, l as wrapShapeElement, lt as SPPR_EFFECT_AFTER, mt as partNameExtension, n as buildShapes, nt as styleRefFill, ot as RPR_FILL_AFTER, pt as SPPR_SP3D_AFTER, q as slideDefinesProp, r as findShapeByIdDeep, rt as styleRefLine, st as SHAPE_AFTER_SPPR, t as GroupShape, tt as resolveSchemeToken, u as GraphicFrame, ut as SPPR_FILL_AFTER, vt as resolveRelativePartName, wt as childElements, x as Picture, xt as OOXML_NS, yt as resolveSingleRel, z as RUN_PROP_NAMES, zt as serializeXml } from "./shapes-BXVgsk0F.js";
7
7
  //#region src/read/opc/content-types.ts
8
8
  /**
9
9
  * Overlay over `[Content_Types].xml`, used to resolve part content types at
@@ -78,7 +78,7 @@ var ContentTypes = class ContentTypes {
78
78
  //#endregion
79
79
  //#region src/read/opc/part.ts
80
80
  const textDecoder$1 = new TextDecoder("utf-8");
81
- const textEncoder$4 = new TextEncoder();
81
+ const textEncoder$5 = new TextEncoder();
82
82
  const XML_DECLARATION_RE = /^\uFEFF?<\?xml[^>]*\?>\s*/;
83
83
  /**
84
84
  * One OPC part. Holds the original bytes from the zip for its whole life;
@@ -135,7 +135,7 @@ var Part = class {
135
135
  if (!this.#dirty) return this.#bytes;
136
136
  let xml = serializeXml(this.dom);
137
137
  if (!xml.startsWith("<?xml")) xml = (XML_DECLARATION_RE.exec(textDecoder$1.decode(this.#bytes))?.[0] ?? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n") + xml;
138
- return textEncoder$4.encode(xml);
138
+ return textEncoder$5.encode(xml);
139
139
  }
140
140
  };
141
141
  //#endregion
@@ -275,7 +275,7 @@ var Relationships = class Relationships {
275
275
  const CONTENT_TYPES_ZIP_PATH = "[Content_Types].xml";
276
276
  const RELATIONSHIPS_CONTENT_TYPE = "application/vnd.openxmlformats-package.relationships+xml";
277
277
  const textDecoder = new TextDecoder("utf-8");
278
- const textEncoder$3 = new TextEncoder();
278
+ const textEncoder$4 = new TextEncoder();
279
279
  /**
280
280
  * An OPC package loaded from `.pptx` bytes.
281
281
  *
@@ -415,7 +415,7 @@ var OpcPackage = class OpcPackage {
415
415
  for (const relationships of this.#relationshipsCache.values()) {
416
416
  if (!relationships.isDirty) continue;
417
417
  const relsPartName = relsPartNameFor(relationships.sourcePartName);
418
- const bytes = textEncoder$3.encode(relationships.serialize());
418
+ const bytes = textEncoder$4.encode(relationships.serialize());
419
419
  this.#parts.set(relsPartName, new Part(relsPartName, RELATIONSHIPS_CONTENT_TYPE, bytes));
420
420
  }
421
421
  }
@@ -1151,7 +1151,711 @@ var NotesSlide = class {
1151
1151
  const cSld = root && firstChild(root, "p:cSld");
1152
1152
  return cSld ? firstChild(cSld, "p:spTree") : null;
1153
1153
  }
1154
- };
1154
+ };
1155
+ //#endregion
1156
+ //#region src/read/api/ops/master-registry.ts
1157
+ /**
1158
+ * Slide-master bookkeeping in `presentation.xml` and in a master's own
1159
+ * `p:sldLayoutIdLst`: registering a master so renderers treat it as active,
1160
+ * promoting masters to the front of the id list, allocating ids out of the shared
1161
+ * master/layout id space, and emptying a freshly-copied master's layout list.
1162
+ *
1163
+ * These are the destination-side half of the import machinery -- they read and
1164
+ * write only this deck, never a source package and never the copy registry -- so
1165
+ * they take a structural {@link DeckTarget} rather than a `Presentation`. The
1166
+ * source-side half that drives most of them is `part-copy.ts`.
1167
+ */
1168
+ /**
1169
+ * Wire a freshly-copied slide master into `presentation.xml`: add a
1170
+ * presentation→master relationship and a `p:sldMasterId` entry in
1171
+ * `p:sldMasterIdLst`. A master that is reachable only through the
1172
+ * slide→layout→master rel chain but absent from `p:sldMasterIdLst` is treated
1173
+ * as inactive by renderers, so its background/graphics never paint. No-op when
1174
+ * the master is already registered (shared across imports from one source).
1175
+ */
1176
+ function registerMaster(dest, masterPartName) {
1177
+ const presPart = dest.presentationPart;
1178
+ const presRels = dest.opc.relationshipsFor(presPart.partName);
1179
+ for (const rel of presRels.byType(SLIDE_MASTER_REL)) if (presRels.resolveTarget(rel.id) === masterPartName) return;
1180
+ const relId = presRels.add(SLIDE_MASTER_REL, relativePartName(presPart.partName, masterPartName)).id;
1181
+ const root = presPart.dom.documentElement;
1182
+ if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a master in");
1183
+ const lst = getOrAddChild(root, "p:sldMasterIdLst", [
1184
+ "p:notesMasterIdLst",
1185
+ "p:handoutMasterIdLst",
1186
+ "p:sldIdLst",
1187
+ "p:sldSz",
1188
+ "p:notesSz",
1189
+ "p:embeddedFontLst",
1190
+ "p:custShowLst",
1191
+ "p:photoAlbum",
1192
+ "p:custDataLst",
1193
+ "p:kinsoku",
1194
+ "p:defaultTextStyle",
1195
+ "p:modifyVerifier",
1196
+ "p:extLst"
1197
+ ]);
1198
+ const entry = createElement(presPart.dom, "p:sldMasterId");
1199
+ setAttr(entry, "id", String(nextMasterLayoutId(dest)));
1200
+ setAttr(entry, "r:id", relId);
1201
+ lst.appendChild(entry);
1202
+ presPart.markDirty();
1203
+ }
1204
+ /**
1205
+ * Move the `p:sldMasterId` entries for `masterPartNames` to the front of
1206
+ * `p:sldMasterIdLst`, preserving their existing relative order, so the first of
1207
+ * them becomes the deck's `Designs(1)` — the theme PowerPoint's Design tab shows.
1208
+ * Reorders the id list only; relationships, ids, and all other parts are left as
1209
+ * they are. A no-op when the named masters already lead (idempotent re-call).
1210
+ */
1211
+ function promoteMasters(dest, masterPartNames) {
1212
+ if (masterPartNames.length === 0) return;
1213
+ const presPart = dest.presentationPart;
1214
+ const root = presPart.dom.documentElement;
1215
+ const lst = root && firstChild(root, "p:sldMasterIdLst");
1216
+ if (!lst) return;
1217
+ const rels = dest.opc.relationshipsFor(presPart.partName);
1218
+ const promote = new Set(masterPartNames);
1219
+ const entries = getElements(lst, "p:sldMasterId");
1220
+ const isPromoted = (entry) => {
1221
+ const relId = attr(entry, "r:id");
1222
+ return relId ? promote.has(rels.resolveTarget(relId)) : false;
1223
+ };
1224
+ const desired = [...entries.filter(isPromoted), ...entries.filter((e) => !isPromoted(e))];
1225
+ if (desired.every((entry, i) => entry === entries[i])) return;
1226
+ for (const entry of desired) lst.appendChild(entry);
1227
+ presPart.markDirty();
1228
+ }
1229
+ /**
1230
+ * The next free id in the presentation-wide slide-master / slide-layout id space.
1231
+ *
1232
+ * `p:sldMasterId/@id` and every master's `p:sldLayoutId/@id` draw from ONE shared
1233
+ * id space (both ST_SlideMasterId/ST_SlideLayoutId, floor 2147483648) and must be
1234
+ * unique across the WHOLE presentation, not just within their own list. A duplicate
1235
+ * anywhere in this space makes PowerPoint report the file as corrupt on open
1236
+ * ("found a problem with content" / 0x80070570), even though LibreOffice silently
1237
+ * tolerates it — so an imported master AND each of its layouts must take an id past
1238
+ * the max of both the master-id list and every layout-id list. Recomputed per
1239
+ * allocation so ids appended earlier in the same import are counted.
1240
+ */
1241
+ function nextMasterLayoutId(dest) {
1242
+ let max = 2147483647;
1243
+ const presPart = dest.presentationPart;
1244
+ const root = presPart.dom.documentElement;
1245
+ const masterLst = root && firstChild(root, "p:sldMasterIdLst");
1246
+ if (masterLst) for (const entry of getElements(masterLst, "p:sldMasterId")) {
1247
+ const id = intValue(attr(entry, "id"));
1248
+ if (id !== null && id > max) max = id;
1249
+ }
1250
+ const presRels = dest.opc.relationshipsFor(presPart.partName);
1251
+ for (const rel of presRels.byType(SLIDE_MASTER_REL)) {
1252
+ const masterRoot = dest.opc.part(presRels.resolveTarget(rel.id))?.dom.documentElement;
1253
+ const layoutLst = masterRoot && firstChild(masterRoot, "p:sldLayoutIdLst");
1254
+ if (!layoutLst) continue;
1255
+ for (const entry of getElements(layoutLst, "p:sldLayoutId")) {
1256
+ const id = intValue(attr(entry, "id"));
1257
+ if (id !== null && id > max) max = id;
1258
+ }
1259
+ }
1260
+ return max + 1;
1261
+ }
1262
+ /** Empty a freshly-copied master's `p:sldLayoutIdLst`; copied layouts repopulate it. */
1263
+ function clearLayoutIdList(dest, masterPartName) {
1264
+ const masterPart = dest.opc.part(masterPartName);
1265
+ const root = masterPart?.dom.documentElement;
1266
+ const lst = root && firstChild(root, "p:sldLayoutIdLst");
1267
+ if (!masterPart || !lst) return;
1268
+ removeChildrenByQName(lst, ["p:sldLayoutId"]);
1269
+ masterPart.markDirty();
1270
+ }
1271
+ /**
1272
+ * Append a `p:sldLayoutId` entry for `layoutPartName` to `masterPartName`'s
1273
+ * `p:sldLayoutIdLst`, adding the master→layout relationship it points at. The
1274
+ * destination-side half of linking a copied layout into its copied master; the
1275
+ * caller resolves which master that is from the source-side rels.
1276
+ */
1277
+ function addLayoutToMaster(dest, masterPartName, layoutPartName) {
1278
+ const masterPart = dest.opc.part(masterPartName);
1279
+ const root = masterPart?.dom.documentElement;
1280
+ if (!masterPart || !root) return;
1281
+ const relId = dest.opc.relationshipsFor(masterPartName).add(SLIDE_LAYOUT_REL, relativePartName(masterPartName, layoutPartName)).id;
1282
+ const lst = getOrAddChild(root, "p:sldLayoutIdLst", [
1283
+ "p:transition",
1284
+ "p:timing",
1285
+ "p:hf",
1286
+ "p:txStyles",
1287
+ "p:extLst"
1288
+ ]);
1289
+ const entry = createElement(masterPart.dom, "p:sldLayoutId");
1290
+ setAttr(entry, "id", String(nextMasterLayoutId(dest)));
1291
+ setAttr(entry, "r:id", relId);
1292
+ lst.appendChild(entry);
1293
+ masterPart.markDirty();
1294
+ }
1295
+ //#endregion
1296
+ //#region src/read/api/ops/page-owned.ts
1297
+ /** Relationship types whose target two copies of one page may point at together. */
1298
+ const SHARED_BY_PAGE_COPIES = /* @__PURE__ */ new Set([
1299
+ SLIDE_LAYOUT_REL,
1300
+ SLIDE_MASTER_REL,
1301
+ NOTES_MASTER_REL,
1302
+ THEME_REL,
1303
+ OFFICE_REL + "themeOverride",
1304
+ OFFICE_REL + "handoutMaster",
1305
+ OFFICE_REL + "presProps",
1306
+ OFFICE_REL + "viewProps",
1307
+ OFFICE_REL + "tableStyles",
1308
+ OFFICE_REL + "commentAuthors",
1309
+ "http://schemas.microsoft.com/office/2018/10/relationships/authors",
1310
+ IMAGE_REL,
1311
+ OFFICE_REL + "audio",
1312
+ OFFICE_REL + "video",
1313
+ "http://schemas.microsoft.com/office/2007/relationships/media",
1314
+ "http://schemas.microsoft.com/office/2017/06/relationships/model3d",
1315
+ OFFICE_REL + "font",
1316
+ SLIDE_REL,
1317
+ OFFICE_REL + "hyperlink"
1318
+ ]);
1319
+ /**
1320
+ * Whether a page copy may point at this relationship's target rather than taking
1321
+ * a copy of it. Everything not listed is owned by the page and copied — see the
1322
+ * module comment for why that is the safe default.
1323
+ */
1324
+ function isSharedByPageCopies(relType) {
1325
+ return SHARED_BY_PAGE_COPIES.has(relType);
1326
+ }
1327
+ /**
1328
+ * Give a just-cloned page its own copy of every part the source page owned,
1329
+ * within one package. The clone's relationships start as a byte copy of the
1330
+ * source page's, so this walks them and repoints the owned ones at fresh parts;
1331
+ * shared targets are left alone.
1332
+ *
1333
+ * The subtree under each owned part is copied the same way, so a chart's workbook
1334
+ * and user-shapes drawing come along while the image inside that drawing stays
1335
+ * shared. A relationship *back* to the source page — a notes slide names the
1336
+ * slide it annotates — is repointed at the clone, which is what makes the copied
1337
+ * notes belong to it.
1338
+ *
1339
+ * A dangling relationship is left dangling rather than made to throw: cloning a
1340
+ * damaged deck is not this function's problem to discover.
1341
+ *
1342
+ * @param opc the package both pages live in
1343
+ * @param sourcePartName partname of the page that was cloned
1344
+ * @param clonePartName partname of the clone, whose rels are still the source's
1345
+ */
1346
+ function duplicateOwnedTargets(opc, sourcePartName, clonePartName) {
1347
+ const copies = /* @__PURE__ */ new Map([[sourcePartName, clonePartName]]);
1348
+ const rels = opc.relationshipsFor(clonePartName);
1349
+ for (const rel of Array.from(rels)) {
1350
+ if (rel.targetMode === "External" || isSharedByPageCopies(rel.type)) continue;
1351
+ const target = rels.resolveTarget(rel.id);
1352
+ const fresh = duplicateSubtree(opc, target, copies);
1353
+ if (fresh === target) continue;
1354
+ rels.remove(rel.id);
1355
+ rels.addWithId(rel.id, rel.type, relativePartName(clonePartName, fresh));
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Copy `partName` and, recursively, every part it owns, into fresh partnames.
1360
+ * `copies` dedupes within the one page copy, so a part two of the page's
1361
+ * relationships reach is copied once here even though the next page copy gets its
1362
+ * own. Returns the copy's partname, or `partName` unchanged when there is no such
1363
+ * part to copy.
1364
+ */
1365
+ function duplicateSubtree(opc, partName, copies) {
1366
+ const already = copies.get(partName);
1367
+ if (already !== void 0) return already;
1368
+ const part = opc.part(partName);
1369
+ if (!part) return partName;
1370
+ const fresh = opc.reservePartNameLike(partName);
1371
+ opc.addPart(fresh, part.contentType, part.bytes);
1372
+ copies.set(partName, fresh);
1373
+ const sourceRels = opc.relationshipsFor(partName);
1374
+ const freshRels = opc.relationshipsFor(fresh);
1375
+ for (const rel of sourceRels) {
1376
+ if (rel.targetMode === "External") {
1377
+ freshRels.addWithId(rel.id, rel.type, rel.target, "External");
1378
+ continue;
1379
+ }
1380
+ const target = sourceRels.resolveTarget(rel.id);
1381
+ const newTarget = isSharedByPageCopies(rel.type) ? copies.get(target) ?? target : duplicateSubtree(opc, target, copies);
1382
+ freshRels.addWithId(rel.id, rel.type, relativePartName(fresh, newTarget));
1383
+ }
1384
+ return fresh;
1385
+ }
1386
+ //#endregion
1387
+ //#region src/read/api/ops/part-copy.ts
1388
+ const SLIDE_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
1389
+ const SLIDE_LAYOUT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
1390
+ const NOTES_SLIDE_CONTENT_TYPE$2 = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
1391
+ /** Open an ownership scope for one page copy. */
1392
+ function newOwnedScope() {
1393
+ return /* @__PURE__ */ new Map();
1394
+ }
1395
+ /**
1396
+ * Copy one slide page across as a part of its own, deduping everything under it
1397
+ * but never the page itself. This is the `theme: 'copy'` arm of
1398
+ * {@link Presentation.importSlide}.
1399
+ *
1400
+ * The page is the one part an import is *not* allowed to share. `copyPart`'s
1401
+ * registry idempotence is right for a theme, master, layout or image, where a
1402
+ * second copy is waste; applied to the slide it made a repeated import of the
1403
+ * same source page return the first copy's partname, and the caller then wired a
1404
+ * second `p:sldId` to a part that already had one — a package PowerPoint refuses
1405
+ * to open (0x80070570), with nothing in the read model to show for it. So the
1406
+ * page goes into the selection plan `copyPart` already honours for a batch, which
1407
+ * re-materializes exactly the named pages and leaves the dedup of their
1408
+ * dependencies alone.
1409
+ *
1410
+ * @param ctx the open import out of the source package
1411
+ * @param sourcePartName partname of the source slide to bring across
1412
+ * @return partname of the new slide part in `ctx.dest`
1413
+ */
1414
+ function copySlidePart(ctx, sourcePartName) {
1415
+ const destinations = /* @__PURE__ */ new Map([[sourcePartName, ctx.dest.opc.reservePartNameLike(sourcePartName)]]);
1416
+ return copyPart({
1417
+ ...ctx,
1418
+ selection: { destinations }
1419
+ }, sourcePartName);
1420
+ }
1421
+ /**
1422
+ * Copy `sourcePartName` (and, recursively, every internal part it references)
1423
+ * from `ctx.source` into `ctx.dest`, returning the new partname. Idempotent
1424
+ * per source package via the copy registry. Relationship ids are preserved so
1425
+ * the copied part body's `r:id`/`r:embed` references stay valid; targets are
1426
+ * rewritten to the freshly-allocated partnames. Notes relationships are
1427
+ * dropped. A copied `slideMaster` does not drag in all its sibling layouts —
1428
+ * each imported `slideLayout` wires itself into the master instead (see
1429
+ * {@link linkLayoutIntoMaster}).
1430
+ *
1431
+ * With `ctx.selection`, partnames for the selected slides were already reserved
1432
+ * by the caller ({@link Presentation.importSlides}); this traversal wires their
1433
+ * relationships to each other instead of re-copying them. The batch's rule that
1434
+ * a `slide → slide` link may not leave the selection is enforced ahead of the
1435
+ * copy by {@link checkSelectionCopyable}, which also proves every part this
1436
+ * traversal will reach exists — so once copying starts there is nothing left to
1437
+ * throw, and a rejected batch never leaves a half-copied deck behind.
1438
+ *
1439
+ * Idempotence stops at the page's own parts. Reaching a page opens an
1440
+ * {@link OwnedScope}, and everything under it that {@link isSharedByPageCopies}
1441
+ * does not clear for sharing — a chart, a diagram, an OLE embedding, each with
1442
+ * its own subtree — is copied into that scope instead of the registry, so the
1443
+ * next copy of the page gets parts of its own. Pass `owned` to open the scope
1444
+ * from outside, as the rebinding import paths do for the page they build
1445
+ * themselves.
1446
+ */
1447
+ function copyPart(ctx, sourcePartName, owned) {
1448
+ if (owned) {
1449
+ const alreadyOwned = owned.get(sourcePartName);
1450
+ if (alreadyOwned !== void 0) return alreadyOwned;
1451
+ } else {
1452
+ const existing = ctx.registry.get(sourcePartName);
1453
+ const selectedDest = ctx.selection?.destinations.get(sourcePartName);
1454
+ if (existing && (selectedDest === void 0 || existing === selectedDest)) return existing;
1455
+ }
1456
+ const sourcePart = ctx.source.part(sourcePartName);
1457
+ if (!sourcePart) throw new PackageReadError("package/part-missing", `importSlide: source package has no part ${sourcePartName}`);
1458
+ const newPartName = owned ? ctx.dest.opc.reservePartNameLike(sourcePartName) : ctx.selection?.destinations.get(sourcePartName) ?? ctx.dest.opc.reservePartNameLike(sourcePartName);
1459
+ if (!ctx.dest.opc.part(newPartName)) ctx.dest.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
1460
+ if (owned) owned.set(sourcePartName, newPartName);
1461
+ else ctx.registry.set(sourcePartName, newPartName);
1462
+ const scope = owned ?? (ctx.selection?.destinations.has(sourcePartName) ? newOwnedScope() : void 0);
1463
+ const isMaster = sourcePart.contentType === SLIDE_MASTER_CONTENT_TYPE;
1464
+ const sourceRels = ctx.source.relationshipsFor(sourcePartName);
1465
+ const targetRels = ctx.dest.opc.relationshipsFor(newPartName);
1466
+ for (const rel of sourceRels) {
1467
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
1468
+ if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
1469
+ if (rel.targetMode === "External") {
1470
+ targetRels.addWithId(rel.id, rel.type, rel.target, "External");
1471
+ continue;
1472
+ }
1473
+ const newTargetPartName = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : scope);
1474
+ targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTargetPartName));
1475
+ }
1476
+ if (isMaster) {
1477
+ clearLayoutIdList(ctx.dest, newPartName);
1478
+ registerMaster(ctx.dest, newPartName);
1479
+ }
1480
+ if (sourcePart.contentType === SLIDE_LAYOUT_CONTENT_TYPE) linkLayoutIntoMaster(ctx, sourceRels, newPartName);
1481
+ return newPartName;
1482
+ }
1483
+ const NO_NOTES = {
1484
+ pages: /* @__PURE__ */ new Set(),
1485
+ copyMaster: false
1486
+ };
1487
+ /**
1488
+ * Dry-run {@link copyPart} over one source's batch selection, reading only the
1489
+ * *source* package: walk the graph the copy will walk, under the same
1490
+ * relationship-skipping rules, and throw what the copy would throw — a source
1491
+ * part that is missing, XML that will not parse, or a `slide → slide` link that
1492
+ * escapes the selection.
1493
+ *
1494
+ * This is what makes a rejected batch leave the destination byte-identical.
1495
+ * `importSlides` reserves partnames and mutates the destination only after this
1496
+ * passes, at which point the copy has no reachable failure left; a check that
1497
+ * lived inside the traversal instead would fire with parts already added, a
1498
+ * master already registered in `presentation.xml`, and no way back.
1499
+ *
1500
+ * Keep this in step with `copyPart`: the two must skip the same relationships
1501
+ * and stop at the same registry hits, or the guarantee is only as good as the
1502
+ * drift between them. With `notes` the same obligation extends to `carryNotes`,
1503
+ * which runs after the copy and is the batch's other way to reach a source part.
1504
+ *
1505
+ * @param source the package the pages are coming from
1506
+ * @param registry the copy registry for that source (parts already in `dest`)
1507
+ * @param selected source partnames of the pages this batch selected
1508
+ * @param notes the pages of `selected` whose notes travel too; none by default
1509
+ */
1510
+ function checkSelectionCopyable(source, registry, selected, notes = NO_NOTES) {
1511
+ const visited = /* @__PURE__ */ new Set();
1512
+ const walk = (partName) => {
1513
+ if (visited.has(partName)) return;
1514
+ if (registry.has(partName) && !selected.has(partName)) return;
1515
+ visited.add(partName);
1516
+ const part = source.part(partName);
1517
+ if (!part) throw new PackageReadError("package/part-missing", `importSlides: source package has no part ${partName}`);
1518
+ const isMaster = part.contentType === SLIDE_MASTER_CONTENT_TYPE;
1519
+ const isNotesSlide = part.contentType === NOTES_SLIDE_CONTENT_TYPE$2;
1520
+ if (isMaster || part.contentType === SLIDE_LAYOUT_CONTENT_TYPE) part.dom;
1521
+ const rels = source.relationshipsFor(partName);
1522
+ for (const rel of rels) {
1523
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") {
1524
+ if (notes.pages.has(partName)) walk(rels.resolveTarget(rel.id));
1525
+ continue;
1526
+ }
1527
+ if (isNotesSlide) {
1528
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") continue;
1529
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" && !notes.copyMaster) continue;
1530
+ }
1531
+ if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
1532
+ if (rel.targetMode === "External") continue;
1533
+ const targetPartName = rels.resolveTarget(rel.id);
1534
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" && !selected.has(targetPartName) && !registry.has(targetPartName)) throw new InvalidOptionError("import/unresolved-slide-link", `importSlides: source slide ${partName} links to ${targetPartName}, which is not among the selected imported pages`);
1535
+ walk(targetPartName);
1536
+ }
1537
+ };
1538
+ for (const partName of selected) walk(partName);
1539
+ }
1540
+ /**
1541
+ * Wire a just-copied layout into its (already-copied) master. Resolves which
1542
+ * destination master that is by running the layout's *source* master rel through
1543
+ * the copy registry, then hands the destination-side wiring to
1544
+ * {@link addLayoutToMaster}. Called once per copied layout, so the master
1545
+ * accumulates exactly the imported layouts.
1546
+ */
1547
+ function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
1548
+ const masterRel = layoutSourceRels.byType(SLIDE_MASTER_REL)[0];
1549
+ if (!masterRel) return;
1550
+ const masterPartName = ctx.registry.get(layoutSourceRels.resolveTarget(masterRel.id));
1551
+ if (!masterPartName) return;
1552
+ addLayoutToMaster(ctx.dest, masterPartName, layoutPartName);
1553
+ }
1554
+ //#endregion
1555
+ //#region src/read/api/ops/notes-master.ts
1556
+ /**
1557
+ * Carrying speaker notes and the deck's single notes master across an import or an append.
1558
+ *
1559
+ * A `notesSlide` is not self-contained: it back-references the slide it annotates and forward-
1560
+ * references the deck's notes master. `CT_NotesMasterIdList` allows at most one master per
1561
+ * presentation, so a deck that already has one keeps it — the destination's notes styling wins —
1562
+ * and a source master is copied only into a deck that has none. That single rule is why both the
1563
+ * `Presentation`-to-`Presentation` path and the generator-XML path funnel through
1564
+ * `registerNotesMaster`: they differ only in how the *part* comes into being.
1565
+ */
1566
+ const NOTES_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
1567
+ const THEME_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml";
1568
+ const textEncoder$3 = new TextEncoder();
1569
+ /**
1570
+ * Carry the source slide's speaker notes onto the just-imported slide (the
1571
+ * `importNotes` option). The slide copy itself dropped the `notesSlide` rel, so
1572
+ * this copies the source `notesSlide` part into a fresh partname, wires a
1573
+ * `slide → notesSlide` rel on the new slide, and rebuilds the copied notesSlide's
1574
+ * own relationships:
1575
+ *
1576
+ * - its `slide` back-rel is repointed at the new slide (`newSlidePartName`) — the
1577
+ * source slide is *not* copied (that would be circular);
1578
+ * - its `notesMaster` rel is resolved through {@link ensureNotesMaster}, which
1579
+ * reuses this deck's notesMaster when it has one and copies the source's only
1580
+ * when it has none (a deck may have at most one notesMaster);
1581
+ * - any other internal target (media, etc.) is copied via {@link copyPart}, under an
1582
+ * ownership scope so a second copy of the page gets notes parts of its own.
1583
+ *
1584
+ * No-op when the source slide has no notes. Content-type registration for the
1585
+ * copied parts is handled by `addPart`/{@link copyPart}.
1586
+ */
1587
+ function carryNotes(dest, source, ctx, sourceSlidePartName, newSlidePartName) {
1588
+ const sourceSlideRels = source.opc.relationshipsFor(sourceSlidePartName);
1589
+ const notesRel = sourceSlideRels.byType(NOTES_SLIDE_REL)[0];
1590
+ if (!notesRel) return;
1591
+ const sourceNotesPartName = sourceSlideRels.resolveTarget(notesRel.id);
1592
+ const sourceNotesPart = source.opc.part(sourceNotesPartName);
1593
+ if (!sourceNotesPart) return;
1594
+ const newNotesPartName = dest.opc.reservePartNameLike(sourceNotesPartName);
1595
+ dest.opc.addPart(newNotesPartName, sourceNotesPart.contentType, sourceNotesPart.bytes);
1596
+ dest.opc.relationshipsFor(newSlidePartName).add(NOTES_SLIDE_REL, relativePartName(newSlidePartName, newNotesPartName));
1597
+ const owned = newOwnedScope();
1598
+ owned.set(sourceNotesPartName, newNotesPartName);
1599
+ const notesSourceRels = source.opc.relationshipsFor(sourceNotesPartName);
1600
+ const notesTargetRels = dest.opc.relationshipsFor(newNotesPartName);
1601
+ for (const rel of notesSourceRels) {
1602
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") {
1603
+ notesTargetRels.addWithId(rel.id, SLIDE_REL, relativePartName(newNotesPartName, newSlidePartName));
1604
+ continue;
1605
+ }
1606
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster") {
1607
+ const notesMaster = ensureNotesMaster(dest, ctx, notesSourceRels.resolveTarget(rel.id));
1608
+ notesTargetRels.addWithId(rel.id, NOTES_MASTER_REL, relativePartName(newNotesPartName, notesMaster));
1609
+ continue;
1610
+ }
1611
+ if (rel.targetMode === "External") {
1612
+ notesTargetRels.addWithId(rel.id, rel.type, rel.target, "External");
1613
+ continue;
1614
+ }
1615
+ const newTarget = copyPart(ctx, notesSourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
1616
+ notesTargetRels.addWithId(rel.id, rel.type, relativePartName(newNotesPartName, newTarget));
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Resolve the notesMaster an imported `notesSlide` should bind to, honouring the
1621
+ * single-notesMaster-per-presentation rule (`p:notesMasterIdLst` holds 0..1
1622
+ * `p:notesMasterId`). If this deck already has a notesMaster it is reused and the
1623
+ * source's is *not* copied (the destination's notes styling wins); otherwise the
1624
+ * source notesMaster (and, via {@link copyPart}, its theme) is copied and
1625
+ * registered in `presentation.xml`. Returns the destination notesMaster partname.
1626
+ */
1627
+ function ensureNotesMaster(dest, ctx, sourceNotesMasterPartName) {
1628
+ const presPart = dest.presentationPart;
1629
+ const presRels = dest.opc.relationshipsFor(presPart.partName);
1630
+ const existing = presRels.byType(NOTES_MASTER_REL)[0];
1631
+ if (existing) return presRels.resolveTarget(existing.id);
1632
+ return registerNotesMaster(dest, copyPart(ctx, sourceNotesMasterPartName));
1633
+ }
1634
+ /**
1635
+ * Wire an already-added notesMaster part into `presentation.xml`: a `notesMaster`
1636
+ * relationship plus the single `p:notesMasterId` entry that `CT_NotesMasterIdList`
1637
+ * allows. Returns the partname, so callers can use it as a rel target.
1638
+ *
1639
+ * Split out of {@link ensureNotesMaster} because the two ways a notesMaster arrives
1640
+ * — copied from another `Presentation`, or authored by a generator and injected by
1641
+ * {@link Presentation.appendSlides} — differ only in how the *part* is created, not in how it is
1642
+ * registered.
1643
+ */
1644
+ function registerNotesMaster(dest, notesMasterPartName) {
1645
+ const presPart = dest.presentationPart;
1646
+ const relId = dest.opc.relationshipsFor(presPart.partName).add(NOTES_MASTER_REL, relativePartName(presPart.partName, notesMasterPartName)).id;
1647
+ const root = presPart.dom.documentElement;
1648
+ if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a notes master in");
1649
+ const lst = getOrAddChild(root, "p:notesMasterIdLst", [
1650
+ "p:handoutMasterIdLst",
1651
+ "p:sldIdLst",
1652
+ "p:sldSz",
1653
+ "p:notesSz",
1654
+ "p:embeddedFontLst",
1655
+ "p:custShowLst",
1656
+ "p:photoAlbum",
1657
+ "p:custDataLst",
1658
+ "p:kinsoku",
1659
+ "p:defaultTextStyle",
1660
+ "p:modifyVerifier",
1661
+ "p:extLst"
1662
+ ]);
1663
+ removeChildrenByQName(lst, ["p:notesMasterId"]);
1664
+ const entry = createElement(presPart.dom, "p:notesMasterId");
1665
+ setAttr(entry, "r:id", relId);
1666
+ lst.appendChild(entry);
1667
+ presPart.markDirty();
1668
+ return notesMasterPartName;
1669
+ }
1670
+ /**
1671
+ * Resolve the notesMaster an *appended* slide's notes should bind to. Same
1672
+ * single-notesMaster rule as {@link ensureNotesMaster}: this deck's own wins when it
1673
+ * has one, so the destination's notes styling is preserved and `master.xml` is
1674
+ * discarded. Otherwise the generator's notes master is installed, together with the
1675
+ * theme its `.rels` requires (the normal write path emits that as `theme2.xml`).
1676
+ */
1677
+ function ensureNotesMasterFromXml(dest, master) {
1678
+ const presPart = dest.presentationPart;
1679
+ const presRels = dest.opc.relationshipsFor(presPart.partName);
1680
+ const existing = presRels.byType(NOTES_MASTER_REL)[0];
1681
+ if (existing) return presRels.resolveTarget(existing.id);
1682
+ const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
1683
+ dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$3.encode(master.xml));
1684
+ const themePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
1685
+ dest.opc.addPart(themePartName, THEME_CONTENT_TYPE, textEncoder$3.encode(master.themeXml));
1686
+ dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, themePartName));
1687
+ return registerNotesMaster(dest, masterPartName);
1688
+ }
1689
+ /**
1690
+ * Resolve the notesMaster a *newly authored* notes slide should bind to
1691
+ * ({@link Slide.addNotes} on a loaded deck). Same single-notesMaster rule as
1692
+ * {@link ensureNotesMaster} and {@link ensureNotesMasterFromXml}: this deck's own
1693
+ * wins when it has one. The third way a notesMaster arrives, and the only one with
1694
+ * no source deck and no generator instance behind it, so it has to build both parts
1695
+ * itself.
1696
+ *
1697
+ * The *theme* is where it differs from the append path. `ensureNotesMasterFromXml`
1698
+ * installs the generator's `theme2.xml` because a generator deck's notes were
1699
+ * authored against it; there is no such theme here, so the deck's **own** theme
1700
+ * (reached through the annotated slide's layout → master → theme chain) is cloned
1701
+ * into a fresh part instead. That is both the closer match to "the destination's
1702
+ * notes styling wins" and the safer package: the notesMaster gets a theme part of
1703
+ * its own rather than a second relationship onto the slide master's, which is the
1704
+ * arrangement PowerPoint writes (`theme1.xml` for the master, `theme2.xml` for the
1705
+ * notes master).
1706
+ *
1707
+ * Throws when the deck has no theme to clone — a package that malformed cannot be
1708
+ * given a valid notesMaster, and silently emitting one without a resolvable theme
1709
+ * is what PowerPoint reports as a repair prompt rather than a bad edit.
1710
+ * @param {Presentation} dest - the deck being authored onto
1711
+ * @param {string} slidePartName - the slide whose notes are being authored, used to find the theme
1712
+ * @return {string} the destination notesMaster partname
1713
+ */
1714
+ function ensureNotesMasterForAuthoring(dest, slidePartName) {
1715
+ const presPart = dest.presentationPart;
1716
+ const presRels = dest.opc.relationshipsFor(presPart.partName);
1717
+ const existing = presRels.byType(NOTES_MASTER_REL)[0];
1718
+ if (existing) return presRels.resolveTarget(existing.id);
1719
+ const themePartName = resolveSlideThemeParts(dest.opc, slidePartName).themePartName;
1720
+ const themePart = themePartName ? dest.opc.part(themePartName) : void 0;
1721
+ if (!themePart) throw new PackageReadError("package/part-missing", `addNotes: no theme reachable from ${slidePartName} to bind a new notes master to`);
1722
+ const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
1723
+ dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$3.encode(makeXmlNotesMaster()));
1724
+ const notesThemePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
1725
+ dest.opc.addPart(notesThemePartName, themePart.contentType, themePart.bytes);
1726
+ dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, notesThemePartName));
1727
+ return registerNotesMaster(dest, masterPartName);
1728
+ }
1729
+ //#endregion
1730
+ //#region src/read/api/ops/notes-author.ts
1731
+ /**
1732
+ * Authoring speaker notes onto a slide of a *loaded* deck ({@link Slide.addNotes}).
1733
+ *
1734
+ * The third way a notes slide comes into being, after the two in `notes-master.ts`
1735
+ * (carried from another `Presentation` by `importSlide({ importNotes: true })`, or
1736
+ * emitted by the generator and spliced on by `appendSlides`). Those two both *move*
1737
+ * an existing part; this one has no source part to move, so it builds one.
1738
+ *
1739
+ * The part it builds is the generator's, not a second design: the fixed three-
1740
+ * placeholder frame comes from {@link makeXmlNotesSlideSkeleton}, and the body
1741
+ * paragraphs carry the same elements and attributes the write path emits for a plain
1742
+ * `pptx.addSlide().addNotes(text)`. `test/read/add-notes.test.js` asserts that
1743
+ * equivalence, so the two cannot drift apart unnoticed — the same guarantee
1744
+ * `src/ooxml/` gives the constants both halves share.
1745
+ *
1746
+ * The one difference is empty-element *spelling*: the write path emits `a:rPr` as an
1747
+ * open/close pair and this emits it self-closed, because a part the read model
1748
+ * authors is reserialized through `read/oxml/dom.ts`, which self-closes every empty
1749
+ * element. Writing the open/close form here would not survive the first save, so the
1750
+ * form that does is written in the first place and the test normalizes the spelling
1751
+ * rather than pretending the bytes match.
1752
+ *
1753
+ * Notes text is split on `\n` into paragraphs, matching the write-side `addNotes`;
1754
+ * runs carry no formatting of their own. Per-run formatting, hyperlinks, and
1755
+ * multi-run paragraphs are reached afterwards through `slide.notesTextFrame`, which
1756
+ * is the read model's normal text-editing surface.
1757
+ */
1758
+ const NOTES_SLIDE_CONTENT_TYPE$1 = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
1759
+ const A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
1760
+ const textEncoder$2 = new TextEncoder();
1761
+ /**
1762
+ * The `a:p` children of a notes body, one per `\n`-separated line: the write path's
1763
+ * output for an unformatted note, modulo the empty-element spelling described above.
1764
+ * An empty line yields a paragraph with no run, exactly as `genXmlNotesParagraphs`
1765
+ * does.
1766
+ * @param {string} text - the note, `\n` separating paragraphs
1767
+ * @return {string} concatenated `a:p` XML
1768
+ */
1769
+ function notesParagraphsXml(text) {
1770
+ return text.split("\n").map((line) => line.replace(/\r/g, "")).map((line) => el("a:p", null, [raw(line === "" ? "" : el("a:r", null, [raw(voidEl("a:rPr", {
1771
+ lang: "en-US",
1772
+ dirty: 0
1773
+ })), raw(el("a:t", null, line))])), raw(voidEl("a:endParaRPr", {
1774
+ lang: "en-US",
1775
+ dirty: 0
1776
+ }))])).join("");
1777
+ }
1778
+ /**
1779
+ * Replace a notes body's paragraphs with `text`, in the body's own document.
1780
+ *
1781
+ * The path taken when the slide *already* has a notes part — an imported one, or one
1782
+ * an earlier `addNotes` wrote. Only the `a:p` children are replaced, so the part's
1783
+ * geometry, its `a:bodyPr`/`a:lstStyle`, and its other two placeholders survive. The
1784
+ * body's hyperlink relationships are deliberately *not* pruned: a caller replacing
1785
+ * text has not asked for its rels to be rewritten, and a now-unreferenced notes
1786
+ * hyperlink rel is inert (nothing resolves it on save).
1787
+ * @param {Element} txBody - the body placeholder's `p:txBody`
1788
+ * @param {string} text - the note, `\n` separating paragraphs
1789
+ */
1790
+ function replaceNotesParagraphs(txBody, text) {
1791
+ const doc = txBody.ownerDocument;
1792
+ if (!doc) throw new InternalError("oxml/node-has-no-document", "Notes body has no owner document");
1793
+ removeChildrenByQName(txBody, ["a:p"]);
1794
+ const wrapper = parseXml(`<w xmlns:a="${A_NS}">${notesParagraphsXml(text)}</w>`).documentElement;
1795
+ if (!wrapper) throw new InternalError("oxml/node-has-no-document", "Authored notes paragraphs did not parse to a root");
1796
+ for (const paragraph of Array.from(wrapper.childNodes)) txBody.appendChild(doc.importNode(paragraph, true));
1797
+ }
1798
+ /**
1799
+ * The `p:txBody` of a notes slide's body placeholder (`p:ph type="body"`), or `null`
1800
+ * when the part carries none. A raw walk rather than a `NotesSlide`/`TextFrame` hop,
1801
+ * because the caller needs the element itself to rewrite and the modeled surface
1802
+ * would resolve a notes theme context it has no use for.
1803
+ * @param {Element | null} root - the notes slide's `p:notes` root
1804
+ * @return {Element | null} the body placeholder's text body
1805
+ */
1806
+ function notesBodyTxBody(root) {
1807
+ const cSld = root && firstChild(root, "p:cSld");
1808
+ const spTree = cSld && firstChild(cSld, "p:spTree");
1809
+ if (!spTree) return null;
1810
+ for (const sp of getElements(spTree, "p:sp")) {
1811
+ const nvSpPr = firstChild(sp, "p:nvSpPr");
1812
+ const nvPr = nvSpPr && firstChild(nvSpPr, "p:nvPr");
1813
+ const ph = nvPr && firstChild(nvPr, "p:ph");
1814
+ if (ph && attr(ph, "type") === "body") return firstChild(sp, "p:txBody");
1815
+ }
1816
+ return null;
1817
+ }
1818
+ /**
1819
+ * Give `slide` the speaker notes `text`, creating its notes slide part when it has
1820
+ * none, and return the notes part's name.
1821
+ *
1822
+ * When the slide already has a notes part, only the body paragraphs are rewritten
1823
+ * (see {@link replaceNotesParagraphs}). When it has none, the part is built and
1824
+ * wired: `slide → notesSlide`, and on the new part the two relationships a notes
1825
+ * slide always carries — `notesMaster` as rId1 and a `slide` back-reference as rId2,
1826
+ * the ids and order the write path reserves.
1827
+ *
1828
+ * The notes master is resolved through {@link ensureNotesMasterForAuthoring}: this
1829
+ * deck's own when it has one, otherwise a fresh one bound to a clone of the deck's
1830
+ * theme. That is the single-notesMaster-per-presentation rule the import and append
1831
+ * paths already follow, so the three cannot produce a second master between them.
1832
+ * @param {Slide} slide - the slide to annotate
1833
+ * @param {string} text - the note, `\n` separating paragraphs
1834
+ * @return {string} the notes slide's partname
1835
+ */
1836
+ function authorNotes(slide, text) {
1837
+ const opc = slide.presentation.opc;
1838
+ const existingRel = slide.relationships.byType(NOTES_SLIDE_REL)[0];
1839
+ if (existingRel) {
1840
+ const partName = slide.relationships.resolveTarget(existingRel.id);
1841
+ const part = opc.part(partName);
1842
+ if (!part) throw new PackageReadError("package/part-missing", `addNotes: slide ${slide.partName} has a notesSlide rel to a missing part ${partName}`);
1843
+ const txBody = notesBodyTxBody(part.dom.documentElement);
1844
+ if (!txBody) throw new PackageReadError("package/part-has-no-root", `addNotes: notes slide ${partName} has no body placeholder to write into`);
1845
+ replaceNotesParagraphs(txBody, text);
1846
+ part.markDirty();
1847
+ return partName;
1848
+ }
1849
+ const slideNum = slide.index + 1;
1850
+ const partName = opc.reservePartNameLike("/ppt/notesSlides/notesSlide1.xml");
1851
+ opc.addPart(partName, NOTES_SLIDE_CONTENT_TYPE$1, textEncoder$2.encode(makeXmlNotesSlideSkeleton(notesParagraphsXml(text), slideNum)));
1852
+ slide.relationships.add(NOTES_SLIDE_REL, relativePartName(slide.partName, partName));
1853
+ const notesRels = opc.relationshipsFor(partName);
1854
+ const masterPartName = ensureNotesMasterForAuthoring(slide.presentation, slide.partName);
1855
+ notesRels.addWithId("rId1", NOTES_MASTER_REL, relativePartName(partName, masterPartName));
1856
+ notesRels.addWithId("rId2", SLIDE_REL, relativePartName(partName, slide.partName));
1857
+ return partName;
1858
+ }
1155
1859
  //#endregion
1156
1860
  //#region src/read/api/comments.ts
1157
1861
  /** The slide → comments-part relationship type (legacy comments). */
@@ -1440,6 +2144,13 @@ function placeholderShapes(spTree) {
1440
2144
  * and text style from. Geometry getters read the placeholder's *own* `a:xfrm`,
1441
2145
  * which a master/layout placeholder carries explicitly (so it round-trips, unlike a
1442
2146
  * notes placeholder's inherited geometry).
2147
+ *
2148
+ * This is the identity-and-geometry view. The same `p:sp` also appears in
2149
+ * {@link SlideMaster.shapes} / {@link SlideLayout.shapes} as an `AutoShape`, which
2150
+ * additionally carries the paint surface (`resolvedFill`, `resolvedLine`,
2151
+ * `presetGeometry`, `rotation`, `absoluteFrame`) and reports its `p:ph` through
2152
+ * `AutoShape.placeholder`. Read a placeholder here to *place* it; read it there to
2153
+ * *draw* it.
1443
2154
  */
1444
2155
  var Placeholder = class {
1445
2156
  sp;
@@ -1513,9 +2224,9 @@ var Placeholder = class {
1513
2224
  /**
1514
2225
  * A deck's slide master (`slideMasterN.xml`) as a modeled object. Exposes the
1515
2226
  * property tiers a bound slide inherits: the {@link colorMap} (token → theme slot),
1516
- * its {@link theme}, its {@link placeholders} (with geometry), its own
1517
- * {@link background}, and the {@link layouts} that build on it. Reachable from
1518
- * {@link SlideLayout.master} and `Slide.master`.
2227
+ * its {@link theme}, its {@link shapes} and the {@link placeholders} subset of them,
2228
+ * its own {@link background}, and the {@link layouts} that build on it. Reachable
2229
+ * from {@link SlideLayout.master} and `Slide.master`.
1519
2230
  */
1520
2231
  var SlideMaster = class {
1521
2232
  opc;
@@ -1529,6 +2240,10 @@ var SlideMaster = class {
1529
2240
  get partName() {
1530
2241
  return this.part.partName;
1531
2242
  }
2243
+ /** This master part's relationships (its layouts, its theme, image embeds, …). */
2244
+ get relationships() {
2245
+ return this.opc.relationshipsFor(this.partName);
2246
+ }
1532
2247
  /** The master's authoring name (`p:cSld/@name`), or `''` when unnamed (the writer's default). */
1533
2248
  get name() {
1534
2249
  const cSld = this.#cSld();
@@ -1552,10 +2267,27 @@ var SlideMaster = class {
1552
2267
  for (const token of COLOR_MAP_TOKENS) out[token] = parsed.get(token) ?? null;
1553
2268
  return out;
1554
2269
  }
2270
+ /**
2271
+ * Every shape in the master's `p:cSld/p:spTree`, in document order — the same
2272
+ * `AnyShape` union `Slide.shapes` returns, so shape-walking code applies
2273
+ * unchanged. This is where a template's non-placeholder furniture lives: the
2274
+ * header band, the rule under the title, the logo. Whether a given slide draws
2275
+ * them is `Slide.showMasterSp` (and, for a layout, {@link SlideLayout.showMasterSp}).
2276
+ *
2277
+ * {@link placeholders} is the filtered view of the same tree.
2278
+ */
2279
+ get shapes() {
2280
+ const spTree = this.#spTree();
2281
+ return spTree ? buildShapes(spTree, this) : [];
2282
+ }
2283
+ /** The shape anywhere in the master's tree with the given drawing id, or `undefined`. */
2284
+ shapeByIdDeep(id) {
2285
+ return findShapeByIdDeep(this.shapes, id);
2286
+ }
1555
2287
  /** The master's placeholder shapes (`p:sp` carrying a `p:ph`), in document order. */
1556
2288
  get placeholders() {
1557
2289
  const ctx = this.themeContext();
1558
- const rels = this.opc.relationshipsFor(this.partName);
2290
+ const rels = this.relationships;
1559
2291
  return placeholderShapes(this.#spTree()).map((sp) => new Placeholder(sp, this.part, ctx, rels));
1560
2292
  }
1561
2293
  /** The layouts built on this master (via `p:sldLayoutIdLst` → its relationships), in list order. */
@@ -1563,7 +2295,7 @@ var SlideMaster = class {
1563
2295
  const root = this.part.dom.documentElement;
1564
2296
  const lst = root && firstChild(root, "p:sldLayoutIdLst");
1565
2297
  if (!lst) return [];
1566
- const rels = this.opc.relationshipsFor(this.partName);
2298
+ const rels = this.relationships;
1567
2299
  const out = [];
1568
2300
  for (const entry of getElements(lst, "p:sldLayoutId")) {
1569
2301
  const relId = attr(entry, "r:id");
@@ -1581,7 +2313,7 @@ var SlideMaster = class {
1581
2313
  const bg = backgroundElementOf(this.part.dom.documentElement);
1582
2314
  if (!bg) return null;
1583
2315
  const themePart = this.#relTarget(THEME_REL);
1584
- return readSlideBackground(bg, "master", this.themeContext(), this.opc.relationshipsFor(this.partName), themePart ? this.opc.relationshipsFor(themePart.partName) : null);
2316
+ return readSlideBackground(bg, "master", this.themeContext(), this.relationships, themePart ? this.opc.relationshipsFor(themePart.partName) : null);
1585
2317
  }
1586
2318
  /**
1587
2319
  * The master's colour/font context (its `p:clrMap` + theme), resolved once and
@@ -1591,7 +2323,7 @@ var SlideMaster = class {
1591
2323
  return this.#themeContext ??= resolveMasterColorContext(this.opc, this.partName);
1592
2324
  }
1593
2325
  #relTarget(type) {
1594
- const rels = this.opc.relationshipsFor(this.partName);
2326
+ const rels = this.relationships;
1595
2327
  const rel = rels.byType(type)[0];
1596
2328
  return rel ? this.opc.part(rels.resolveTarget(rel.id)) ?? null : null;
1597
2329
  }
@@ -1608,8 +2340,8 @@ var SlideMaster = class {
1608
2340
  * A deck's slide layout (`slideLayoutN.xml`) as a modeled object — the gallery entry
1609
2341
  * a slide binds to. Exposes its {@link name}, its {@link type} (import-only; the
1610
2342
  * writer authors none), its {@link master} (and, through it, the {@link theme}), its
1611
- * {@link placeholders} (with geometry), and its own {@link background}. Reachable
1612
- * from `Slide.layout` and {@link SlideMaster.layouts}.
2343
+ * {@link shapes} and the {@link placeholders} subset of them, and its own
2344
+ * {@link background}. Reachable from `Slide.layout` and {@link SlideMaster.layouts}.
1613
2345
  */
1614
2346
  var SlideLayout = class {
1615
2347
  opc;
@@ -1623,6 +2355,10 @@ var SlideLayout = class {
1623
2355
  get partName() {
1624
2356
  return this.part.partName;
1625
2357
  }
2358
+ /** This layout part's relationships (its master, image embeds, hyperlinks, …). */
2359
+ get relationships() {
2360
+ return this.opc.relationshipsFor(this.partName);
2361
+ }
1626
2362
  /** The layout's authoring name (`p:cSld/@name`, e.g. `Title and Content`), or `''` when unnamed. */
1627
2363
  get name() {
1628
2364
  const cSld = this.#cSld();
@@ -1637,9 +2373,21 @@ var SlideLayout = class {
1637
2373
  const root = this.part.dom.documentElement;
1638
2374
  return root ? attr(root, "type") : null;
1639
2375
  }
2376
+ /**
2377
+ * Whether this layout draws the master's non-placeholder shapes
2378
+ * (`p:sldLayout/@showMasterSp`). `xsd:boolean` defaulting to `true`, so an absent
2379
+ * attribute means shown — the layout-tier counterpart of `Slide.showMasterSp`,
2380
+ * and the one PowerPoint actually writes on a section-divider or full-bleed
2381
+ * layout. A renderer resolving what to paint under a slide's own shapes has to
2382
+ * consult both: the slide's flag, then its layout's.
2383
+ */
2384
+ get showMasterSp() {
2385
+ const root = this.part.dom.documentElement;
2386
+ return boolValue(root && attr(root, "showMasterSp")) !== false;
2387
+ }
1640
2388
  /** The master this layout is built on (via its `slideMaster` relationship), or `null` when absent. */
1641
2389
  get master() {
1642
- const rels = this.opc.relationshipsFor(this.partName);
2390
+ const rels = this.relationships;
1643
2391
  const rel = rels.byType(SLIDE_MASTER_REL)[0];
1644
2392
  const part = rel ? this.opc.part(rels.resolveTarget(rel.id)) : null;
1645
2393
  return part ? new SlideMaster(this.opc, part) : null;
@@ -1648,10 +2396,27 @@ var SlideLayout = class {
1648
2396
  get theme() {
1649
2397
  return this.master?.theme ?? null;
1650
2398
  }
2399
+ /**
2400
+ * Every shape in the layout's `p:cSld/p:spTree`, in document order — the same
2401
+ * `AnyShape` union `Slide.shapes` returns, so shape-walking code applies
2402
+ * unchanged. The write API authors non-placeholder shapes here: every
2403
+ * `defineSlideMaster({ objects })` member that is not a `placeholder` (a `rect`,
2404
+ * a `line`, an `image`, a `chart`, a `text` box) lands in this tree.
2405
+ *
2406
+ * {@link placeholders} is the filtered view of the same tree.
2407
+ */
2408
+ get shapes() {
2409
+ const spTree = this.#spTree();
2410
+ return spTree ? buildShapes(spTree, this) : [];
2411
+ }
2412
+ /** The shape anywhere in the layout's tree with the given drawing id, or `undefined`. */
2413
+ shapeByIdDeep(id) {
2414
+ return findShapeByIdDeep(this.shapes, id);
2415
+ }
1651
2416
  /** The layout's placeholder shapes (`p:sp` carrying a `p:ph`), in document order. */
1652
2417
  get placeholders() {
1653
2418
  const ctx = this.themeContext();
1654
- const rels = this.opc.relationshipsFor(this.partName);
2419
+ const rels = this.relationships;
1655
2420
  return placeholderShapes(this.#spTree()).map((sp) => new Placeholder(sp, this.part, ctx, rels));
1656
2421
  }
1657
2422
  /**
@@ -1664,7 +2429,7 @@ var SlideLayout = class {
1664
2429
  const bg = backgroundElementOf(this.part.dom.documentElement);
1665
2430
  if (!bg) return null;
1666
2431
  const themePart = this.master?.theme?.part ?? null;
1667
- return readSlideBackground(bg, "layout", this.themeContext(), this.opc.relationshipsFor(this.partName), themePart ? this.opc.relationshipsFor(themePart.partName) : null);
2432
+ return readSlideBackground(bg, "layout", this.themeContext(), this.relationships, themePart ? this.opc.relationshipsFor(themePart.partName) : null);
1668
2433
  }
1669
2434
  /**
1670
2435
  * The layout's colour/font context (walked layout → master → theme), resolved once
@@ -2178,6 +2943,14 @@ var Slide = class {
2178
2943
  get relationships() {
2179
2944
  return this.presentation.opc.relationshipsFor(this.partName);
2180
2945
  }
2946
+ /**
2947
+ * The deck's OPC package. Convenience for `presentation.opc`, and the member
2948
+ * {@link ShapeHost} names so a shape can reach a referenced part (an image, a
2949
+ * chart) the same way whether it lives on a slide, a layout, or a master.
2950
+ */
2951
+ get opc() {
2952
+ return this.presentation.opc;
2953
+ }
2181
2954
  #themeColors;
2182
2955
  /**
2183
2956
  * The slide's resolved theme context (`clrMap` + `clrScheme` + the theme
@@ -2223,6 +2996,22 @@ var Slide = class {
2223
2996
  this.part.markDirty();
2224
2997
  }
2225
2998
  /**
2999
+ * Whether this slide draws the master's non-placeholder shapes
3000
+ * (`p:sld/@showMasterSp`). `xsd:boolean` defaulting to `true`, so an absent
3001
+ * attribute means shown — the same shape as {@link hidden}.
3002
+ *
3003
+ * This is the switch that decides whether {@link SlideMaster.shapes} belongs on
3004
+ * *this* slide. A slide the author gave a full-bleed image or a section divider
3005
+ * usually sets it to `0`, and a renderer that paints the master's band and logo
3006
+ * anyway puts the template's furniture on top of a slide that deliberately hid
3007
+ * it. Placeholders are unaffected: the flag suppresses only the master's
3008
+ * decorative shapes.
3009
+ */
3010
+ get showMasterSp() {
3011
+ const root = this.part.dom.documentElement;
3012
+ return boolValue(root && attr(root, "showMasterSp")) !== false;
3013
+ }
3014
+ /**
2226
3015
  * The slide's show transition (`p:transition`), decoded into a typed model, or
2227
3016
  * `null` when the slide has none. Handles both PowerPoint forms: the bare
2228
3017
  * `<p:transition>` and the `mc:AlternateContent` wrapper that carries the exact
@@ -2530,16 +3319,7 @@ var Slide = class {
2530
3319
  * resolve a binding into a group that top-level {@link shapeById} cannot see.
2531
3320
  */
2532
3321
  shapeByIdDeep(id) {
2533
- const walk = (shapes) => {
2534
- for (const shape of shapes) {
2535
- if (shape.id === id) return shape;
2536
- if (shape instanceof GroupShape) {
2537
- const found = walk(shape.shapes);
2538
- if (found) return found;
2539
- }
2540
- }
2541
- };
2542
- return walk(this.shapes);
3322
+ return findShapeByIdDeep(this.shapes, id);
2543
3323
  }
2544
3324
  /** The first top-level shape with the given name (`p:cNvPr/@name`), or `undefined`. */
2545
3325
  shapeByName(name) {
@@ -2624,6 +3404,34 @@ var Slide = class {
2624
3404
  return new Picture(pic, this);
2625
3405
  }
2626
3406
  /**
3407
+ * Give this slide the speaker notes `text`, and return the resulting
3408
+ * {@link NotesSlide}. A `
3409
+ ` starts a new paragraph, matching the write-side
3410
+ * `addNotes`; the runs carry no formatting of their own, so style them
3411
+ * afterwards through {@link notesTextFrame}.
3412
+ *
3413
+ * The counterpart to the read side's `notesText`/`notesTextFrame`/`notesSlide`
3414
+ * getters, and the way to annotate a slide that has **no notes part at all** —
3415
+ * the state an `importSlide` without `{ importNotes: true }` leaves behind, and
3416
+ * the one a `notesTextFrame` edit cannot reach because there is no frame to
3417
+ * hand back. Calling it on a slide that already has notes replaces the body
3418
+ * text and leaves the rest of the part (its geometry, its other two
3419
+ * placeholders) alone.
3420
+ *
3421
+ * Creating the part pulls in what a notes slide must bind to: a `notesMaster`,
3422
+ * of which a presentation may hold at most one. This deck's own is reused when
3423
+ * it has one; otherwise one is installed, bound to a clone of this deck's theme
3424
+ * so it resolves against the destination palette. That is the same
3425
+ * single-master rule `importSlide({ importNotes: true })` and `appendSlides`
3426
+ * follow, so mixing the three cannot produce a second notes master.
3427
+ */
3428
+ addNotes(text) {
3429
+ const partName = authorNotes(this, text);
3430
+ const part = this.presentation.opc.part(partName);
3431
+ if (!part) throw new InternalError("import/part-went-missing", `Authored notes part went missing: ${partName}`);
3432
+ return new NotesSlide(this.presentation.opc, part);
3433
+ }
3434
+ /**
2627
3435
  * @internal The slide's shape tree (`p:cSld/p:spTree`), or `null` if absent.
2628
3436
  * Exposed for cross-slide composition (`Presentation.importShape`).
2629
3437
  */
@@ -3005,7 +3813,7 @@ function rescaleTableGrid(graphicFrame, t) {
3005
3813
  const TABLE_STYLES_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles";
3006
3814
  const TABLE_STYLES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
3007
3815
  const TABLE_STYLES_DEFAULT_GUID = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
3008
- const textEncoder$2 = new TextEncoder();
3816
+ const textEncoder$1 = new TextEncoder();
3009
3817
  /**
3010
3818
  * Copy any table style the restyled slide references from the source
3011
3819
  * `tableStyles.xml` into this deck's (the `remapLiterals` table leg). A restyled
@@ -3046,260 +3854,64 @@ function copySourceTableStyles(dest, sourceOpc, slideRoot) {
3046
3854
  * anything was added.
3047
3855
  */
3048
3856
  function addTableStyles(destList, styles) {
3049
- const present = new Set(getElements(destList, "a:tblStyle").map((st) => attr(st, "styleId")));
3050
- let added = false;
3051
- for (const style of styles) {
3052
- const id = attr(style, "styleId");
3053
- if (!id || present.has(id)) continue;
3054
- destList.appendChild(ownerDocumentOf(destList).importNode(style, true));
3055
- present.add(id);
3056
- added = true;
3057
- }
3058
- return added;
3059
- }
3060
- /**
3061
- * Copy the source deck's **whole** `tableStyles.xml` into this deck: every
3062
- * `<a:tblStyle>` it defines, plus its `a:tblStyleLst@def` (the default table
3063
- * style). Presentation-level, so a separate traversal from the master/layout copy
3064
- * chain — and whole-deck, since `tableStyles.xml` does not record which style
3065
- * belongs to which master. See `ImportSlideMastersOptions.tableStyles`.
3066
- *
3067
- * Styles union by `styleId` (destination-wins per id, via {@link addTableStyles}),
3068
- * but `def` is **source-wins**: a caller asking for the source's table styling
3069
- * wants its default too, and the destination's `def` is typically a generator stub
3070
- * rather than a deliberate choice. Carrying the styles without the `def` would be
3071
- * the worse half-measure — the standard default GUID is one most templates also
3072
- * define, so a new table would silently resolve to the wrong style rather than
3073
- * visibly to none. Idempotent: a re-call re-adds nothing and re-sets `def` to the
3074
- * same value.
3075
- */
3076
- function carryTableStyles(dest, sourceOpc) {
3077
- const sourceList = tableStyleList(sourceOpc);
3078
- if (!sourceList) return;
3079
- const destPart = ensureTableStylesPart(dest);
3080
- const destList = destPart.dom.documentElement;
3081
- if (!destList) return;
3082
- let changed = addTableStyles(destList, getElements(sourceList, "a:tblStyle"));
3083
- const sourceDef = attr(sourceList, "def");
3084
- if (sourceDef && attr(destList, "def") !== sourceDef) {
3085
- destList.setAttribute("def", sourceDef);
3086
- changed = true;
3087
- }
3088
- if (changed) destPart.markDirty();
3089
- }
3090
- /** The `a:tblStyleLst` root of a package's `tableStyles.xml`, or `null` when it has none. */
3091
- function tableStyleList(opc) {
3092
- const part = opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3093
- return part ? part.dom.documentElement : null;
3094
- }
3095
- /**
3096
- * The deck's `tableStyles.xml` part, creating an empty one (and wiring its
3097
- * `presentation.xml` relationship + content type) when the deck has none.
3098
- */
3099
- function ensureTableStylesPart(dest) {
3100
- const existing = dest.opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3101
- if (existing) return existing;
3102
- const partName = dest.opc.reservePartNameLike("/ppt/tableStyles.xml");
3103
- const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n<a:tblStyleLst xmlns:a="${OOXML_NS.a}" def="${TABLE_STYLES_DEFAULT_GUID}"/>`;
3104
- const part = dest.opc.addPart(partName, TABLE_STYLES_CONTENT_TYPE, textEncoder$2.encode(xml));
3105
- dest.opc.relationshipsFor(dest.presentationPart.partName).add(TABLE_STYLES_REL, relativePartName(dest.presentationPart.partName, partName));
3106
- return part;
3107
- }
3108
- //#endregion
3109
- //#region src/read/api/ops/master-registry.ts
3110
- /**
3111
- * Slide-master bookkeeping in `presentation.xml` and in a master's own
3112
- * `p:sldLayoutIdLst`: registering a master so renderers treat it as active,
3113
- * promoting masters to the front of the id list, allocating ids out of the shared
3114
- * master/layout id space, and emptying a freshly-copied master's layout list.
3115
- *
3116
- * These are the destination-side half of the import machinery -- they read and
3117
- * write only this deck, never a source package and never the copy registry -- so
3118
- * they take a structural {@link DeckTarget} rather than a `Presentation`. The
3119
- * source-side half that drives most of them is `part-copy.ts`.
3120
- */
3121
- /**
3122
- * Wire a freshly-copied slide master into `presentation.xml`: add a
3123
- * presentation→master relationship and a `p:sldMasterId` entry in
3124
- * `p:sldMasterIdLst`. A master that is reachable only through the
3125
- * slide→layout→master rel chain but absent from `p:sldMasterIdLst` is treated
3126
- * as inactive by renderers, so its background/graphics never paint. No-op when
3127
- * the master is already registered (shared across imports from one source).
3128
- */
3129
- function registerMaster(dest, masterPartName) {
3130
- const presPart = dest.presentationPart;
3131
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3132
- for (const rel of presRels.byType(SLIDE_MASTER_REL)) if (presRels.resolveTarget(rel.id) === masterPartName) return;
3133
- const relId = presRels.add(SLIDE_MASTER_REL, relativePartName(presPart.partName, masterPartName)).id;
3134
- const root = presPart.dom.documentElement;
3135
- if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a master in");
3136
- const lst = getOrAddChild(root, "p:sldMasterIdLst", [
3137
- "p:notesMasterIdLst",
3138
- "p:handoutMasterIdLst",
3139
- "p:sldIdLst",
3140
- "p:sldSz",
3141
- "p:notesSz",
3142
- "p:embeddedFontLst",
3143
- "p:custShowLst",
3144
- "p:photoAlbum",
3145
- "p:custDataLst",
3146
- "p:kinsoku",
3147
- "p:defaultTextStyle",
3148
- "p:modifyVerifier",
3149
- "p:extLst"
3150
- ]);
3151
- const entry = createElement(presPart.dom, "p:sldMasterId");
3152
- setAttr(entry, "id", String(nextMasterLayoutId(dest)));
3153
- setAttr(entry, "r:id", relId);
3154
- lst.appendChild(entry);
3155
- presPart.markDirty();
3156
- }
3157
- /**
3158
- * Move the `p:sldMasterId` entries for `masterPartNames` to the front of
3159
- * `p:sldMasterIdLst`, preserving their existing relative order, so the first of
3160
- * them becomes the deck's `Designs(1)` — the theme PowerPoint's Design tab shows.
3161
- * Reorders the id list only; relationships, ids, and all other parts are left as
3162
- * they are. A no-op when the named masters already lead (idempotent re-call).
3163
- */
3164
- function promoteMasters(dest, masterPartNames) {
3165
- if (masterPartNames.length === 0) return;
3166
- const presPart = dest.presentationPart;
3167
- const root = presPart.dom.documentElement;
3168
- const lst = root && firstChild(root, "p:sldMasterIdLst");
3169
- if (!lst) return;
3170
- const rels = dest.opc.relationshipsFor(presPart.partName);
3171
- const promote = new Set(masterPartNames);
3172
- const entries = getElements(lst, "p:sldMasterId");
3173
- const isPromoted = (entry) => {
3174
- const relId = attr(entry, "r:id");
3175
- return relId ? promote.has(rels.resolveTarget(relId)) : false;
3176
- };
3177
- const desired = [...entries.filter(isPromoted), ...entries.filter((e) => !isPromoted(e))];
3178
- if (desired.every((entry, i) => entry === entries[i])) return;
3179
- for (const entry of desired) lst.appendChild(entry);
3180
- presPart.markDirty();
3181
- }
3182
- /**
3183
- * The next free id in the presentation-wide slide-master / slide-layout id space.
3184
- *
3185
- * `p:sldMasterId/@id` and every master's `p:sldLayoutId/@id` draw from ONE shared
3186
- * id space (both ST_SlideMasterId/ST_SlideLayoutId, floor 2147483648) and must be
3187
- * unique across the WHOLE presentation, not just within their own list. A duplicate
3188
- * anywhere in this space makes PowerPoint report the file as corrupt on open
3189
- * ("found a problem with content" / 0x80070570), even though LibreOffice silently
3190
- * tolerates it — so an imported master AND each of its layouts must take an id past
3191
- * the max of both the master-id list and every layout-id list. Recomputed per
3192
- * allocation so ids appended earlier in the same import are counted.
3193
- */
3194
- function nextMasterLayoutId(dest) {
3195
- let max = 2147483647;
3196
- const presPart = dest.presentationPart;
3197
- const root = presPart.dom.documentElement;
3198
- const masterLst = root && firstChild(root, "p:sldMasterIdLst");
3199
- if (masterLst) for (const entry of getElements(masterLst, "p:sldMasterId")) {
3200
- const id = intValue(attr(entry, "id"));
3201
- if (id !== null && id > max) max = id;
3202
- }
3203
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3204
- for (const rel of presRels.byType(SLIDE_MASTER_REL)) {
3205
- const masterRoot = dest.opc.part(presRels.resolveTarget(rel.id))?.dom.documentElement;
3206
- const layoutLst = masterRoot && firstChild(masterRoot, "p:sldLayoutIdLst");
3207
- if (!layoutLst) continue;
3208
- for (const entry of getElements(layoutLst, "p:sldLayoutId")) {
3209
- const id = intValue(attr(entry, "id"));
3210
- if (id !== null && id > max) max = id;
3211
- }
3212
- }
3213
- return max + 1;
3214
- }
3215
- /** Empty a freshly-copied master's `p:sldLayoutIdLst`; copied layouts repopulate it. */
3216
- function clearLayoutIdList(dest, masterPartName) {
3217
- const masterPart = dest.opc.part(masterPartName);
3218
- const root = masterPart?.dom.documentElement;
3219
- const lst = root && firstChild(root, "p:sldLayoutIdLst");
3220
- if (!masterPart || !lst) return;
3221
- removeChildrenByQName(lst, ["p:sldLayoutId"]);
3222
- masterPart.markDirty();
3223
- }
3224
- /**
3225
- * Append a `p:sldLayoutId` entry for `layoutPartName` to `masterPartName`'s
3226
- * `p:sldLayoutIdLst`, adding the master→layout relationship it points at. The
3227
- * destination-side half of linking a copied layout into its copied master; the
3228
- * caller resolves which master that is from the source-side rels.
3229
- */
3230
- function addLayoutToMaster(dest, masterPartName, layoutPartName) {
3231
- const masterPart = dest.opc.part(masterPartName);
3232
- const root = masterPart?.dom.documentElement;
3233
- if (!masterPart || !root) return;
3234
- const relId = dest.opc.relationshipsFor(masterPartName).add(SLIDE_LAYOUT_REL, relativePartName(masterPartName, layoutPartName)).id;
3235
- const lst = getOrAddChild(root, "p:sldLayoutIdLst", [
3236
- "p:transition",
3237
- "p:timing",
3238
- "p:hf",
3239
- "p:txStyles",
3240
- "p:extLst"
3241
- ]);
3242
- const entry = createElement(masterPart.dom, "p:sldLayoutId");
3243
- setAttr(entry, "id", String(nextMasterLayoutId(dest)));
3244
- setAttr(entry, "r:id", relId);
3245
- lst.appendChild(entry);
3246
- masterPart.markDirty();
3857
+ const present = new Set(getElements(destList, "a:tblStyle").map((st) => attr(st, "styleId")));
3858
+ let added = false;
3859
+ for (const style of styles) {
3860
+ const id = attr(style, "styleId");
3861
+ if (!id || present.has(id)) continue;
3862
+ destList.appendChild(ownerDocumentOf(destList).importNode(style, true));
3863
+ present.add(id);
3864
+ added = true;
3865
+ }
3866
+ return added;
3247
3867
  }
3248
- //#endregion
3249
- //#region src/read/api/ops/part-copy.ts
3250
- const SLIDE_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
3251
- const SLIDE_LAYOUT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
3252
3868
  /**
3253
- * Copy `sourcePartName` (and, recursively, every internal part it references)
3254
- * from `ctx.source` into `ctx.dest`, returning the new partname. Idempotent
3255
- * per source package via the copy registry. Relationship ids are preserved so
3256
- * the copied part body's `r:id`/`r:embed` references stay valid; targets are
3257
- * rewritten to the freshly-allocated partnames. Notes relationships are
3258
- * dropped. A copied `slideMaster` does not drag in all its sibling layouts —
3259
- * each imported `slideLayout` wires itself into the master instead (see
3260
- * {@link linkLayoutIntoMaster}).
3869
+ * Copy the source deck's **whole** `tableStyles.xml` into this deck: every
3870
+ * `<a:tblStyle>` it defines, plus its `a:tblStyleLst@def` (the default table
3871
+ * style). Presentation-level, so a separate traversal from the master/layout copy
3872
+ * chain — and whole-deck, since `tableStyles.xml` does not record which style
3873
+ * belongs to which master. See `ImportSlideMastersOptions.tableStyles`.
3874
+ *
3875
+ * Styles union by `styleId` (destination-wins per id, via {@link addTableStyles}),
3876
+ * but `def` is **source-wins**: a caller asking for the source's table styling
3877
+ * wants its default too, and the destination's `def` is typically a generator stub
3878
+ * rather than a deliberate choice. Carrying the styles without the `def` would be
3879
+ * the worse half-measure — the standard default GUID is one most templates also
3880
+ * define, so a new table would silently resolve to the wrong style rather than
3881
+ * visibly to none. Idempotent: a re-call re-adds nothing and re-sets `def` to the
3882
+ * same value.
3261
3883
  */
3262
- function copyPart(ctx, sourcePartName) {
3263
- const existing = ctx.registry.get(sourcePartName);
3264
- if (existing) return existing;
3265
- const sourcePart = ctx.source.part(sourcePartName);
3266
- if (!sourcePart) throw new PackageReadError("package/part-missing", `importSlide: source package has no part ${sourcePartName}`);
3267
- const newPartName = ctx.dest.opc.reservePartNameLike(sourcePartName);
3268
- ctx.dest.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
3269
- ctx.registry.set(sourcePartName, newPartName);
3270
- const isMaster = sourcePart.contentType === SLIDE_MASTER_CONTENT_TYPE;
3271
- const sourceRels = ctx.source.relationshipsFor(sourcePartName);
3272
- const targetRels = ctx.dest.opc.relationshipsFor(newPartName);
3273
- for (const rel of sourceRels) {
3274
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
3275
- if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
3276
- if (rel.targetMode === "External") {
3277
- targetRels.addWithId(rel.id, rel.type, rel.target, "External");
3278
- continue;
3279
- }
3280
- const newTargetPartName = copyPart(ctx, sourceRels.resolveTarget(rel.id));
3281
- targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTargetPartName));
3282
- }
3283
- if (isMaster) {
3284
- clearLayoutIdList(ctx.dest, newPartName);
3285
- registerMaster(ctx.dest, newPartName);
3884
+ function carryTableStyles(dest, sourceOpc) {
3885
+ const sourceList = tableStyleList(sourceOpc);
3886
+ if (!sourceList) return;
3887
+ const destPart = ensureTableStylesPart(dest);
3888
+ const destList = destPart.dom.documentElement;
3889
+ if (!destList) return;
3890
+ let changed = addTableStyles(destList, getElements(sourceList, "a:tblStyle"));
3891
+ const sourceDef = attr(sourceList, "def");
3892
+ if (sourceDef && attr(destList, "def") !== sourceDef) {
3893
+ destList.setAttribute("def", sourceDef);
3894
+ changed = true;
3286
3895
  }
3287
- if (sourcePart.contentType === SLIDE_LAYOUT_CONTENT_TYPE) linkLayoutIntoMaster(ctx, sourceRels, newPartName);
3288
- return newPartName;
3896
+ if (changed) destPart.markDirty();
3897
+ }
3898
+ /** The `a:tblStyleLst` root of a package's `tableStyles.xml`, or `null` when it has none. */
3899
+ function tableStyleList(opc) {
3900
+ const part = opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3901
+ return part ? part.dom.documentElement : null;
3289
3902
  }
3290
3903
  /**
3291
- * Wire a just-copied layout into its (already-copied) master. Resolves which
3292
- * destination master that is by running the layout's *source* master rel through
3293
- * the copy registry, then hands the destination-side wiring to
3294
- * {@link addLayoutToMaster}. Called once per copied layout, so the master
3295
- * accumulates exactly the imported layouts.
3904
+ * The deck's `tableStyles.xml` part, creating an empty one (and wiring its
3905
+ * `presentation.xml` relationship + content type) when the deck has none.
3296
3906
  */
3297
- function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
3298
- const masterRel = layoutSourceRels.byType(SLIDE_MASTER_REL)[0];
3299
- if (!masterRel) return;
3300
- const masterPartName = ctx.registry.get(layoutSourceRels.resolveTarget(masterRel.id));
3301
- if (!masterPartName) return;
3302
- addLayoutToMaster(ctx.dest, masterPartName, layoutPartName);
3907
+ function ensureTableStylesPart(dest) {
3908
+ const existing = dest.opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3909
+ if (existing) return existing;
3910
+ const partName = dest.opc.reservePartNameLike("/ppt/tableStyles.xml");
3911
+ const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n<a:tblStyleLst xmlns:a="${OOXML_NS.a}" def="${TABLE_STYLES_DEFAULT_GUID}"/>`;
3912
+ const part = dest.opc.addPart(partName, TABLE_STYLES_CONTENT_TYPE, textEncoder$1.encode(xml));
3913
+ dest.opc.relationshipsFor(dest.presentationPart.partName).add(TABLE_STYLES_REL, relativePartName(dest.presentationPart.partName, partName));
3914
+ return part;
3303
3915
  }
3304
3916
  //#endregion
3305
3917
  //#region src/read/api/ops/carried-rels.ts
@@ -3325,8 +3937,9 @@ function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
3325
3937
  * @param {string} destPartName - partname of the destination part the subtree is being placed in
3326
3938
  * @param {Relationships} destRels - relationships of that destination part
3327
3939
  * @param {Map<string, string>} relIdMap - per-call cache (source part + source id → new id) deduping references shared within one import
3940
+ * @param {OwnedScope} [owned] - ownership scope of the thing being carried, so the chart or diagram under a carried shape is copied for it rather than shared with an earlier copy
3328
3941
  */
3329
- function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap) {
3942
+ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap, owned) {
3330
3943
  const elements = [];
3331
3944
  collectElements(node, elements);
3332
3945
  for (const el of elements) {
@@ -3341,7 +3954,7 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
3341
3954
  id: a.value
3342
3955
  });
3343
3956
  }
3344
- for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap));
3957
+ for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned));
3345
3958
  }
3346
3959
  }
3347
3960
  /**
@@ -3352,16 +3965,20 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
3352
3965
  * @param {string} destPartName - partname of the destination part
3353
3966
  * @param {Relationships} destRels - relationships of that destination part
3354
3967
  * @param {Map<string, string>} relIdMap - per-call dedupe cache
3968
+ * @param {OwnedScope} [owned] - ownership scope of the carried content, for the parts it owns
3355
3969
  * @return {string} the new relationship id on the destination part
3356
3970
  */
3357
- function carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap) {
3358
- const key = `${sourceRels.sourcePartName}|${id}`;
3359
- const cached = relIdMap.get(key);
3360
- if (cached) return cached;
3971
+ function carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned) {
3361
3972
  const rel = sourceRels.get(id);
3362
3973
  if (!rel) throw new InvalidOptionError("relationship/not-found", `Relationships of ${sourceRels.sourcePartName}: no relationship with id ${id}`);
3363
- const newId = rel.targetMode === "External" ? destRels.add(rel.type, rel.target, "External").id : destRels.add(rel.type, relativePartName(destPartName, copyPart(ctx, sourceRels.resolveTarget(id)))).id;
3364
- relIdMap.set(key, newId);
3974
+ const ownsIt = owned !== void 0 && rel.targetMode !== "External" && !isSharedByPageCopies(rel.type);
3975
+ const key = `${sourceRels.sourcePartName}|${id}`;
3976
+ if (!ownsIt) {
3977
+ const cached = relIdMap.get(key);
3978
+ if (cached) return cached;
3979
+ }
3980
+ const newId = rel.targetMode === "External" ? destRels.add(rel.type, rel.target, "External").id : destRels.add(rel.type, relativePartName(destPartName, copyPart(ctx, sourceRels.resolveTarget(id), ownsIt ? owned : void 0))).id;
3981
+ if (!ownsIt) relIdMap.set(key, newId);
3365
3982
  return newId;
3366
3983
  }
3367
3984
  //#endregion
@@ -3743,6 +4360,7 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3743
4360
  if (!slideRoot) throw new PackageReadError("package/part-has-no-root", `Imported slide ${newPartName} has no root element`);
3744
4361
  const sourceRels = source.opc.relationshipsFor(sourceSlide.partName);
3745
4362
  const targetRels = dest.opc.relationshipsFor(newPartName);
4363
+ const owned = newOwnedScope();
3746
4364
  for (const rel of sourceRels) {
3747
4365
  if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
3748
4366
  if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") {
@@ -3753,10 +4371,10 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3753
4371
  targetRels.addWithId(rel.id, rel.type, rel.target, "External");
3754
4372
  continue;
3755
4373
  }
3756
- const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id));
4374
+ const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
3757
4375
  targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTarget));
3758
4376
  }
3759
- if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName);
4377
+ if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName, owned);
3760
4378
  return {
3761
4379
  newPartName,
3762
4380
  slideRoot,
@@ -3777,8 +4395,9 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3777
4395
  * @param {Element} slideRoot - root element of the new slide part (mutated in place)
3778
4396
  * @param {string} newPartName - partname of the new slide part
3779
4397
  * @param {string} slidePartName - partname of the source slide, for resolving its layout/master
4398
+ * @param {OwnedScope} owned - the page's ownership scope, so a decoration's own parts land in it
3780
4399
  */
3781
- function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
4400
+ function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName, owned) {
3782
4401
  const sourceOpc = ctx.source;
3783
4402
  const layoutPartName = resolveSingleRel(sourceOpc, slidePartName, SLIDE_LAYOUT_REL);
3784
4403
  const masterPartName = layoutPartName ? resolveSingleRel(sourceOpc, layoutPartName, SLIDE_MASTER_REL) : null;
@@ -3796,7 +4415,7 @@ function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
3796
4415
  const sourceRels = sourceOpc.relationshipsFor(partName);
3797
4416
  for (const deco of decorations) {
3798
4417
  const imported = doc.importNode(deco, true);
3799
- rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap);
4418
+ rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap, owned);
3800
4419
  spTree.insertBefore(imported, anchor);
3801
4420
  }
3802
4421
  }
@@ -3819,138 +4438,6 @@ function destinationLayoutPartName(dest) {
3819
4438
  return masterRels.resolveTarget(layoutRel.id);
3820
4439
  }
3821
4440
  //#endregion
3822
- //#region src/read/api/ops/notes-master.ts
3823
- /**
3824
- * Carrying speaker notes and the deck's single notes master across an import or an append.
3825
- *
3826
- * A `notesSlide` is not self-contained: it back-references the slide it annotates and forward-
3827
- * references the deck's notes master. `CT_NotesMasterIdList` allows at most one master per
3828
- * presentation, so a deck that already has one keeps it — the destination's notes styling wins —
3829
- * and a source master is copied only into a deck that has none. That single rule is why both the
3830
- * `Presentation`-to-`Presentation` path and the generator-XML path funnel through
3831
- * `registerNotesMaster`: they differ only in how the *part* comes into being.
3832
- */
3833
- const NOTES_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
3834
- const THEME_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml";
3835
- const textEncoder$1 = new TextEncoder();
3836
- /**
3837
- * Carry the source slide's speaker notes onto the just-imported slide (the
3838
- * `importNotes` option). The slide copy itself dropped the `notesSlide` rel, so
3839
- * this copies the source `notesSlide` part into a fresh partname, wires a
3840
- * `slide → notesSlide` rel on the new slide, and rebuilds the copied notesSlide's
3841
- * own relationships:
3842
- *
3843
- * - its `slide` back-rel is repointed at the new slide (`newSlidePartName`) — the
3844
- * source slide is *not* copied (that would be circular);
3845
- * - its `notesMaster` rel is resolved through {@link ensureNotesMaster}, which
3846
- * reuses this deck's notesMaster when it has one and copies the source's only
3847
- * when it has none (a deck may have at most one notesMaster);
3848
- * - any other internal target (media, etc.) is copied via {@link copyPart}.
3849
- *
3850
- * No-op when the source slide has no notes. Content-type registration for the
3851
- * copied parts is handled by `addPart`/{@link copyPart}.
3852
- */
3853
- function carryNotes(dest, source, ctx, sourceSlidePartName, newSlidePartName) {
3854
- const sourceSlideRels = source.opc.relationshipsFor(sourceSlidePartName);
3855
- const notesRel = sourceSlideRels.byType(NOTES_SLIDE_REL)[0];
3856
- if (!notesRel) return;
3857
- const sourceNotesPartName = sourceSlideRels.resolveTarget(notesRel.id);
3858
- const sourceNotesPart = source.opc.part(sourceNotesPartName);
3859
- if (!sourceNotesPart) return;
3860
- const newNotesPartName = dest.opc.reservePartNameLike(sourceNotesPartName);
3861
- dest.opc.addPart(newNotesPartName, sourceNotesPart.contentType, sourceNotesPart.bytes);
3862
- dest.opc.relationshipsFor(newSlidePartName).add(NOTES_SLIDE_REL, relativePartName(newSlidePartName, newNotesPartName));
3863
- const notesSourceRels = source.opc.relationshipsFor(sourceNotesPartName);
3864
- const notesTargetRels = dest.opc.relationshipsFor(newNotesPartName);
3865
- for (const rel of notesSourceRels) {
3866
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") {
3867
- notesTargetRels.addWithId(rel.id, SLIDE_REL, relativePartName(newNotesPartName, newSlidePartName));
3868
- continue;
3869
- }
3870
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster") {
3871
- const notesMaster = ensureNotesMaster(dest, ctx, notesSourceRels.resolveTarget(rel.id));
3872
- notesTargetRels.addWithId(rel.id, NOTES_MASTER_REL, relativePartName(newNotesPartName, notesMaster));
3873
- continue;
3874
- }
3875
- if (rel.targetMode === "External") {
3876
- notesTargetRels.addWithId(rel.id, rel.type, rel.target, "External");
3877
- continue;
3878
- }
3879
- const newTarget = copyPart(ctx, notesSourceRels.resolveTarget(rel.id));
3880
- notesTargetRels.addWithId(rel.id, rel.type, relativePartName(newNotesPartName, newTarget));
3881
- }
3882
- }
3883
- /**
3884
- * Resolve the notesMaster an imported `notesSlide` should bind to, honouring the
3885
- * single-notesMaster-per-presentation rule (`p:notesMasterIdLst` holds 0..1
3886
- * `p:notesMasterId`). If this deck already has a notesMaster it is reused and the
3887
- * source's is *not* copied (the destination's notes styling wins); otherwise the
3888
- * source notesMaster (and, via {@link copyPart}, its theme) is copied and
3889
- * registered in `presentation.xml`. Returns the destination notesMaster partname.
3890
- */
3891
- function ensureNotesMaster(dest, ctx, sourceNotesMasterPartName) {
3892
- const presPart = dest.presentationPart;
3893
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3894
- const existing = presRels.byType(NOTES_MASTER_REL)[0];
3895
- if (existing) return presRels.resolveTarget(existing.id);
3896
- return registerNotesMaster(dest, copyPart(ctx, sourceNotesMasterPartName));
3897
- }
3898
- /**
3899
- * Wire an already-added notesMaster part into `presentation.xml`: a `notesMaster`
3900
- * relationship plus the single `p:notesMasterId` entry that `CT_NotesMasterIdList`
3901
- * allows. Returns the partname, so callers can use it as a rel target.
3902
- *
3903
- * Split out of {@link ensureNotesMaster} because the two ways a notesMaster arrives
3904
- * — copied from another `Presentation`, or authored by a generator and injected by
3905
- * {@link Presentation.appendSlides} — differ only in how the *part* is created, not in how it is
3906
- * registered.
3907
- */
3908
- function registerNotesMaster(dest, notesMasterPartName) {
3909
- const presPart = dest.presentationPart;
3910
- const relId = dest.opc.relationshipsFor(presPart.partName).add(NOTES_MASTER_REL, relativePartName(presPart.partName, notesMasterPartName)).id;
3911
- const root = presPart.dom.documentElement;
3912
- if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a notes master in");
3913
- const lst = getOrAddChild(root, "p:notesMasterIdLst", [
3914
- "p:handoutMasterIdLst",
3915
- "p:sldIdLst",
3916
- "p:sldSz",
3917
- "p:notesSz",
3918
- "p:embeddedFontLst",
3919
- "p:custShowLst",
3920
- "p:photoAlbum",
3921
- "p:custDataLst",
3922
- "p:kinsoku",
3923
- "p:defaultTextStyle",
3924
- "p:modifyVerifier",
3925
- "p:extLst"
3926
- ]);
3927
- removeChildrenByQName(lst, ["p:notesMasterId"]);
3928
- const entry = createElement(presPart.dom, "p:notesMasterId");
3929
- setAttr(entry, "r:id", relId);
3930
- lst.appendChild(entry);
3931
- presPart.markDirty();
3932
- return notesMasterPartName;
3933
- }
3934
- /**
3935
- * Resolve the notesMaster an *appended* slide's notes should bind to. Same
3936
- * single-notesMaster rule as {@link ensureNotesMaster}: this deck's own wins when it
3937
- * has one, so the destination's notes styling is preserved and `master.xml` is
3938
- * discarded. Otherwise the generator's notes master is installed, together with the
3939
- * theme its `.rels` requires (the normal write path emits that as `theme2.xml`).
3940
- */
3941
- function ensureNotesMasterFromXml(dest, master) {
3942
- const presPart = dest.presentationPart;
3943
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3944
- const existing = presRels.byType(NOTES_MASTER_REL)[0];
3945
- if (existing) return presRels.resolveTarget(existing.id);
3946
- const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
3947
- dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$1.encode(master.xml));
3948
- const themePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
3949
- dest.opc.addPart(themePartName, THEME_CONTENT_TYPE, textEncoder$1.encode(master.themeXml));
3950
- dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, themePartName));
3951
- return registerNotesMaster(dest, masterPartName);
3952
- }
3953
- //#endregion
3954
4441
  //#region src/read/api/ops/prune.ts
3955
4442
  /**
3956
4443
  * Orphan pruning after a part stops being referenced.
@@ -4068,6 +4555,9 @@ function rescalePartGeometry(dest, rescaledParts, partName, transform) {
4068
4555
  */
4069
4556
  const HYPERLINK_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
4070
4557
  const CHART_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
4558
+ const CHARTEX_REL = "http://schemas.microsoft.com/office/2014/relationships/chartEx";
4559
+ const CHART_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartStyle";
4560
+ const CHART_COLOR_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
4071
4561
  const PACKAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
4072
4562
  const AUDIO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
4073
4563
  const VIDEO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
@@ -4075,6 +4565,9 @@ const MS_MEDIA_REL = "http://schemas.microsoft.com/office/2007/relationships/med
4075
4565
  const SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
4076
4566
  const NOTES_SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
4077
4567
  const CHART_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
4568
+ const CHARTEX_CONTENT_TYPE = "application/vnd.ms-office.chartex+xml";
4569
+ const CHART_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartstyle+xml";
4570
+ const CHART_COLOR_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartcolorstyle+xml";
4078
4571
  const XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
4079
4572
  /** Content type of the main part in an editable `.pptx` package. */
4080
4573
  const PRESENTATION_MAIN_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
@@ -4304,12 +4797,17 @@ var Presentation = class Presentation {
4304
4797
  * Duplicate the slide at `index` and insert the copy at `options.at` (deck
4305
4798
  * order; `0` = first), defaulting to appending at the end when `at` is omitted
4306
4799
  * or out of range. Returns the new slide. The new slide part copies the source
4307
- * bytes verbatim and shares the source's relationship targets (layout, images,
4308
- * …) by copying its `.rels`; a new presentation→slide relationship and a
4309
- * `p:sldId` entry are wired up. Marks the presentation part dirty.
4800
+ * bytes verbatim and shares the source's deck-wide relationship targets
4801
+ * (layout, images, media) by copying its `.rels`; a new presentation→slide
4802
+ * relationship and a `p:sldId` entry are wired up. Marks the presentation part
4803
+ * dirty.
4310
4804
  *
4311
- * Note: relationships are copied as-is, so a source slide that owns a
4312
- * one-to-one part (e.g. a notes slide) would end up shared with the clone.
4805
+ * What the source page *owns* is copied rather than shared: its notes slide,
4806
+ * charts, SmartArt diagrams and OLE embeddings, each with the subtree under it
4807
+ * (a chart's embedded workbook comes along; the image inside its user-shapes
4808
+ * drawing stays shared). PowerPoint refuses to open a deck where two slides
4809
+ * resolve to one chart or diagram, so this is not tidiness — see
4810
+ * `page-owned.ts` for the rule and the evidence behind it.
4313
4811
  */
4314
4812
  cloneSlide(index, options = {}) {
4315
4813
  const source = this.slides[index];
@@ -4318,8 +4816,10 @@ var Presentation = class Presentation {
4318
4816
  const sourcePart = source.part;
4319
4817
  const newPartName = opc.reservePartNameLike(sourcePart.partName);
4320
4818
  const newPart = opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
4321
- const sourceRels = opc.part(relsPartNameFor(sourcePart.partName));
4322
- if (sourceRels) opc.addPart(relsPartNameFor(newPartName), sourceRels.contentType, sourceRels.bytes);
4819
+ const sourceRels = opc.relationshipsFor(sourcePart.partName);
4820
+ const cloneRels = opc.relationshipsFor(newPartName);
4821
+ for (const rel of sourceRels) cloneRels.addWithId(rel.id, rel.type, rel.target, rel.targetMode);
4822
+ duplicateOwnedTargets(opc, sourcePart.partName, newPartName);
4323
4823
  return this.#insertSlidePart(newPart, options.at);
4324
4824
  }
4325
4825
  /**
@@ -4372,7 +4872,10 @@ var Presentation = class Presentation {
4372
4872
  * Only the layout(s) actually used by imported slides are copied; the imported
4373
4873
  * master's `p:sldLayoutIdLst` is pruned to exactly those, mirroring how
4374
4874
  * PowerPoint's "Reuse Slides" brings a slide across. Parts shared by repeated
4375
- * imports from the same source deck are copied once and reused.
4875
+ * imports from the same source deck are copied once and reused — every part
4876
+ * except the page itself. Importing the same source slide twice yields two
4877
+ * independent copies over one shared layout/master/theme, which is what lets a
4878
+ * deck show one page twice (verbatim, then edited).
4376
4879
  *
4377
4880
  * With `{ theme: 'preserve' }` the slide's source theme is instead *flattened*
4378
4881
  * into the slide XML and the slide is bound to this deck's existing
@@ -4397,7 +4900,7 @@ var Presentation = class Presentation {
4397
4900
  }
4398
4901
  if (sizesDiffer && options.rescale && (!target || !incoming)) throw new InvalidOptionError("import/slide-size-unknown", "importSlide rescale requires both decks to declare a slide size (p:sldSz)");
4399
4902
  const importCtx = this.#importContext(source.opc);
4400
- const newPartName = options.theme === "preserve" ? importSlidePreserve(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true) : options.theme === "restyle" ? importSlideRestyle(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true, options.remapLiterals === true) : copyPart(importCtx, sourceSlide.partName);
4903
+ const newPartName = options.theme === "preserve" ? importSlidePreserve(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true) : options.theme === "restyle" ? importSlideRestyle(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true, options.remapLiterals === true) : copySlidePart(importCtx, sourceSlide.partName);
4401
4904
  const newPart = this.opc.part(newPartName);
4402
4905
  if (!newPart) throw new InternalError("import/part-went-missing", `Imported slide part went missing: ${newPartName}`);
4403
4906
  if (sizesDiffer && options.rescale && target && incoming) rescaleImportedGeometry(this, this.#rescaledParts, newPartName, options.theme, incoming, target, options.rescale === true ? "fit" : options.rescale);
@@ -4407,6 +4910,140 @@ var Presentation = class Presentation {
4407
4910
  return slide;
4408
4911
  }
4409
4912
  /**
4913
+ * Import selected pages from one or more loaded source presentations as one
4914
+ * batch. Each imported page lands at its `outputIndex` in the complete
4915
+ * destination slide list after the batch, and the returned array is parallel
4916
+ * to `requests` — `result[i]` is the page `requests[i]` asked for, whatever
4917
+ * order the output positions were given in.
4918
+ *
4919
+ * Everything is checked before a single byte of this deck moves: every request
4920
+ * names an existing source page, final output positions are unique, each source
4921
+ * has this deck's slide size, and a read-only dry run of the copy proves every
4922
+ * part it would reach is present. A batch therefore either applies in full or
4923
+ * leaves this deck byte-identical, where a per-page loop of
4924
+ * {@link importSlide} can leave a half-stitched deck behind.
4925
+ *
4926
+ * The batch also decides what a `slide → slide` link means: an internal link
4927
+ * on a selected page must target another **selected** page (or one this deck
4928
+ * already contains via an earlier import from that source), and is rewritten
4929
+ * to the fresh partname — importing page 3 of 10 does not drag pages 1–2
4930
+ * across as dependencies, and never strands the link. This mirrors
4931
+ * `appendSlides`' `import/unresolved-slide-link`, which the write side already
4932
+ * enforces for generator decks.
4933
+ *
4934
+ * One request is one output page, so naming the same source page in several
4935
+ * requests is how you ask for several independent copies of it — the page part
4936
+ * is the one thing an import never shares, exactly as in {@link importSlide}.
4937
+ * Everything under it (layout, master, theme, media) is still copied once and
4938
+ * shared. Where such a page is the target of a `slide → slide` link, the link
4939
+ * resolves to one of its copies: pages duplicated together are copied in
4940
+ * lockstep and link to their round-mates, and a link into a page requested only
4941
+ * once always lands on that single copy.
4942
+ *
4943
+ * Speaker notes travel per request: `{ importNotes: true }` carries that page's
4944
+ * `notesSlide` part across, wired to the new page and bound to a single
4945
+ * `notesMaster` under the same 0..1 rule {@link importSlide} and
4946
+ * {@link appendSlides} follow — the destination's own master wins when it has
4947
+ * one. Notes are part of the up-front dry run too, so a batch that would fail
4948
+ * carrying them is refused with the deck still byte-identical. A page named in
4949
+ * several requests gets its own copy of its notes each time, as of everything
4950
+ * else that page owns.
4951
+ *
4952
+ * Scope: pages come across under `'copy'` theme semantics (their own layout →
4953
+ * master → theme subgraph, shared parts deduped via the copy registry).
4954
+ * Embedded fonts are not carried, and sizes must match, since there is no batch
4955
+ * spelling for `embedFonts` or `rescale`. Use {@link importSlide} when you need
4956
+ * either of those.
4957
+ */
4958
+ importSlides(requests) {
4959
+ const resolved = requests.map((request, requestIndex) => {
4960
+ if (!Number.isInteger(request.sourceIndex) || request.sourceIndex < 0) throw new InvalidOptionError("slide/index-out-of-range", `importSlides: sourceIndex must be a non-negative integer; received ${request.sourceIndex}`);
4961
+ if (!Number.isInteger(request.outputIndex) || request.outputIndex < 0) throw new InvalidOptionError("import/output-index-out-of-range", `importSlides: outputIndex must be a non-negative integer; received ${request.outputIndex}`);
4962
+ const sourceSlide = request.source.slides[request.sourceIndex];
4963
+ if (!sourceSlide) throw new InvalidOptionError("slide/index-out-of-range", `importSlides: no slide at index ${request.sourceIndex} to import`);
4964
+ return {
4965
+ ...request,
4966
+ requestIndex,
4967
+ sourceSlide
4968
+ };
4969
+ });
4970
+ if (resolved.length === 0) return [];
4971
+ if (!this.presentationPart.dom.documentElement) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to insert slides into");
4972
+ const finalSlideCount = this.slides.length + resolved.length;
4973
+ const selectedPages = /* @__PURE__ */ new Map();
4974
+ const notesPages = /* @__PURE__ */ new Map();
4975
+ for (const request of resolved) {
4976
+ let pages = selectedPages.get(request.source.opc);
4977
+ if (!pages) {
4978
+ pages = /* @__PURE__ */ new Set();
4979
+ selectedPages.set(request.source.opc, pages);
4980
+ }
4981
+ pages.add(request.sourceSlide.partName);
4982
+ if (!request.importNotes) continue;
4983
+ let withNotes = notesPages.get(request.source.opc);
4984
+ if (!withNotes) {
4985
+ withNotes = /* @__PURE__ */ new Set();
4986
+ notesPages.set(request.source.opc, withNotes);
4987
+ }
4988
+ withNotes.add(request.sourceSlide.partName);
4989
+ }
4990
+ const target = this.slideSize;
4991
+ const outputIndexes = /* @__PURE__ */ new Set();
4992
+ for (const request of resolved) {
4993
+ if (request.outputIndex >= finalSlideCount) throw new InvalidOptionError("import/output-index-out-of-range", `importSlides: outputIndex ${request.outputIndex} is outside the final slide list of ${finalSlideCount} slides`);
4994
+ if (outputIndexes.has(request.outputIndex)) throw new InvalidOptionError("import/output-index-conflict", `importSlides: outputIndex ${request.outputIndex} is requested more than once`);
4995
+ outputIndexes.add(request.outputIndex);
4996
+ const incoming = request.source.slideSize;
4997
+ if (!target || !incoming || target.widthEmu !== incoming.widthEmu || target.heightEmu !== incoming.heightEmu) {
4998
+ const fmt = (size) => size ? `${size.widthEmu}×${size.heightEmu} EMU` : "unknown";
4999
+ throw new InvalidOptionError("import/slide-size-mismatch", `importSlides requires equal slide sizes; target is ${fmt(target)}, source is ${fmt(incoming)}`);
5000
+ }
5001
+ }
5002
+ const copyMaster = this.opc.relationshipsFor(this.presentationPart.partName).byType(NOTES_MASTER_REL).length === 0;
5003
+ for (const [sourceOpc, pages] of selectedPages) checkSelectionCopyable(sourceOpc, this.#importContext(sourceOpc).registry, pages, {
5004
+ pages: notesPages.get(sourceOpc) ?? /* @__PURE__ */ new Set(),
5005
+ copyMaster
5006
+ });
5007
+ const destinationsBySource = /* @__PURE__ */ new Map();
5008
+ const planned = resolved.map((request) => {
5009
+ let destinations = destinationsBySource.get(request.source.opc);
5010
+ if (!destinations) {
5011
+ destinations = /* @__PURE__ */ new Map();
5012
+ destinationsBySource.set(request.source.opc, destinations);
5013
+ }
5014
+ const sourcePart = request.sourceSlide.part;
5015
+ const newPartName = this.opc.reservePartNameLike(request.sourceSlide.partName);
5016
+ const destPart = this.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
5017
+ const reserved = destinations.get(request.sourceSlide.partName);
5018
+ if (reserved) reserved.push(newPartName);
5019
+ else destinations.set(request.sourceSlide.partName, [newPartName]);
5020
+ return {
5021
+ ...request,
5022
+ destPart
5023
+ };
5024
+ });
5025
+ for (const [sourceOpc, destinations] of destinationsBySource) {
5026
+ const base = this.#importContext(sourceOpc);
5027
+ const rounds = Math.max(...[...destinations.values()].map((reserved) => reserved.length));
5028
+ for (let round = 0; round < rounds; round++) {
5029
+ const plan = /* @__PURE__ */ new Map();
5030
+ for (const [sourcePartName, reserved] of destinations) plan.set(sourcePartName, reserved[round] ?? reserved[0]);
5031
+ const ctx = {
5032
+ ...base,
5033
+ selection: { destinations: plan }
5034
+ };
5035
+ for (const [sourcePartName, reserved] of destinations) if (round < reserved.length) copyPart(ctx, sourcePartName);
5036
+ }
5037
+ }
5038
+ for (const request of planned) {
5039
+ if (!request.importNotes) continue;
5040
+ carryNotes(this, request.source, this.#importContext(request.source.opc), request.sourceSlide.partName, request.destPart.partName);
5041
+ }
5042
+ const added = [];
5043
+ for (const request of [...planned].sort((left, right) => left.outputIndex - right.outputIndex)) added[request.requestIndex] = this.#insertSlidePart(request.destPart, request.outputIndex);
5044
+ return added;
5045
+ }
5046
+ /**
4410
5047
  * Graft slide master(s) from another open package into this one and return what
4411
5048
  * was copied. Unlike {@link importSlide} — which brings a master across only as
4412
5049
  * the dependency of an imported *slide* and prunes it to the one layout that
@@ -4516,7 +5153,10 @@ var Presentation = class Presentation {
4516
5153
  *
4517
5154
  * Charts and internal slide-to-slide hyperlinks are carried across: chart parts
4518
5155
  * (chart XML + `.rels` + embedded workbook) are injected under fresh names, and a
4519
- * `slide:N` link is repointed at the Nth appended slide's new partname.
5156
+ * `slide:N` link is repointed at the Nth appended slide's new partname. A chartEx
5157
+ * (Office 2016 — waterfall, funnel, treemap, ...) chart carries too, as its own
5158
+ * `chartEx{N}.xml` part behind the MS chartEx rel, with the style and color-style
5159
+ * sidecars PowerPoint requires alongside it.
4520
5160
  *
4521
5161
  * The generator's presentation-level embedded fonts (`pptx.embedFont`) are also
4522
5162
  * carried into this deck and merged into its `p:embeddedFontLst`, de-duped by
@@ -4612,13 +5252,23 @@ var Presentation = class Presentation {
4612
5252
  for (const h of slide.notes.hyperlinks) notesRels.addWithId(`rId${h.rId}`, HYPERLINK_REL, h.target, "External");
4613
5253
  }
4614
5254
  for (const c of slide.charts) {
4615
- const chartPartName = this.opc.reservePartNameLike("/ppt/charts/chart1.xml");
4616
- this.opc.addPart(chartPartName, CHART_CONTENT_TYPE, textEncoder.encode(c.chartXml));
5255
+ const isChartEx = c.chartEx !== void 0;
5256
+ const chartPartName = this.opc.reservePartNameLike(isChartEx ? "/ppt/charts/chartEx1.xml" : "/ppt/charts/chart1.xml");
5257
+ this.opc.addPart(chartPartName, isChartEx ? CHARTEX_CONTENT_TYPE : CHART_CONTENT_TYPE, textEncoder.encode(c.chartXml));
4617
5258
  const embeddingPartName = this.opc.reservePartNameLike("/ppt/embeddings/Microsoft_Excel_Worksheet1.xlsx");
4618
5259
  this.opc.contentTypes.ensureDefault("xlsx", XLSX_CONTENT_TYPE);
4619
5260
  this.opc.addPart(embeddingPartName, XLSX_CONTENT_TYPE, c.embeddingBytes);
4620
- this.opc.relationshipsFor(chartPartName).addWithId("rId1", PACKAGE_REL, relativePartName(chartPartName, embeddingPartName));
4621
- rels.addWithId(`rId${c.rId}`, CHART_REL, relativePartName(partName, chartPartName));
5261
+ const chartRels = this.opc.relationshipsFor(chartPartName);
5262
+ chartRels.addWithId("rId1", PACKAGE_REL, relativePartName(chartPartName, embeddingPartName));
5263
+ if (c.chartEx) {
5264
+ const colorsPartName = this.opc.reservePartNameLike("/ppt/charts/colors1.xml");
5265
+ this.opc.addPart(colorsPartName, CHART_COLOR_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.colorsXml));
5266
+ chartRels.addWithId("rId2", CHART_COLOR_STYLE_REL, relativePartName(chartPartName, colorsPartName));
5267
+ const stylePartName = this.opc.reservePartNameLike("/ppt/charts/style1.xml");
5268
+ this.opc.addPart(stylePartName, CHART_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.styleXml));
5269
+ chartRels.addWithId("rId3", CHART_STYLE_REL, relativePartName(chartPartName, stylePartName));
5270
+ }
5271
+ rels.addWithId(`rId${c.rId}`, isChartEx ? CHARTEX_REL : CHART_REL, relativePartName(partName, chartPartName));
4622
5272
  }
4623
5273
  for (const link of slide.slideLinks) {
4624
5274
  const targetPartName = partBySourceNumber.get(link.sourceSlideNumber);
@@ -4701,7 +5351,7 @@ var Presentation = class Presentation {
4701
5351
  const result = [];
4702
5352
  for (const shapeEl of sourceElements) {
4703
5353
  const imported = targetDoc.importNode(shapeEl, true);
4704
- rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap);
5354
+ rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap, newOwnedScope());
4705
5355
  if (ctx) {
4706
5356
  const holder = createElement(targetDoc, "p:spTree");
4707
5357
  holder.appendChild(imported);
@@ -4763,7 +5413,13 @@ var Presentation = class Presentation {
4763
5413
  */
4764
5414
  #insertSlidePart(newPart, at) {
4765
5415
  const presPart = this.presentationPart;
4766
- const relId = this.opc.relationshipsFor(presPart.partName).add(SLIDE_REL, relativePartName(presPart.partName, newPart.partName)).id;
5416
+ const presRels = this.opc.relationshipsFor(presPart.partName);
5417
+ for (const rel of presRels) {
5418
+ if (rel.type !== "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" || rel.targetMode === "External") continue;
5419
+ if (presRels.resolveTarget(rel.id) !== newPart.partName) continue;
5420
+ throw new InternalError("slide/part-already-in-deck", `Slide part ${newPart.partName} is already in this deck's slide list; a second p:sldId entry for it would make the package unopenable`);
5421
+ }
5422
+ const relId = presRels.add(SLIDE_REL, relativePartName(presPart.partName, newPart.partName)).id;
4767
5423
  const root = presPart.dom.documentElement;
4768
5424
  if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to append a slide to");
4769
5425
  const sldIdLst = getOrAddChild(root, "p:sldIdLst", PRESENTATION_SLD_ID_LST_SUCCESSORS);
@@ -4797,4 +5453,4 @@ var Presentation = class Presentation {
4797
5453
  //#endregion
4798
5454
  export { Placeholder as a, Theme as c, NotesSlide as d, OpcPackage as f, ContentTypes as h, Slide as i, readTagsForPart as l, Part as m, readCoreProperties as n, SlideLayout as o, Relationships as p, readCustomProperties as r, SlideMaster as s, Presentation as t, NotesPlaceholder as u };
4799
5455
 
4800
- //# sourceMappingURL=presentation-BP1B8PCF.js.map
5456
+ //# sourceMappingURL=presentation-DeKTJGHf.js.map