@shbernal/ts-pptx 3.2.0 → 3.4.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 (77) hide show
  1. package/README.md +19 -20
  2. package/dist/{autopage-QQyfRtoK.js → autopage-BYlTzX-l.js} +21 -17
  3. package/dist/autopage-BYlTzX-l.js.map +1 -0
  4. package/dist/browser.d.ts +6 -6
  5. package/dist/browser.js +11 -12
  6. package/dist/browser.js.map +1 -1
  7. package/dist/{diagnostics-Chjz_4_Q.d.ts → diagnostics-B0OmTOoz.d.ts} +65 -16
  8. package/dist/diagnostics-B0OmTOoz.d.ts.map +1 -0
  9. package/dist/{diagnostics-DYQC7xLV.js → diagnostics-CPVADSL1.js} +0 -0
  10. package/dist/{diagnostics-DYQC7xLV.js.map → diagnostics-CPVADSL1.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-BVLCFr-t.d.ts → errors-fB1KDJKQ.d.ts} +5 -5
  14. package/dist/{errors-BVLCFr-t.d.ts.map → errors-fB1KDJKQ.d.ts.map} +1 -1
  15. package/dist/{fit-DhMbQG5O.js → fit-TjpNbMru.js} +0 -0
  16. package/dist/fit-TjpNbMru.js.map +1 -0
  17. package/dist/{html-dom-DajBuobe.js → html-dom-yUSdCExA.js} +5 -6
  18. package/dist/html-dom-yUSdCExA.js.map +1 -0
  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 +16 -34
  23. package/dist/index.js.map +1 -1
  24. package/dist/inspect.d.ts +1 -1
  25. package/dist/inspect.js +5 -4
  26. package/dist/inspect.js.map +1 -1
  27. package/dist/math.d.ts +1 -1
  28. package/dist/measure.d.ts +96 -6
  29. package/dist/measure.d.ts.map +1 -1
  30. package/dist/measure.js +2 -2
  31. package/dist/{st-enums-evQG2A7Z.js → namespaces-Sy_m7Nhd.js} +318 -209
  32. package/dist/namespaces-Sy_m7Nhd.js.map +1 -0
  33. package/dist/node.d.ts +6 -6
  34. package/dist/node.js +7 -20
  35. package/dist/node.js.map +1 -1
  36. package/dist/notes-bWApshah.js +1727 -0
  37. package/dist/notes-bWApshah.js.map +1 -0
  38. package/dist/{pres-C-EU3SkN.d.ts → pres-Dybf7ZUl.d.ts} +4 -4
  39. package/dist/{pres-C-EU3SkN.d.ts.map → pres-Dybf7ZUl.d.ts.map} +1 -1
  40. package/dist/{presentation-CxnUVhK9.js → presentation-3rAKKRfm.js} +1247 -743
  41. package/dist/presentation-3rAKKRfm.js.map +1 -0
  42. package/dist/{presentation-Osx1cDdJ.d.ts → presentation-types-D7cgT-eG.d.ts} +867 -24
  43. package/dist/presentation-types-D7cgT-eG.d.ts.map +1 -0
  44. package/dist/read.d.ts +4 -5
  45. package/dist/read.d.ts.map +1 -1
  46. package/dist/read.js +3 -3
  47. package/dist/script.d.ts +2 -2
  48. package/dist/script.d.ts.map +1 -1
  49. package/dist/script.js +0 -0
  50. package/dist/script.js.map +1 -1
  51. package/dist/{shapes-DoJBPrwN.js → shapes-Dyw5bxwO.js} +371 -52
  52. package/dist/shapes-Dyw5bxwO.js.map +1 -0
  53. package/dist/{slide-BTfn1Pyp.d.ts → slide-B_aInw3N.d.ts} +246 -182
  54. package/dist/slide-B_aInw3N.d.ts.map +1 -0
  55. package/dist/{text-C4P9MNdf.js → text-DSN3anEh.js} +3609 -4932
  56. package/dist/text-DSN3anEh.js.map +1 -0
  57. package/dist/{units-internal-CR8s8YQa.js → units-internal-DNvK2I6o.js} +207 -35
  58. package/dist/units-internal-DNvK2I6o.js.map +1 -0
  59. package/dist/zip.d.ts +2 -2
  60. package/package.json +36 -27
  61. package/dist/autopage-QQyfRtoK.js.map +0 -1
  62. package/dist/diagnostics-Chjz_4_Q.d.ts.map +0 -1
  63. package/dist/embedded-fonts-MaynAHa9.js +0 -94
  64. package/dist/embedded-fonts-MaynAHa9.js.map +0 -1
  65. package/dist/fit-DhMbQG5O.js.map +0 -1
  66. package/dist/html-dom-DajBuobe.js.map +0 -1
  67. package/dist/presentation-CxnUVhK9.js.map +0 -1
  68. package/dist/presentation-Osx1cDdJ.d.ts.map +0 -1
  69. package/dist/presentation-types-Dj4riuDs.d.ts +0 -538
  70. package/dist/presentation-types-Dj4riuDs.d.ts.map +0 -1
  71. package/dist/shapes-DoJBPrwN.js.map +0 -1
  72. package/dist/slide-BTfn1Pyp.d.ts.map +0 -1
  73. package/dist/st-enums-evQG2A7Z.js.map +0 -1
  74. package/dist/text-C4P9MNdf.js.map +0 -1
  75. package/dist/units-C7QM1KgH.js +0 -144
  76. package/dist/units-C7QM1KgH.js.map +0 -1
  77. 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 { mt as emuToInches } from "./units-internal-DNvK2I6o.js";
3
+ import { D as voidEl, E as raw, T as el, g as FONT_REL_TYPE, h as FONT_DATA_EXTENSION, m as FONT_DATA_CONTENT_TYPE, n as makeXmlNotesMaster, o as makeXmlNotesSlideSkeleton, p as EMBEDDED_FONT_SLOTS } from "./notes-bWApshah.js";
4
+ import { _ as THEME_REL, a as NOTES_MASTER_REL, c as OFFICE_DOCUMENT_REL, d as SLIDE_LAYOUT_CONTENT_TYPE, f as SLIDE_LAYOUT_REL, g as THEME_CONTENT_TYPE, h as SLIDE_REL, i as NOTES_MASTER_CONTENT_TYPE, l as OFFICE_REL, m as SLIDE_MASTER_REL, o as NOTES_SLIDE_CONTENT_TYPE, p as SLIDE_MASTER_CONTENT_TYPE, r as IMAGE_REL, s as NOTES_SLIDE_REL, t as OOXML_NS } from "./namespaces-Sy_m7Nhd.js";
5
5
  import { ZipWriter, readZip } from "./zip.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-DoJBPrwN.js";
6
+ import { $ as collectElements, A as readPatternFill, At as zipPathToPartName, B as LST_STYLE_LEVELS, Bt as getOrAddChild, C as Picture, Ct as SPPR_SP3D_AFTER, Dt as relsPartNameFor, Et as relativePartName, F as resolveMasterColorContext, Ft as descendantsByTag, G as placeholderInheritedRunProps, Gt as removeAttr, H as placeholderInheritedAnchor, Ht as intValue, I as resolveNotesColorContext, It as escapeXmlAttribute, J as slideDefinesColor, Jt as serializeXml, K as placeholderInheritedXfrm, Kt as removeChildrenByQName, L as resolveSlideColorContext, Lt as firstChild, M as readGradientFill, Mt as boolValue, N as resolveColorElement, Nt as childElements, O as TextFrame, Ot as resolveRelativePartName, P as resolveLayoutColorContext, Pt as createElement, Q as carriedDecorations, R as resolveSlideThemeParts, Rt as firstChildElement, St as SPPR_SCENE3D_AFTER, Tt as partNameToZipPath, U as placeholderInheritedColor, Ut as ownerDocumentOf, V as RUN_PROP_NAMES, Vt as insertInOrder, W as placeholderInheritedListStyles, Wt as parseXml, X as cSldName, Y as slideDefinesProp, Yt as setAttr, Z as cSldOf, _t as SHAPE_AFTER_SPPR, at as fmtEntry, bt as SPPR_FILL_AFTER, ct as parseClrMap, dt as resolveSchemeToken, et as firstShapeChild, ft as styleRefFill, gt as RPR_FILL_AFTER, ht as FILL_CHOICES, it as SCHEME_SLOTS, j as readPictureFill, jt as attr, k as spPrXfrmEmu, kt as resolveSingleRel, l as wrapShapeElement, lt as parseClrScheme, mt as substitutePhClr, n as buildShapes, nt as nvPrOf, ot as intAttr, pt as styleRefLine, q as placeholderOf, qt as replaceInParent, r as findShapeByIdDeep, rt as spTreeOf, st as isA, t as GroupShape, tt as nthShapeChild, u as GraphicFrame, ut as resolveColor, vt as SPPR_AFTER_XFRM, w as AutoShape, wt as partNameExtension, xt as SPPR_LN_AFTER, yt as SPPR_EFFECT_AFTER, zt as getElements } from "./shapes-Dyw5bxwO.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
  }
@@ -594,7 +594,7 @@ function reverseClrMap(clrMap) {
594
594
  function applyInheritedBackground(slideRoot, ctx) {
595
595
  const inherited = ctx.inheritedBackground;
596
596
  if (!inherited) return;
597
- const cSld = firstChild(slideRoot, "p:cSld");
597
+ const cSld = cSldOf(slideRoot);
598
598
  if (!cSld || firstChild(cSld, "p:bg")) return;
599
599
  const bg = ownerDocumentOf(slideRoot).importNode(inherited, true);
600
600
  insertInOrder(cSld, bg, [
@@ -644,6 +644,28 @@ function resolveSchemeColors(root, ctx) {
644
644
  * re-bind to the destination styles.
645
645
  */
646
646
  function resolvePlaceholderRunColors(slideRoot, ctx) {
647
+ bakePlaceholderRunProperty(slideRoot, ctx, placeholderInheritedColor, (run, color, pPr, slideLst, level) => {
648
+ if (slideDefinesColor(run, pPr, slideLst, level)) return;
649
+ writeRunColor(run, color);
650
+ });
651
+ }
652
+ /**
653
+ * The traversal both run-property passes share: every placeholder shape's text body, every
654
+ * paragraph in it, every `a:r` and `a:fld` in that paragraph.
655
+ *
656
+ * `resolve` is asked once per *list level* per shape and memoized, because walking the
657
+ * layout/master style chain is the expensive half and every paragraph at the same level gets
658
+ * the same answer. `write` then runs per run, and owns the decision to skip a run the slide
659
+ * already fixes — the two passes ask that question differently (one colour check versus three
660
+ * independent property checks), so it stays on their side of the boundary rather than becoming
661
+ * a predicate parameter only one caller could use.
662
+ *
663
+ * @param slideRoot - the slide part's root element
664
+ * @param ctx - the flatten context, carrying the source layout/master roots
665
+ * @param resolve - the inherited value for a placeholder type/idx at a list level, or `null`
666
+ * @param write - apply a resolved value to one run
667
+ */
668
+ function bakePlaceholderRunProperty(slideRoot, ctx, resolve, write) {
647
669
  if (!ctx.layoutRoot && !ctx.masterRoot) return;
648
670
  for (const sp of descendantsByTag(slideRoot, OOXML_NS.p, "sp")) {
649
671
  const ph = placeholderOf(sp);
@@ -659,16 +681,13 @@ function resolvePlaceholderRunColors(slideRoot, ctx) {
659
681
  const level = (pPr && intValue(attr(pPr, "lvl"))) ?? 0;
660
682
  const runs = [...getElements(p, "a:r"), ...getElements(p, "a:fld")];
661
683
  if (runs.length === 0) continue;
662
- let color = byLevel.get(level);
663
- if (color === void 0) {
664
- color = placeholderInheritedColor(type, idx, level, ctx);
665
- byLevel.set(level, color);
666
- }
667
- if (!color) continue;
668
- for (const run of runs) {
669
- if (slideDefinesColor(run, pPr, slideLst, level)) continue;
670
- writeRunColor(run, color);
684
+ let value = byLevel.get(level);
685
+ if (value === void 0) {
686
+ value = resolve(type, idx, level, ctx);
687
+ byLevel.set(level, value);
671
688
  }
689
+ if (!value) continue;
690
+ for (const run of runs) write(run, value, pPr, slideLst, level);
672
691
  }
673
692
  }
674
693
  }
@@ -788,8 +807,7 @@ function resolvePlaceholderListStyle(shapeRoot, ctx) {
788
807
  */
789
808
  function demotePlaceholders(shapeRoot) {
790
809
  for (const sp of descendantsByTag(shapeRoot, OOXML_NS.p, "sp")) {
791
- const nvSpPr = firstChild(sp, "p:nvSpPr");
792
- const nvPr = nvSpPr && firstChild(nvSpPr, "p:nvPr");
810
+ const nvPr = nvPrOf(sp);
793
811
  if (nvPr) removeChildrenByQName(nvPr, ["p:ph"]);
794
812
  }
795
813
  }
@@ -803,30 +821,7 @@ function demotePlaceholders(shapeRoot) {
803
821
  * is left to re-bind to the destination theme, as the colour pass does for the `fontRef`.
804
822
  */
805
823
  function resolvePlaceholderRunSizes(slideRoot, ctx) {
806
- if (!ctx.layoutRoot && !ctx.masterRoot) return;
807
- for (const sp of descendantsByTag(slideRoot, OOXML_NS.p, "sp")) {
808
- const ph = placeholderOf(sp);
809
- if (!ph) continue;
810
- const txBody = firstChild(sp, "p:txBody");
811
- if (!txBody) continue;
812
- const type = attr(ph, "type");
813
- const idx = attr(ph, "idx") ?? "0";
814
- const slideLst = firstChild(txBody, "a:lstStyle");
815
- const byLevel = /* @__PURE__ */ new Map();
816
- for (const p of getElements(txBody, "a:p")) {
817
- const pPr = firstChild(p, "a:pPr");
818
- const level = (pPr && intValue(attr(pPr, "lvl"))) ?? 0;
819
- const runs = [...getElements(p, "a:r"), ...getElements(p, "a:fld")];
820
- if (runs.length === 0) continue;
821
- let props = byLevel.get(level);
822
- if (props === void 0) {
823
- props = placeholderInheritedRunProps(type, idx, level, ctx);
824
- byLevel.set(level, props);
825
- }
826
- if (!props) continue;
827
- for (const run of runs) writeRunProps(run, props, pPr, slideLst, level);
828
- }
829
- }
824
+ bakePlaceholderRunProperty(slideRoot, ctx, placeholderInheritedRunProps, writeRunProps);
830
825
  }
831
826
  /** Write each resolved run property onto a run's `a:rPr`, skipping ones the slide already fixes. */
832
827
  function writeRunProps(run, props, pPr, slideLst, level) {
@@ -907,7 +902,7 @@ function neutralizeRef(ref) {
907
902
  */
908
903
  /** The `p:cSld/p:bg` element of a slide/layout/master root, or `null`. */
909
904
  function backgroundElementOf(root) {
910
- const cSld = root && firstChild(root, "p:cSld");
905
+ const cSld = cSldOf(root);
911
906
  return cSld ? firstChild(cSld, "p:bg") : null;
912
907
  }
913
908
  /**
@@ -1012,8 +1007,7 @@ var NotesPlaceholder = class {
1012
1007
  this.relationships = relationships;
1013
1008
  }
1014
1009
  #ph() {
1015
- const nvSpPr = firstChild(this.sp, "p:nvSpPr");
1016
- const nvPr = nvSpPr && firstChild(nvSpPr, "p:nvPr");
1010
+ const nvPr = nvPrOf(this.sp);
1017
1011
  return nvPr ? firstChild(nvPr, "p:ph") : null;
1018
1012
  }
1019
1013
  #cNvPr() {
@@ -1147,149 +1141,854 @@ var NotesSlide = class {
1147
1141
  return this.placeholders.find((ph) => ph.type === type) ?? null;
1148
1142
  }
1149
1143
  #spTree() {
1150
- const root = this.part.dom.documentElement;
1151
- const cSld = root && firstChild(root, "p:cSld");
1152
- return cSld ? firstChild(cSld, "p:spTree") : null;
1144
+ return spTreeOf(this.part.dom.documentElement);
1153
1145
  }
1154
1146
  };
1155
1147
  //#endregion
1156
- //#region src/read/api/comments.ts
1157
- /** The slide → comments-part relationship type (legacy comments). */
1158
- const COMMENTS_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
1159
- /** The presentation → commentAuthors-part relationship type. */
1160
- const COMMENT_AUTHORS_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors";
1161
- /** Content type of a legacy per-slide comments part (`ppt/comments/commentN.xml`). */
1162
- const LEGACY_COMMENTS_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.comments+xml";
1163
- /** The slide → modern-comments-part relationship type (2018 schema). */
1164
- const MODERN_COMMENTS_REL_TYPE = "http://schemas.microsoft.com/office/2018/10/relationships/comments";
1165
- /** The presentation → modern-authors-part relationship type (2018 schema). */
1166
- const MODERN_AUTHORS_REL_TYPE = "http://schemas.microsoft.com/office/2018/10/relationships/authors";
1167
- /** Content type of the modern per-slide comments part (`ppt/comments/modernComment_*.xml`). */
1168
- const MODERN_COMMENTS_CONTENT_TYPE = "application/vnd.ms-powerpoint.comments+xml";
1169
- /** Parse a `p:cmAuthorLst` root element into the author registry. */
1170
- function parseCommentAuthors(root) {
1171
- if (!root) return [];
1172
- return getElements(root, "p:cmAuthor").map((el) => ({
1173
- id: intValue(attr(el, "id")),
1174
- name: attr(el, "name") ?? "",
1175
- initials: attr(el, "initials") ?? "",
1176
- lastIdx: intValue(attr(el, "lastIdx")),
1177
- clrIdx: intValue(attr(el, "clrIdx"))
1178
- }));
1179
- }
1148
+ //#region src/read/api/ops/deck-target.ts
1180
1149
  /**
1181
- * Read the deck-wide comment authors from `ppt/commentAuthors.xml`, resolved via the
1182
- * presentation part's `commentAuthors` relationship. `[]` when the deck has no comments.
1150
+ * `ppt/_rels/presentation.xml.rels` -- the deck's own relationship part.
1151
+ *
1152
+ * Every part that masters, layouts, the notes master and embedded fonts hang off
1153
+ * is reached through here, so the two-step `dest.presentationPart` ->
1154
+ * `dest.opc.relationshipsFor(part.partName)` was written out at every one of
1155
+ * those wiring sites. Naming it once means a call site says which rels it wants
1156
+ * rather than how to find them.
1157
+ * @param {DeckTarget} dest - the destination deck
1158
+ * @returns {Relationships} the presentation part's relationships
1183
1159
  */
1184
- function readCommentAuthors(opc, presentationPartName) {
1185
- const rels = opc.relationshipsFor(presentationPartName);
1186
- const rel = rels.byType(COMMENT_AUTHORS_REL_TYPE)[0];
1187
- if (!rel) return [];
1188
- return parseCommentAuthors(opc.part(rels.resolveTarget(rel.id))?.dom.documentElement ?? null);
1160
+ function presentationRels(dest) {
1161
+ return dest.opc.relationshipsFor(dest.presentationPart.partName);
1189
1162
  }
1163
+ //#endregion
1164
+ //#region src/read/api/ops/master-registry.ts
1190
1165
  /**
1191
- * Read one slide's legacy comments from its `comments/commentN.xml` part (resolved
1192
- * via the slide's `comments` relationship), resolving each comment's `@authorId`
1193
- * against `authors` (typically {@link readCommentAuthors}). `[]` when the slide has
1194
- * no comments part.
1166
+ * Slide-master bookkeeping in `presentation.xml` and in a master's own
1167
+ * `p:sldLayoutIdLst`: registering a master so renderers treat it as active,
1168
+ * promoting masters to the front of the id list, allocating ids out of the shared
1169
+ * master/layout id space, and emptying a freshly-copied master's layout list.
1170
+ *
1171
+ * These are the destination-side half of the import machinery -- they read and
1172
+ * write only this deck, never a source package and never the copy registry -- so
1173
+ * they take a structural {@link DeckTarget} rather than a `Presentation`. The
1174
+ * source-side half that drives most of them is `part-copy.ts`.
1195
1175
  */
1196
- function readSlideComments(opc, slidePart, authors) {
1197
- const rels = opc.relationshipsFor(slidePart.partName);
1198
- const rel = rels.byType(COMMENTS_REL_TYPE)[0];
1199
- if (!rel) return [];
1200
- const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1201
- if (!root) return [];
1202
- const byId = new Map(authors.map((a) => [a.id, a]));
1203
- return getElements(root, "p:cm").map((cm) => {
1204
- const authorId = intValue(attr(cm, "authorId"));
1205
- const author = authorId !== null ? byId.get(authorId) ?? null : null;
1206
- const pos = firstChild(cm, "p:pos");
1207
- const textEl = firstChild(cm, "p:text");
1208
- return {
1209
- author: author ? author.name : null,
1210
- authorInitials: author ? author.initials : null,
1211
- authorId,
1212
- idx: intValue(attr(cm, "idx")),
1213
- text: textEl?.textContent ?? "",
1214
- x: pos ? intValue(attr(pos, "x")) : null,
1215
- y: pos ? intValue(attr(pos, "y")) : null,
1216
- date: attr(cm, "dt") ?? null
1217
- };
1218
- });
1219
- }
1220
- /** Concatenate a `p188:txBody`'s paragraphs into plain text, joining paragraphs with `\n`. */
1221
- function modernCommentText(el) {
1222
- const txBody = firstChild(el, "p188:txBody");
1223
- if (!txBody) return "";
1224
- const paras = getElements(txBody, "a:p");
1225
- if (paras.length === 0) return txBody.textContent ?? "";
1226
- return paras.map((p) => p.textContent ?? "").join("\n");
1227
- }
1228
1176
  /**
1229
- * Decode one `p188:cm` (or `p188:reply`) element. Only the top-level comment
1230
- * carries a position and a reply list; replies pass `withThread: false` so they
1231
- * report `x`/`y` as `null` and never recurse (the schema forbids nested replies).
1177
+ * Wire a freshly-copied slide master into `presentation.xml`: add a
1178
+ * presentation→master relationship and a `p:sldMasterId` entry in
1179
+ * `p:sldMasterIdLst`. A master that is reachable only through the
1180
+ * slide→layout→master rel chain but absent from `p:sldMasterIdLst` is treated
1181
+ * as inactive by renderers, so its background/graphics never paint. No-op when
1182
+ * the master is already registered (shared across imports from one source).
1232
1183
  */
1233
- function parseModernComment(el, byId, withThread) {
1234
- const authorId = attr(el, "authorId");
1235
- const author = authorId !== null ? byId.get(authorId) ?? null : null;
1236
- const pos = withThread ? firstChild(el, "p188:pos") : null;
1237
- const replyLst = withThread ? firstChild(el, "p188:replyLst") : null;
1238
- return {
1239
- id: attr(el, "id"),
1240
- author: author ? author.name : null,
1241
- authorInitials: author ? author.initials : null,
1242
- authorId,
1243
- created: attr(el, "created"),
1244
- text: modernCommentText(el),
1245
- x: pos ? intValue(attr(pos, "x")) : null,
1246
- y: pos ? intValue(attr(pos, "y")) : null,
1247
- replies: replyLst ? getElements(replyLst, "p188:reply").map((r) => parseModernComment(r, byId, false)) : []
1248
- };
1184
+ function registerMaster(dest, masterPartName) {
1185
+ const presPart = dest.presentationPart;
1186
+ const presRels = presentationRels(dest);
1187
+ for (const rel of presRels.byType(SLIDE_MASTER_REL)) if (presRels.resolveTarget(rel.id) === masterPartName) return;
1188
+ const relId = presRels.add(SLIDE_MASTER_REL, relativePartName(presPart.partName, masterPartName)).id;
1189
+ const root = presPart.dom.documentElement;
1190
+ if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a master in");
1191
+ const lst = getOrAddChild(root, "p:sldMasterIdLst", [
1192
+ "p:notesMasterIdLst",
1193
+ "p:handoutMasterIdLst",
1194
+ "p:sldIdLst",
1195
+ "p:sldSz",
1196
+ "p:notesSz",
1197
+ "p:embeddedFontLst",
1198
+ "p:custShowLst",
1199
+ "p:photoAlbum",
1200
+ "p:custDataLst",
1201
+ "p:kinsoku",
1202
+ "p:defaultTextStyle",
1203
+ "p:modifyVerifier",
1204
+ "p:extLst"
1205
+ ]);
1206
+ const entry = createElement(presPart.dom, "p:sldMasterId");
1207
+ setAttr(entry, "id", String(nextMasterLayoutId(dest)));
1208
+ setAttr(entry, "r:id", relId);
1209
+ lst.appendChild(entry);
1210
+ presPart.markDirty();
1249
1211
  }
1250
1212
  /**
1251
- * Read the deck-wide modern comment authors from `ppt/authors.xml`, resolved via
1252
- * the presentation part's 2018 `authors` relationship. `[]` when the deck has no
1253
- * modern authors part.
1213
+ * Move the `p:sldMasterId` entries for `masterPartNames` to the front of
1214
+ * `p:sldMasterIdLst`, preserving their existing relative order, so the first of
1215
+ * them becomes the deck's `Designs(1)` — the theme PowerPoint's Design tab shows.
1216
+ * Reorders the id list only; relationships, ids, and all other parts are left as
1217
+ * they are. A no-op when the named masters already lead (idempotent re-call).
1254
1218
  */
1255
- function readModernCommentAuthors(opc, presentationPartName) {
1256
- const rels = opc.relationshipsFor(presentationPartName);
1257
- const rel = rels.byType(MODERN_AUTHORS_REL_TYPE)[0];
1258
- if (!rel) return [];
1259
- const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1260
- if (!root) return [];
1261
- return getElements(root, "p188:author").map((el) => ({
1262
- id: attr(el, "id"),
1263
- name: attr(el, "name") ?? "",
1264
- initials: attr(el, "initials") ?? "",
1265
- userId: attr(el, "userId"),
1266
- providerId: attr(el, "providerId")
1267
- }));
1219
+ function promoteMasters(dest, masterPartNames) {
1220
+ if (masterPartNames.length === 0) return;
1221
+ const presPart = dest.presentationPart;
1222
+ const root = presPart.dom.documentElement;
1223
+ const lst = root && firstChild(root, "p:sldMasterIdLst");
1224
+ if (!lst) return;
1225
+ const rels = presentationRels(dest);
1226
+ const promote = new Set(masterPartNames);
1227
+ const entries = getElements(lst, "p:sldMasterId");
1228
+ const isPromoted = (entry) => {
1229
+ const relId = attr(entry, "r:id");
1230
+ return relId ? promote.has(rels.resolveTarget(relId)) : false;
1231
+ };
1232
+ const desired = [...entries.filter(isPromoted), ...entries.filter((e) => !isPromoted(e))];
1233
+ if (desired.every((entry, i) => entry === entries[i])) return;
1234
+ for (const entry of desired) lst.appendChild(entry);
1235
+ presPart.markDirty();
1268
1236
  }
1269
1237
  /**
1270
- * Read one slide's modern comments from its `modernComment_*.xml` part (resolved
1271
- * via the slide's 2018 `comments` relationship), resolving each comment's and
1272
- * reply's `@authorId` GUID against `authors` (typically
1273
- * {@link readModernCommentAuthors}). Replies are nested, not flattened. `[]` when
1274
- * the slide has no modern comments part.
1238
+ * The next free id in the presentation-wide slide-master / slide-layout id space.
1239
+ *
1240
+ * `p:sldMasterId/@id` and every master's `p:sldLayoutId/@id` draw from ONE shared
1241
+ * id space (both ST_SlideMasterId/ST_SlideLayoutId, floor 2147483648) and must be
1242
+ * unique across the WHOLE presentation, not just within their own list. A duplicate
1243
+ * anywhere in this space makes PowerPoint report the file as corrupt on open
1244
+ * ("found a problem with content" / 0x80070570), even though LibreOffice silently
1245
+ * tolerates it — so an imported master AND each of its layouts must take an id past
1246
+ * the max of both the master-id list and every layout-id list. Recomputed per
1247
+ * allocation so ids appended earlier in the same import are counted.
1275
1248
  */
1276
- function readModernSlideComments(opc, slidePart, authors) {
1277
- const rels = opc.relationshipsFor(slidePart.partName);
1278
- const rel = rels.byType(MODERN_COMMENTS_REL_TYPE)[0];
1279
- if (!rel) return [];
1280
- const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1281
- if (!root) return [];
1282
- const byId = new Map(authors.map((a) => [a.id, a]));
1283
- return getElements(root, "p188:cm").map((cm) => parseModernComment(cm, byId, true));
1249
+ function nextMasterLayoutId(dest) {
1250
+ let max = 2147483647;
1251
+ const root = dest.presentationPart.dom.documentElement;
1252
+ const masterLst = root && firstChild(root, "p:sldMasterIdLst");
1253
+ if (masterLst) for (const entry of getElements(masterLst, "p:sldMasterId")) {
1254
+ const id = intValue(attr(entry, "id"));
1255
+ if (id !== null && id > max) max = id;
1256
+ }
1257
+ const presRels = presentationRels(dest);
1258
+ for (const rel of presRels.byType(SLIDE_MASTER_REL)) {
1259
+ const masterRoot = dest.opc.part(presRels.resolveTarget(rel.id))?.dom.documentElement;
1260
+ const layoutLst = masterRoot && firstChild(masterRoot, "p:sldLayoutIdLst");
1261
+ if (!layoutLst) continue;
1262
+ for (const entry of getElements(layoutLst, "p:sldLayoutId")) {
1263
+ const id = intValue(attr(entry, "id"));
1264
+ if (id !== null && id > max) max = id;
1265
+ }
1266
+ }
1267
+ return max + 1;
1268
+ }
1269
+ /** Empty a freshly-copied master's `p:sldLayoutIdLst`; copied layouts repopulate it. */
1270
+ function clearLayoutIdList(dest, masterPartName) {
1271
+ const masterPart = dest.opc.part(masterPartName);
1272
+ const root = masterPart?.dom.documentElement;
1273
+ const lst = root && firstChild(root, "p:sldLayoutIdLst");
1274
+ if (!masterPart || !lst) return;
1275
+ removeChildrenByQName(lst, ["p:sldLayoutId"]);
1276
+ masterPart.markDirty();
1284
1277
  }
1285
1278
  /**
1286
- * Report which comment schema a deck uses: `'modern'` when it carries any 2018
1287
- * `modernComment_*` part, else `'legacy'` when it carries any classic
1288
- * `commentN.xml` part, else `'none'`. Lets a consumer pick the right accessor
1289
- * ({@link readModernSlideComments} vs {@link readSlideComments}) without probing
1290
- * both.
1279
+ * Append a `p:sldLayoutId` entry for `layoutPartName` to `masterPartName`'s
1280
+ * `p:sldLayoutIdLst`, adding the master→layout relationship it points at. The
1281
+ * destination-side half of linking a copied layout into its copied master; the
1282
+ * caller resolves which master that is from the source-side rels.
1291
1283
  */
1292
- function commentSchema(opc) {
1284
+ function addLayoutToMaster(dest, masterPartName, layoutPartName) {
1285
+ const masterPart = dest.opc.part(masterPartName);
1286
+ const root = masterPart?.dom.documentElement;
1287
+ if (!masterPart || !root) return;
1288
+ const relId = dest.opc.relationshipsFor(masterPartName).add(SLIDE_LAYOUT_REL, relativePartName(masterPartName, layoutPartName)).id;
1289
+ const lst = getOrAddChild(root, "p:sldLayoutIdLst", [
1290
+ "p:transition",
1291
+ "p:timing",
1292
+ "p:hf",
1293
+ "p:txStyles",
1294
+ "p:extLst"
1295
+ ]);
1296
+ const entry = createElement(masterPart.dom, "p:sldLayoutId");
1297
+ setAttr(entry, "id", String(nextMasterLayoutId(dest)));
1298
+ setAttr(entry, "r:id", relId);
1299
+ lst.appendChild(entry);
1300
+ masterPart.markDirty();
1301
+ }
1302
+ //#endregion
1303
+ //#region src/read/api/ops/page-owned.ts
1304
+ /** Relationship types whose target two copies of one page may point at together. */
1305
+ const SHARED_BY_PAGE_COPIES = /* @__PURE__ */ new Set([
1306
+ SLIDE_LAYOUT_REL,
1307
+ SLIDE_MASTER_REL,
1308
+ NOTES_MASTER_REL,
1309
+ THEME_REL,
1310
+ OFFICE_REL + "themeOverride",
1311
+ OFFICE_REL + "handoutMaster",
1312
+ OFFICE_REL + "presProps",
1313
+ OFFICE_REL + "viewProps",
1314
+ OFFICE_REL + "tableStyles",
1315
+ OFFICE_REL + "commentAuthors",
1316
+ "http://schemas.microsoft.com/office/2018/10/relationships/authors",
1317
+ IMAGE_REL,
1318
+ OFFICE_REL + "audio",
1319
+ OFFICE_REL + "video",
1320
+ "http://schemas.microsoft.com/office/2007/relationships/media",
1321
+ "http://schemas.microsoft.com/office/2017/06/relationships/model3d",
1322
+ OFFICE_REL + "font",
1323
+ SLIDE_REL,
1324
+ OFFICE_REL + "hyperlink"
1325
+ ]);
1326
+ /**
1327
+ * Whether a page copy may point at this relationship's target rather than taking
1328
+ * a copy of it. Everything not listed is owned by the page and copied — see the
1329
+ * module comment for why that is the safe default.
1330
+ */
1331
+ function isSharedByPageCopies(relType) {
1332
+ return SHARED_BY_PAGE_COPIES.has(relType);
1333
+ }
1334
+ /**
1335
+ * Give a just-cloned page its own copy of every part the source page owned,
1336
+ * within one package. The clone's relationships start as a byte copy of the
1337
+ * source page's, so this walks them and repoints the owned ones at fresh parts;
1338
+ * shared targets are left alone.
1339
+ *
1340
+ * The subtree under each owned part is copied the same way, so a chart's workbook
1341
+ * and user-shapes drawing come along while the image inside that drawing stays
1342
+ * shared. A relationship *back* to the source page — a notes slide names the
1343
+ * slide it annotates — is repointed at the clone, which is what makes the copied
1344
+ * notes belong to it.
1345
+ *
1346
+ * A dangling relationship is left dangling rather than made to throw: cloning a
1347
+ * damaged deck is not this function's problem to discover.
1348
+ *
1349
+ * @param opc the package both pages live in
1350
+ * @param sourcePartName partname of the page that was cloned
1351
+ * @param clonePartName partname of the clone, whose rels are still the source's
1352
+ */
1353
+ function duplicateOwnedTargets(opc, sourcePartName, clonePartName) {
1354
+ const copies = /* @__PURE__ */ new Map([[sourcePartName, clonePartName]]);
1355
+ const rels = opc.relationshipsFor(clonePartName);
1356
+ for (const rel of Array.from(rels)) {
1357
+ if (rel.targetMode === "External" || isSharedByPageCopies(rel.type)) continue;
1358
+ const target = rels.resolveTarget(rel.id);
1359
+ const fresh = duplicateSubtree(opc, target, copies);
1360
+ if (fresh === target) continue;
1361
+ rels.remove(rel.id);
1362
+ rels.addWithId(rel.id, rel.type, relativePartName(clonePartName, fresh));
1363
+ }
1364
+ }
1365
+ /**
1366
+ * Copy `partName` and, recursively, every part it owns, into fresh partnames.
1367
+ * `copies` dedupes within the one page copy, so a part two of the page's
1368
+ * relationships reach is copied once here even though the next page copy gets its
1369
+ * own. Returns the copy's partname, or `partName` unchanged when there is no such
1370
+ * part to copy.
1371
+ */
1372
+ function duplicateSubtree(opc, partName, copies) {
1373
+ const already = copies.get(partName);
1374
+ if (already !== void 0) return already;
1375
+ const part = opc.part(partName);
1376
+ if (!part) return partName;
1377
+ const fresh = opc.reservePartNameLike(partName);
1378
+ opc.addPart(fresh, part.contentType, part.bytes);
1379
+ copies.set(partName, fresh);
1380
+ const sourceRels = opc.relationshipsFor(partName);
1381
+ const freshRels = opc.relationshipsFor(fresh);
1382
+ for (const rel of sourceRels) {
1383
+ if (rel.targetMode === "External") {
1384
+ freshRels.addWithId(rel.id, rel.type, rel.target, "External");
1385
+ continue;
1386
+ }
1387
+ const target = sourceRels.resolveTarget(rel.id);
1388
+ const newTarget = isSharedByPageCopies(rel.type) ? copies.get(target) ?? target : duplicateSubtree(opc, target, copies);
1389
+ freshRels.addWithId(rel.id, rel.type, relativePartName(fresh, newTarget));
1390
+ }
1391
+ return fresh;
1392
+ }
1393
+ //#endregion
1394
+ //#region src/read/api/ops/part-copy.ts
1395
+ /** Open an ownership scope for one page copy. */
1396
+ function newOwnedScope() {
1397
+ return /* @__PURE__ */ new Map();
1398
+ }
1399
+ /**
1400
+ * Copy one slide page across as a part of its own, deduping everything under it
1401
+ * but never the page itself. This is the `theme: 'copy'` arm of
1402
+ * {@link Presentation.importSlide}.
1403
+ *
1404
+ * The page is the one part an import is *not* allowed to share. `copyPart`'s
1405
+ * registry idempotence is right for a theme, master, layout or image, where a
1406
+ * second copy is waste; applied to the slide it made a repeated import of the
1407
+ * same source page return the first copy's partname, and the caller then wired a
1408
+ * second `p:sldId` to a part that already had one — a package PowerPoint refuses
1409
+ * to open (0x80070570), with nothing in the read model to show for it. So the
1410
+ * page goes into the selection plan `copyPart` already honours for a batch, which
1411
+ * re-materializes exactly the named pages and leaves the dedup of their
1412
+ * dependencies alone.
1413
+ *
1414
+ * @param ctx the open import out of the source package
1415
+ * @param sourcePartName partname of the source slide to bring across
1416
+ * @return partname of the new slide part in `ctx.dest`
1417
+ */
1418
+ function copySlidePart(ctx, sourcePartName) {
1419
+ const destinations = /* @__PURE__ */ new Map([[sourcePartName, ctx.dest.opc.reservePartNameLike(sourcePartName)]]);
1420
+ return copyPart({
1421
+ ...ctx,
1422
+ selection: { destinations }
1423
+ }, sourcePartName);
1424
+ }
1425
+ /**
1426
+ * Copy `sourcePartName` (and, recursively, every internal part it references)
1427
+ * from `ctx.source` into `ctx.dest`, returning the new partname. Idempotent
1428
+ * per source package via the copy registry. Relationship ids are preserved so
1429
+ * the copied part body's `r:id`/`r:embed` references stay valid; targets are
1430
+ * rewritten to the freshly-allocated partnames. Notes relationships are
1431
+ * dropped. A copied `slideMaster` does not drag in all its sibling layouts —
1432
+ * each imported `slideLayout` wires itself into the master instead (see
1433
+ * {@link linkLayoutIntoMaster}).
1434
+ *
1435
+ * With `ctx.selection`, partnames for the selected slides were already reserved
1436
+ * by the caller ({@link Presentation.importSlides}); this traversal wires their
1437
+ * relationships to each other instead of re-copying them. The batch's rule that
1438
+ * a `slide → slide` link may not leave the selection is enforced ahead of the
1439
+ * copy by {@link checkSelectionCopyable}, which also proves every part this
1440
+ * traversal will reach exists — so once copying starts there is nothing left to
1441
+ * throw, and a rejected batch never leaves a half-copied deck behind.
1442
+ *
1443
+ * Idempotence stops at the page's own parts. Reaching a page opens an
1444
+ * {@link OwnedScope}, and everything under it that {@link isSharedByPageCopies}
1445
+ * does not clear for sharing — a chart, a diagram, an OLE embedding, each with
1446
+ * its own subtree — is copied into that scope instead of the registry, so the
1447
+ * next copy of the page gets parts of its own. Pass `owned` to open the scope
1448
+ * from outside, as the rebinding import paths do for the page they build
1449
+ * themselves.
1450
+ */
1451
+ function copyPart(ctx, sourcePartName, owned) {
1452
+ if (owned) {
1453
+ const alreadyOwned = owned.get(sourcePartName);
1454
+ if (alreadyOwned !== void 0) return alreadyOwned;
1455
+ } else {
1456
+ const existing = ctx.registry.get(sourcePartName);
1457
+ const selectedDest = ctx.selection?.destinations.get(sourcePartName);
1458
+ if (existing && (selectedDest === void 0 || existing === selectedDest)) return existing;
1459
+ }
1460
+ const sourcePart = ctx.source.part(sourcePartName);
1461
+ if (!sourcePart) throw new PackageReadError("package/part-missing", `importSlide: source package has no part ${sourcePartName}`);
1462
+ const newPartName = owned ? ctx.dest.opc.reservePartNameLike(sourcePartName) : ctx.selection?.destinations.get(sourcePartName) ?? ctx.dest.opc.reservePartNameLike(sourcePartName);
1463
+ if (!ctx.dest.opc.part(newPartName)) ctx.dest.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
1464
+ if (owned) owned.set(sourcePartName, newPartName);
1465
+ else ctx.registry.set(sourcePartName, newPartName);
1466
+ const scope = owned ?? (ctx.selection?.destinations.has(sourcePartName) ? newOwnedScope() : void 0);
1467
+ const isMaster = sourcePart.contentType === SLIDE_MASTER_CONTENT_TYPE;
1468
+ const sourceRels = ctx.source.relationshipsFor(sourcePartName);
1469
+ const targetRels = ctx.dest.opc.relationshipsFor(newPartName);
1470
+ for (const rel of sourceRels) {
1471
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
1472
+ if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
1473
+ if (rel.targetMode === "External") {
1474
+ targetRels.addWithId(rel.id, rel.type, rel.target, "External");
1475
+ continue;
1476
+ }
1477
+ const newTargetPartName = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : scope);
1478
+ targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTargetPartName));
1479
+ }
1480
+ if (isMaster) {
1481
+ clearLayoutIdList(ctx.dest, newPartName);
1482
+ registerMaster(ctx.dest, newPartName);
1483
+ }
1484
+ if (sourcePart.contentType === "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml") linkLayoutIntoMaster(ctx, sourceRels, newPartName);
1485
+ return newPartName;
1486
+ }
1487
+ const NO_NOTES = {
1488
+ pages: /* @__PURE__ */ new Set(),
1489
+ copyMaster: false
1490
+ };
1491
+ /**
1492
+ * Dry-run {@link copyPart} over one source's batch selection, reading only the
1493
+ * *source* package: walk the graph the copy will walk, under the same
1494
+ * relationship-skipping rules, and throw what the copy would throw — a source
1495
+ * part that is missing, XML that will not parse, or a `slide → slide` link that
1496
+ * escapes the selection.
1497
+ *
1498
+ * This is what makes a rejected batch leave the destination byte-identical.
1499
+ * `importSlides` reserves partnames and mutates the destination only after this
1500
+ * passes, at which point the copy has no reachable failure left; a check that
1501
+ * lived inside the traversal instead would fire with parts already added, a
1502
+ * master already registered in `presentation.xml`, and no way back.
1503
+ *
1504
+ * Keep this in step with `copyPart`: the two must skip the same relationships
1505
+ * and stop at the same registry hits, or the guarantee is only as good as the
1506
+ * drift between them. With `notes` the same obligation extends to `carryNotes`,
1507
+ * which runs after the copy and is the batch's other way to reach a source part.
1508
+ *
1509
+ * @param source the package the pages are coming from
1510
+ * @param registry the copy registry for that source (parts already in `dest`)
1511
+ * @param selected source partnames of the pages this batch selected
1512
+ * @param notes the pages of `selected` whose notes travel too; none by default
1513
+ */
1514
+ function checkSelectionCopyable(source, registry, selected, notes = NO_NOTES) {
1515
+ const visited = /* @__PURE__ */ new Set();
1516
+ const walk = (partName) => {
1517
+ if (visited.has(partName)) return;
1518
+ if (registry.has(partName) && !selected.has(partName)) return;
1519
+ visited.add(partName);
1520
+ const part = source.part(partName);
1521
+ if (!part) throw new PackageReadError("package/part-missing", `importSlides: source package has no part ${partName}`);
1522
+ const isMaster = part.contentType === SLIDE_MASTER_CONTENT_TYPE;
1523
+ const isNotesSlide = part.contentType === NOTES_SLIDE_CONTENT_TYPE;
1524
+ if (isMaster || part.contentType === "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml") part.dom;
1525
+ const rels = source.relationshipsFor(partName);
1526
+ for (const rel of rels) {
1527
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") {
1528
+ if (notes.pages.has(partName)) walk(rels.resolveTarget(rel.id));
1529
+ continue;
1530
+ }
1531
+ if (isNotesSlide) {
1532
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") continue;
1533
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" && !notes.copyMaster) continue;
1534
+ }
1535
+ if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
1536
+ if (rel.targetMode === "External") continue;
1537
+ const targetPartName = rels.resolveTarget(rel.id);
1538
+ 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`);
1539
+ walk(targetPartName);
1540
+ }
1541
+ };
1542
+ for (const partName of selected) walk(partName);
1543
+ }
1544
+ /**
1545
+ * Wire a just-copied layout into its (already-copied) master. Resolves which
1546
+ * destination master that is by running the layout's *source* master rel through
1547
+ * the copy registry, then hands the destination-side wiring to
1548
+ * {@link addLayoutToMaster}. Called once per copied layout, so the master
1549
+ * accumulates exactly the imported layouts.
1550
+ */
1551
+ function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
1552
+ const masterRel = layoutSourceRels.byType(SLIDE_MASTER_REL)[0];
1553
+ if (!masterRel) return;
1554
+ const masterPartName = ctx.registry.get(layoutSourceRels.resolveTarget(masterRel.id));
1555
+ if (!masterPartName) return;
1556
+ addLayoutToMaster(ctx.dest, masterPartName, layoutPartName);
1557
+ }
1558
+ //#endregion
1559
+ //#region src/read/api/ops/notes-master.ts
1560
+ /**
1561
+ * Carrying speaker notes and the deck's single notes master across an import or an append.
1562
+ *
1563
+ * A `notesSlide` is not self-contained: it back-references the slide it annotates and forward-
1564
+ * references the deck's notes master. `CT_NotesMasterIdList` allows at most one master per
1565
+ * presentation, so a deck that already has one keeps it — the destination's notes styling wins —
1566
+ * and a source master is copied only into a deck that has none. That single rule is why both the
1567
+ * `Presentation`-to-`Presentation` path and the generator-XML path funnel through
1568
+ * `registerNotesMaster`: they differ only in how the *part* comes into being.
1569
+ */
1570
+ const textEncoder$3 = new TextEncoder();
1571
+ /**
1572
+ * Carry the source slide's speaker notes onto the just-imported slide (the
1573
+ * `importNotes` option). The slide copy itself dropped the `notesSlide` rel, so
1574
+ * this copies the source `notesSlide` part into a fresh partname, wires a
1575
+ * `slide → notesSlide` rel on the new slide, and rebuilds the copied notesSlide's
1576
+ * own relationships:
1577
+ *
1578
+ * - its `slide` back-rel is repointed at the new slide (`newSlidePartName`) — the
1579
+ * source slide is *not* copied (that would be circular);
1580
+ * - its `notesMaster` rel is resolved through {@link ensureNotesMaster}, which
1581
+ * reuses this deck's notesMaster when it has one and copies the source's only
1582
+ * when it has none (a deck may have at most one notesMaster);
1583
+ * - any other internal target (media, etc.) is copied via {@link copyPart}, under an
1584
+ * ownership scope so a second copy of the page gets notes parts of its own.
1585
+ *
1586
+ * No-op when the source slide has no notes. Content-type registration for the
1587
+ * copied parts is handled by `addPart`/{@link copyPart}.
1588
+ */
1589
+ function carryNotes(dest, source, ctx, sourceSlidePartName, newSlidePartName) {
1590
+ const sourceSlideRels = source.opc.relationshipsFor(sourceSlidePartName);
1591
+ const notesRel = sourceSlideRels.byType(NOTES_SLIDE_REL)[0];
1592
+ if (!notesRel) return;
1593
+ const sourceNotesPartName = sourceSlideRels.resolveTarget(notesRel.id);
1594
+ const sourceNotesPart = source.opc.part(sourceNotesPartName);
1595
+ if (!sourceNotesPart) return;
1596
+ const newNotesPartName = dest.opc.reservePartNameLike(sourceNotesPartName);
1597
+ dest.opc.addPart(newNotesPartName, sourceNotesPart.contentType, sourceNotesPart.bytes);
1598
+ dest.opc.relationshipsFor(newSlidePartName).add(NOTES_SLIDE_REL, relativePartName(newSlidePartName, newNotesPartName));
1599
+ const owned = newOwnedScope();
1600
+ owned.set(sourceNotesPartName, newNotesPartName);
1601
+ const notesSourceRels = source.opc.relationshipsFor(sourceNotesPartName);
1602
+ const notesTargetRels = dest.opc.relationshipsFor(newNotesPartName);
1603
+ for (const rel of notesSourceRels) {
1604
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") {
1605
+ notesTargetRels.addWithId(rel.id, SLIDE_REL, relativePartName(newNotesPartName, newSlidePartName));
1606
+ continue;
1607
+ }
1608
+ if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster") {
1609
+ const notesMaster = ensureNotesMaster(dest, ctx, notesSourceRels.resolveTarget(rel.id));
1610
+ notesTargetRels.addWithId(rel.id, NOTES_MASTER_REL, relativePartName(newNotesPartName, notesMaster));
1611
+ continue;
1612
+ }
1613
+ if (rel.targetMode === "External") {
1614
+ notesTargetRels.addWithId(rel.id, rel.type, rel.target, "External");
1615
+ continue;
1616
+ }
1617
+ const newTarget = copyPart(ctx, notesSourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
1618
+ notesTargetRels.addWithId(rel.id, rel.type, relativePartName(newNotesPartName, newTarget));
1619
+ }
1620
+ }
1621
+ /**
1622
+ * Resolve the notesMaster an imported `notesSlide` should bind to, honouring the
1623
+ * single-notesMaster-per-presentation rule (`p:notesMasterIdLst` holds 0..1
1624
+ * `p:notesMasterId`). If this deck already has a notesMaster it is reused and the
1625
+ * source's is *not* copied (the destination's notes styling wins); otherwise the
1626
+ * source notesMaster (and, via {@link copyPart}, its theme) is copied and
1627
+ * registered in `presentation.xml`. Returns the destination notesMaster partname.
1628
+ */
1629
+ function ensureNotesMaster(dest, ctx, sourceNotesMasterPartName) {
1630
+ const presRels = presentationRels(dest);
1631
+ const existing = presRels.byType(NOTES_MASTER_REL)[0];
1632
+ if (existing) return presRels.resolveTarget(existing.id);
1633
+ return registerNotesMaster(dest, copyPart(ctx, sourceNotesMasterPartName));
1634
+ }
1635
+ /**
1636
+ * Wire an already-added notesMaster part into `presentation.xml`: a `notesMaster`
1637
+ * relationship plus the single `p:notesMasterId` entry that `CT_NotesMasterIdList`
1638
+ * allows. Returns the partname, so callers can use it as a rel target.
1639
+ *
1640
+ * Split out of {@link ensureNotesMaster} because the two ways a notesMaster arrives
1641
+ * — copied from another `Presentation`, or authored by a generator and injected by
1642
+ * {@link Presentation.appendSlides} — differ only in how the *part* is created, not in how it is
1643
+ * registered.
1644
+ */
1645
+ function registerNotesMaster(dest, notesMasterPartName) {
1646
+ const presPart = dest.presentationPart;
1647
+ const relId = presentationRels(dest).add(NOTES_MASTER_REL, relativePartName(presPart.partName, notesMasterPartName)).id;
1648
+ const root = presPart.dom.documentElement;
1649
+ if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a notes master in");
1650
+ const lst = getOrAddChild(root, "p:notesMasterIdLst", [
1651
+ "p:handoutMasterIdLst",
1652
+ "p:sldIdLst",
1653
+ "p:sldSz",
1654
+ "p:notesSz",
1655
+ "p:embeddedFontLst",
1656
+ "p:custShowLst",
1657
+ "p:photoAlbum",
1658
+ "p:custDataLst",
1659
+ "p:kinsoku",
1660
+ "p:defaultTextStyle",
1661
+ "p:modifyVerifier",
1662
+ "p:extLst"
1663
+ ]);
1664
+ removeChildrenByQName(lst, ["p:notesMasterId"]);
1665
+ const entry = createElement(presPart.dom, "p:notesMasterId");
1666
+ setAttr(entry, "r:id", relId);
1667
+ lst.appendChild(entry);
1668
+ presPart.markDirty();
1669
+ return notesMasterPartName;
1670
+ }
1671
+ /**
1672
+ * Resolve the notesMaster an *appended* slide's notes should bind to. Same
1673
+ * single-notesMaster rule as {@link ensureNotesMaster}: this deck's own wins when it
1674
+ * has one, so the destination's notes styling is preserved and `master.xml` is
1675
+ * discarded. Otherwise the generator's notes master is installed, together with the
1676
+ * theme its `.rels` requires (the normal write path emits that as `theme2.xml`).
1677
+ */
1678
+ function ensureNotesMasterFromXml(dest, master) {
1679
+ const presRels = presentationRels(dest);
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 presRels = presentationRels(dest);
1716
+ const existing = presRels.byType(NOTES_MASTER_REL)[0];
1717
+ if (existing) return presRels.resolveTarget(existing.id);
1718
+ const themePartName = resolveSlideThemeParts(dest.opc, slidePartName).themePartName;
1719
+ const themePart = themePartName ? dest.opc.part(themePartName) : void 0;
1720
+ if (!themePart) throw new PackageReadError("package/part-missing", `addNotes: no theme reachable from ${slidePartName} to bind a new notes master to`);
1721
+ const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
1722
+ dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$3.encode(makeXmlNotesMaster()));
1723
+ const notesThemePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
1724
+ dest.opc.addPart(notesThemePartName, themePart.contentType, themePart.bytes);
1725
+ dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, notesThemePartName));
1726
+ return registerNotesMaster(dest, masterPartName);
1727
+ }
1728
+ //#endregion
1729
+ //#region src/read/api/ops/notes-author.ts
1730
+ /**
1731
+ * Authoring speaker notes onto a slide of a *loaded* deck ({@link Slide.addNotes}).
1732
+ *
1733
+ * The third way a notes slide comes into being, after the two in `notes-master.ts`
1734
+ * (carried from another `Presentation` by `importSlide({ importNotes: true })`, or
1735
+ * emitted by the generator and spliced on by `appendSlides`). Those two both *move*
1736
+ * an existing part; this one has no source part to move, so it builds one.
1737
+ *
1738
+ * The part it builds is the generator's, not a second design: the fixed three-
1739
+ * placeholder frame comes from {@link makeXmlNotesSlideSkeleton}, and the body
1740
+ * paragraphs carry the same elements and attributes the write path emits for a plain
1741
+ * `pptx.addSlide().addNotes(text)`. `test/read/add-notes.test.js` asserts that
1742
+ * equivalence, so the two cannot drift apart unnoticed — the same guarantee
1743
+ * `src/ooxml/` gives the constants both halves share.
1744
+ *
1745
+ * The one difference is empty-element *spelling*: the write path emits `a:rPr` as an
1746
+ * open/close pair and this emits it self-closed, because a part the read model
1747
+ * authors is reserialized through `read/oxml/dom.ts`, which self-closes every empty
1748
+ * element. Writing the open/close form here would not survive the first save, so the
1749
+ * form that does is written in the first place and the test normalizes the spelling
1750
+ * rather than pretending the bytes match.
1751
+ *
1752
+ * Notes text is split on `\n` into paragraphs, matching the write-side `addNotes`;
1753
+ * runs carry no formatting of their own. Per-run formatting, hyperlinks, and
1754
+ * multi-run paragraphs are reached afterwards through `slide.notesTextFrame`, which
1755
+ * is the read model's normal text-editing surface.
1756
+ */
1757
+ const textEncoder$2 = new TextEncoder();
1758
+ /**
1759
+ * The `a:p` children of a notes body, one per `\n`-separated line: the write path's
1760
+ * output for an unformatted note, modulo the empty-element spelling described above.
1761
+ * An empty line yields a paragraph with no run, exactly as `genXmlNotesParagraphs`
1762
+ * does.
1763
+ * @param {string} text - the note, `\n` separating paragraphs
1764
+ * @return {string} concatenated `a:p` XML
1765
+ */
1766
+ function notesParagraphsXml(text) {
1767
+ 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", {
1768
+ lang: "en-US",
1769
+ dirty: 0
1770
+ })), raw(el("a:t", null, line))])), raw(voidEl("a:endParaRPr", {
1771
+ lang: "en-US",
1772
+ dirty: 0
1773
+ }))])).join("");
1774
+ }
1775
+ /**
1776
+ * Replace a notes body's paragraphs with `text`, in the body's own document.
1777
+ *
1778
+ * The path taken when the slide *already* has a notes part — an imported one, or one
1779
+ * an earlier `addNotes` wrote. Only the `a:p` children are replaced, so the part's
1780
+ * geometry, its `a:bodyPr`/`a:lstStyle`, and its other two placeholders survive. The
1781
+ * body's hyperlink relationships are deliberately *not* pruned: a caller replacing
1782
+ * text has not asked for its rels to be rewritten, and a now-unreferenced notes
1783
+ * hyperlink rel is inert (nothing resolves it on save).
1784
+ * @param {Element} txBody - the body placeholder's `p:txBody`
1785
+ * @param {string} text - the note, `\n` separating paragraphs
1786
+ */
1787
+ function replaceNotesParagraphs(txBody, text) {
1788
+ const doc = txBody.ownerDocument;
1789
+ if (!doc) throw new InternalError("oxml/node-has-no-document", "Notes body has no owner document");
1790
+ removeChildrenByQName(txBody, ["a:p"]);
1791
+ const wrapper = parseXml(`<w xmlns:a="${OOXML_NS.a}">${notesParagraphsXml(text)}</w>`).documentElement;
1792
+ if (!wrapper) throw new InternalError("oxml/node-has-no-document", "Authored notes paragraphs did not parse to a root");
1793
+ for (const paragraph of Array.from(wrapper.childNodes)) txBody.appendChild(doc.importNode(paragraph, true));
1794
+ }
1795
+ /**
1796
+ * The `p:txBody` of a notes slide's body placeholder (`p:ph type="body"`), or `null`
1797
+ * when the part carries none. A raw walk rather than a `NotesSlide`/`TextFrame` hop,
1798
+ * because the caller needs the element itself to rewrite and the modeled surface
1799
+ * would resolve a notes theme context it has no use for.
1800
+ * @param {Element | null} root - the notes slide's `p:notes` root
1801
+ * @return {Element | null} the body placeholder's text body
1802
+ */
1803
+ function notesBodyTxBody(root) {
1804
+ const spTree = spTreeOf(root);
1805
+ if (!spTree) return null;
1806
+ for (const sp of getElements(spTree, "p:sp")) {
1807
+ const nvPr = nvPrOf(sp);
1808
+ const ph = nvPr && firstChild(nvPr, "p:ph");
1809
+ if (ph && attr(ph, "type") === "body") return firstChild(sp, "p:txBody");
1810
+ }
1811
+ return null;
1812
+ }
1813
+ /**
1814
+ * Give `slide` the speaker notes `text`, creating its notes slide part when it has
1815
+ * none, and return the notes part's name.
1816
+ *
1817
+ * When the slide already has a notes part, only the body paragraphs are rewritten
1818
+ * (see {@link replaceNotesParagraphs}). When it has none, the part is built and
1819
+ * wired: `slide → notesSlide`, and on the new part the two relationships a notes
1820
+ * slide always carries — `notesMaster` as rId1 and a `slide` back-reference as rId2,
1821
+ * the ids and order the write path reserves.
1822
+ *
1823
+ * The notes master is resolved through {@link ensureNotesMasterForAuthoring}: this
1824
+ * deck's own when it has one, otherwise a fresh one bound to a clone of the deck's
1825
+ * theme. That is the single-notesMaster-per-presentation rule the import and append
1826
+ * paths already follow, so the three cannot produce a second master between them.
1827
+ * @param {Slide} slide - the slide to annotate
1828
+ * @param {string} text - the note, `\n` separating paragraphs
1829
+ * @return {string} the notes slide's partname
1830
+ */
1831
+ function authorNotes(slide, text) {
1832
+ const opc = slide.presentation.opc;
1833
+ const existingRel = slide.relationships.byType(NOTES_SLIDE_REL)[0];
1834
+ if (existingRel) {
1835
+ const partName = slide.relationships.resolveTarget(existingRel.id);
1836
+ const part = opc.part(partName);
1837
+ if (!part) throw new PackageReadError("package/part-missing", `addNotes: slide ${slide.partName} has a notesSlide rel to a missing part ${partName}`);
1838
+ const txBody = notesBodyTxBody(part.dom.documentElement);
1839
+ if (!txBody) throw new PackageReadError("package/part-has-no-root", `addNotes: notes slide ${partName} has no body placeholder to write into`);
1840
+ replaceNotesParagraphs(txBody, text);
1841
+ part.markDirty();
1842
+ return partName;
1843
+ }
1844
+ const slideNum = slide.index + 1;
1845
+ const partName = opc.reservePartNameLike("/ppt/notesSlides/notesSlide1.xml");
1846
+ opc.addPart(partName, NOTES_SLIDE_CONTENT_TYPE, textEncoder$2.encode(makeXmlNotesSlideSkeleton(notesParagraphsXml(text), slideNum)));
1847
+ slide.relationships.add(NOTES_SLIDE_REL, relativePartName(slide.partName, partName));
1848
+ const notesRels = opc.relationshipsFor(partName);
1849
+ const masterPartName = ensureNotesMasterForAuthoring(slide.presentation, slide.partName);
1850
+ notesRels.addWithId("rId1", NOTES_MASTER_REL, relativePartName(partName, masterPartName));
1851
+ notesRels.addWithId("rId2", SLIDE_REL, relativePartName(partName, slide.partName));
1852
+ return partName;
1853
+ }
1854
+ //#endregion
1855
+ //#region src/read/api/comments.ts
1856
+ /** The slide → comments-part relationship type (legacy comments). */
1857
+ const COMMENTS_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
1858
+ /** The presentation → commentAuthors-part relationship type. */
1859
+ const COMMENT_AUTHORS_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors";
1860
+ /** Content type of a legacy per-slide comments part (`ppt/comments/commentN.xml`). */
1861
+ const LEGACY_COMMENTS_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.comments+xml";
1862
+ /** The slide → modern-comments-part relationship type (2018 schema). */
1863
+ const MODERN_COMMENTS_REL_TYPE = "http://schemas.microsoft.com/office/2018/10/relationships/comments";
1864
+ /** The presentation → modern-authors-part relationship type (2018 schema). */
1865
+ const MODERN_AUTHORS_REL_TYPE = "http://schemas.microsoft.com/office/2018/10/relationships/authors";
1866
+ /** Content type of the modern per-slide comments part (`ppt/comments/modernComment_*.xml`). */
1867
+ const MODERN_COMMENTS_CONTENT_TYPE = "application/vnd.ms-powerpoint.comments+xml";
1868
+ /** Parse a `p:cmAuthorLst` root element into the author registry. */
1869
+ function parseCommentAuthors(root) {
1870
+ if (!root) return [];
1871
+ return getElements(root, "p:cmAuthor").map((el) => ({
1872
+ id: intValue(attr(el, "id")),
1873
+ name: attr(el, "name") ?? "",
1874
+ initials: attr(el, "initials") ?? "",
1875
+ lastIdx: intValue(attr(el, "lastIdx")),
1876
+ clrIdx: intValue(attr(el, "clrIdx"))
1877
+ }));
1878
+ }
1879
+ /**
1880
+ * Read the deck-wide comment authors from `ppt/commentAuthors.xml`, resolved via the
1881
+ * presentation part's `commentAuthors` relationship. `[]` when the deck has no comments.
1882
+ */
1883
+ function readCommentAuthors(opc, presentationPartName) {
1884
+ const rels = opc.relationshipsFor(presentationPartName);
1885
+ const rel = rels.byType(COMMENT_AUTHORS_REL_TYPE)[0];
1886
+ if (!rel) return [];
1887
+ return parseCommentAuthors(opc.part(rels.resolveTarget(rel.id))?.dom.documentElement ?? null);
1888
+ }
1889
+ /**
1890
+ * Read one slide's legacy comments from its `comments/commentN.xml` part (resolved
1891
+ * via the slide's `comments` relationship), resolving each comment's `@authorId`
1892
+ * against `authors` (typically {@link readCommentAuthors}). `[]` when the slide has
1893
+ * no comments part.
1894
+ */
1895
+ function readSlideComments(opc, slidePart, authors) {
1896
+ const rels = opc.relationshipsFor(slidePart.partName);
1897
+ const rel = rels.byType(COMMENTS_REL_TYPE)[0];
1898
+ if (!rel) return [];
1899
+ const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1900
+ if (!root) return [];
1901
+ const byId = new Map(authors.map((a) => [a.id, a]));
1902
+ return getElements(root, "p:cm").map((cm) => {
1903
+ const authorId = intValue(attr(cm, "authorId"));
1904
+ const author = authorId !== null ? byId.get(authorId) ?? null : null;
1905
+ const pos = firstChild(cm, "p:pos");
1906
+ const textEl = firstChild(cm, "p:text");
1907
+ return {
1908
+ author: author ? author.name : null,
1909
+ authorInitials: author ? author.initials : null,
1910
+ authorId,
1911
+ idx: intValue(attr(cm, "idx")),
1912
+ text: textEl?.textContent ?? "",
1913
+ x: pos ? intValue(attr(pos, "x")) : null,
1914
+ y: pos ? intValue(attr(pos, "y")) : null,
1915
+ date: attr(cm, "dt") ?? null
1916
+ };
1917
+ });
1918
+ }
1919
+ /** Concatenate a `p188:txBody`'s paragraphs into plain text, joining paragraphs with `\n`. */
1920
+ function modernCommentText(el) {
1921
+ const txBody = firstChild(el, "p188:txBody");
1922
+ if (!txBody) return "";
1923
+ const paras = getElements(txBody, "a:p");
1924
+ if (paras.length === 0) return txBody.textContent ?? "";
1925
+ return paras.map((p) => p.textContent ?? "").join("\n");
1926
+ }
1927
+ /**
1928
+ * Decode one `p188:cm` (or `p188:reply`) element. Only the top-level comment
1929
+ * carries a position and a reply list; replies pass `withThread: false` so they
1930
+ * report `x`/`y` as `null` and never recurse (the schema forbids nested replies).
1931
+ */
1932
+ function parseModernComment(el, byId, withThread) {
1933
+ const authorId = attr(el, "authorId");
1934
+ const author = authorId !== null ? byId.get(authorId) ?? null : null;
1935
+ const pos = withThread ? firstChild(el, "p188:pos") : null;
1936
+ const replyLst = withThread ? firstChild(el, "p188:replyLst") : null;
1937
+ return {
1938
+ id: attr(el, "id"),
1939
+ author: author ? author.name : null,
1940
+ authorInitials: author ? author.initials : null,
1941
+ authorId,
1942
+ created: attr(el, "created"),
1943
+ text: modernCommentText(el),
1944
+ x: pos ? intValue(attr(pos, "x")) : null,
1945
+ y: pos ? intValue(attr(pos, "y")) : null,
1946
+ replies: replyLst ? getElements(replyLst, "p188:reply").map((r) => parseModernComment(r, byId, false)) : []
1947
+ };
1948
+ }
1949
+ /**
1950
+ * Read the deck-wide modern comment authors from `ppt/authors.xml`, resolved via
1951
+ * the presentation part's 2018 `authors` relationship. `[]` when the deck has no
1952
+ * modern authors part.
1953
+ */
1954
+ function readModernCommentAuthors(opc, presentationPartName) {
1955
+ const rels = opc.relationshipsFor(presentationPartName);
1956
+ const rel = rels.byType(MODERN_AUTHORS_REL_TYPE)[0];
1957
+ if (!rel) return [];
1958
+ const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1959
+ if (!root) return [];
1960
+ return getElements(root, "p188:author").map((el) => ({
1961
+ id: attr(el, "id"),
1962
+ name: attr(el, "name") ?? "",
1963
+ initials: attr(el, "initials") ?? "",
1964
+ userId: attr(el, "userId"),
1965
+ providerId: attr(el, "providerId")
1966
+ }));
1967
+ }
1968
+ /**
1969
+ * Read one slide's modern comments from its `modernComment_*.xml` part (resolved
1970
+ * via the slide's 2018 `comments` relationship), resolving each comment's and
1971
+ * reply's `@authorId` GUID against `authors` (typically
1972
+ * {@link readModernCommentAuthors}). Replies are nested, not flattened. `[]` when
1973
+ * the slide has no modern comments part.
1974
+ */
1975
+ function readModernSlideComments(opc, slidePart, authors) {
1976
+ const rels = opc.relationshipsFor(slidePart.partName);
1977
+ const rel = rels.byType(MODERN_COMMENTS_REL_TYPE)[0];
1978
+ if (!rel) return [];
1979
+ const root = opc.part(rels.resolveTarget(rel.id))?.dom.documentElement;
1980
+ if (!root) return [];
1981
+ const byId = new Map(authors.map((a) => [a.id, a]));
1982
+ return getElements(root, "p188:cm").map((cm) => parseModernComment(cm, byId, true));
1983
+ }
1984
+ /**
1985
+ * Report which comment schema a deck uses: `'modern'` when it carries any 2018
1986
+ * `modernComment_*` part, else `'legacy'` when it carries any classic
1987
+ * `commentN.xml` part, else `'none'`. Lets a consumer pick the right accessor
1988
+ * ({@link readModernSlideComments} vs {@link readSlideComments}) without probing
1989
+ * both.
1990
+ */
1991
+ function commentSchema(opc) {
1293
1992
  if (opc.partsByContentType(MODERN_COMMENTS_CONTENT_TYPE).length > 0) return "modern";
1294
1993
  if (opc.partsByContentType(LEGACY_COMMENTS_CONTENT_TYPE).length > 0) return "legacy";
1295
1994
  return "none";
@@ -1624,12 +2323,10 @@ var SlideMaster = class {
1624
2323
  return rel ? this.opc.part(rels.resolveTarget(rel.id)) ?? null : null;
1625
2324
  }
1626
2325
  #cSld() {
1627
- const root = this.part.dom.documentElement;
1628
- return root ? firstChild(root, "p:cSld") : null;
2326
+ return cSldOf(this.part.dom.documentElement);
1629
2327
  }
1630
2328
  #spTree() {
1631
- const cSld = this.#cSld();
1632
- return cSld ? firstChild(cSld, "p:spTree") : null;
2329
+ return spTreeOf(this.part.dom.documentElement);
1633
2330
  }
1634
2331
  };
1635
2332
  /**
@@ -1735,12 +2432,10 @@ var SlideLayout = class {
1735
2432
  return this.#themeContext ??= resolveLayoutColorContext(this.opc, this.partName);
1736
2433
  }
1737
2434
  #cSld() {
1738
- const root = this.part.dom.documentElement;
1739
- return root ? firstChild(root, "p:cSld") : null;
2435
+ return cSldOf(this.part.dom.documentElement);
1740
2436
  }
1741
2437
  #spTree() {
1742
- const cSld = this.#cSld();
1743
- return cSld ? firstChild(cSld, "p:spTree") : null;
2438
+ return spTreeOf(this.part.dom.documentElement);
1744
2439
  }
1745
2440
  };
1746
2441
  //#endregion
@@ -2399,9 +3094,14 @@ var Slide = class {
2399
3094
  * All text on the slide, flattened in document order — the read-model
2400
3095
  * counterpart to {@link TextFrame.text} one level up. Every text-bearing shape
2401
3096
  * contributes its text, recursing into groups ({@link GroupShape}) and reading
2402
- * table cells ({@link GraphicFrame.table}); text-free shapes (pictures,
2403
- * connectors, empty boxes) contribute nothing. Blocks are joined by `\n`; within
2404
- * a table, cells in a row are joined by `\t` and rows by `\n`.
3097
+ * table cells ({@link GraphicFrame.table}) and SmartArt node text
3098
+ * ({@link GraphicFrame.diagram}); text-free shapes (pictures, connectors, empty
3099
+ * boxes) contribute nothing. Blocks are joined by `\n`; within a table, cells in
3100
+ * a row are joined by `\t` and rows by `\n`.
3101
+ *
3102
+ * A diagram contributes for the same reason a table does: its nodes are body text
3103
+ * PowerPoint itself searches and spell-checks, so a slide whose whole message is a
3104
+ * SmartArt graphic would otherwise flatten to the empty string.
2405
3105
  *
2406
3106
  * This is deliberately scoped to the slide's own shape tree. It does **not**
2407
3107
  * include speaker notes (read those via {@link notesText}) or chart data labels
@@ -2415,11 +3115,16 @@ var Slide = class {
2415
3115
  for (const shape of shapes) if (shape instanceof GroupShape) walk(shape.shapes);
2416
3116
  else if (shape instanceof GraphicFrame) {
2417
3117
  const table = shape.table;
2418
- if (!table) continue;
2419
- for (const row of table.rows) {
2420
- const cells = row.cells.map((cell) => cell.text);
2421
- if (cells.some((cell) => cell.length > 0)) blocks.push(cells.join(" "));
3118
+ if (table) {
3119
+ for (const row of table.rows) {
3120
+ const cells = row.cells.map((cell) => cell.text);
3121
+ if (cells.some((cell) => cell.length > 0)) blocks.push(cells.join(" "));
3122
+ }
3123
+ continue;
2422
3124
  }
3125
+ const diagram = shape.diagram;
3126
+ const diagramText = diagram ? diagram.text : "";
3127
+ if (diagramText.length > 0) blocks.push(diagramText);
2423
3128
  } else {
2424
3129
  const text = shape.text;
2425
3130
  if (text.length > 0) blocks.push(text);
@@ -2700,6 +3405,34 @@ var Slide = class {
2700
3405
  return new Picture(pic, this);
2701
3406
  }
2702
3407
  /**
3408
+ * Give this slide the speaker notes `text`, and return the resulting
3409
+ * {@link NotesSlide}. A `
3410
+ ` starts a new paragraph, matching the write-side
3411
+ * `addNotes`; the runs carry no formatting of their own, so style them
3412
+ * afterwards through {@link notesTextFrame}.
3413
+ *
3414
+ * The counterpart to the read side's `notesText`/`notesTextFrame`/`notesSlide`
3415
+ * getters, and the way to annotate a slide that has **no notes part at all** —
3416
+ * the state an `importSlide` without `{ importNotes: true }` leaves behind, and
3417
+ * the one a `notesTextFrame` edit cannot reach because there is no frame to
3418
+ * hand back. Calling it on a slide that already has notes replaces the body
3419
+ * text and leaves the rest of the part (its geometry, its other two
3420
+ * placeholders) alone.
3421
+ *
3422
+ * Creating the part pulls in what a notes slide must bind to: a `notesMaster`,
3423
+ * of which a presentation may hold at most one. This deck's own is reused when
3424
+ * it has one; otherwise one is installed, bound to a clone of this deck's theme
3425
+ * so it resolves against the destination palette. That is the same
3426
+ * single-master rule `importSlide({ importNotes: true })` and `appendSlides`
3427
+ * follow, so mixing the three cannot produce a second notes master.
3428
+ */
3429
+ addNotes(text) {
3430
+ const partName = authorNotes(this, text);
3431
+ const part = this.presentation.opc.part(partName);
3432
+ if (!part) throw new InternalError("import/part-went-missing", `Authored notes part went missing: ${partName}`);
3433
+ return new NotesSlide(this.presentation.opc, part);
3434
+ }
3435
+ /**
2703
3436
  * @internal The slide's shape tree (`p:cSld/p:spTree`), or `null` if absent.
2704
3437
  * Exposed for cross-slide composition (`Presentation.importShape`).
2705
3438
  */
@@ -2730,12 +3463,10 @@ var Slide = class {
2730
3463
  return max + 1;
2731
3464
  }
2732
3465
  #cSld() {
2733
- const root = this.part.dom.documentElement;
2734
- return root ? firstChild(root, "p:cSld") : null;
3466
+ return cSldOf(this.part.dom.documentElement);
2735
3467
  }
2736
3468
  #spTree() {
2737
- const cSld = this.#cSld();
2738
- return cSld ? firstChild(cSld, "p:spTree") : null;
3469
+ return spTreeOf(this.part.dom.documentElement);
2739
3470
  }
2740
3471
  };
2741
3472
  function requireFinite(value, name) {
@@ -2745,39 +3476,53 @@ function requirePositive(value, name) {
2745
3476
  requireFinite(value, name);
2746
3477
  if (value <= 0) throw new InvalidOptionError("coord/not-positive", `${name} must be positive, got ${value}`);
2747
3478
  }
2748
- /** Build a minimal, schema-valid text-box `p:sp` element (not yet attached). */
2749
- function buildTextBox(doc, spec) {
2750
- const make = (qname) => createElement(doc, qname);
2751
- const append = (parent, qname) => {
2752
- const child = make(qname);
2753
- parent.appendChild(child);
2754
- return child;
2755
- };
2756
- const sp = make("p:sp");
2757
- const nvSpPr = append(sp, "p:nvSpPr");
2758
- const cNvPr = append(nvSpPr, "p:cNvPr");
2759
- setAttr(cNvPr, "id", String(spec.id));
2760
- setAttr(cNvPr, "name", spec.name);
2761
- const cNvSpPr = append(nvSpPr, "p:cNvSpPr");
2762
- setAttr(cNvSpPr, "txBox", "1");
2763
- append(nvSpPr, "p:nvPr");
2764
- const spPr = append(sp, "p:spPr");
2765
- const xfrm = append(spPr, "a:xfrm");
2766
- const off = append(xfrm, "a:off");
3479
+ /**
3480
+ * Create `qname` in the parent's document and append it.
3481
+ *
3482
+ * The builders below are uniformly parent-then-child, so this is the only shape
3483
+ * of element creation they need; `ownerDocumentOf` is what lets it take the
3484
+ * parent alone rather than threading a `Document` through every call.
3485
+ */
3486
+ function appendEl(parent, qname) {
3487
+ const child = createElement(ownerDocumentOf(parent), qname);
3488
+ parent.appendChild(child);
3489
+ return child;
3490
+ }
3491
+ /**
3492
+ * Append the `p:spPr` a built `p:sp` and `p:pic` share: the spec's box as an
3493
+ * `a:xfrm`, then a rect `prstGeom` with the empty `a:avLst` the schema requires.
3494
+ */
3495
+ function appendSpPr(parent, spec) {
3496
+ const spPr = appendEl(parent, "p:spPr");
3497
+ const xfrm = appendEl(spPr, "a:xfrm");
3498
+ const off = appendEl(xfrm, "a:off");
2767
3499
  setAttr(off, "x", String(spec.left));
2768
3500
  setAttr(off, "y", String(spec.top));
2769
- const ext = append(xfrm, "a:ext");
3501
+ const ext = appendEl(xfrm, "a:ext");
2770
3502
  setAttr(ext, "cx", String(spec.width));
2771
3503
  setAttr(ext, "cy", String(spec.height));
2772
- const prstGeom = append(spPr, "a:prstGeom");
3504
+ const prstGeom = appendEl(spPr, "a:prstGeom");
2773
3505
  setAttr(prstGeom, "prst", "rect");
2774
- append(prstGeom, "a:avLst");
2775
- const txBody = append(sp, "p:txBody");
2776
- append(txBody, "a:bodyPr");
2777
- append(txBody, "a:lstStyle");
2778
- const p = append(txBody, "a:p");
3506
+ appendEl(prstGeom, "a:avLst");
3507
+ return spPr;
3508
+ }
3509
+ /** Build a minimal, schema-valid text-box `p:sp` element (not yet attached). */
3510
+ function buildTextBox(doc, spec) {
3511
+ const sp = createElement(doc, "p:sp");
3512
+ const nvSpPr = appendEl(sp, "p:nvSpPr");
3513
+ const cNvPr = appendEl(nvSpPr, "p:cNvPr");
3514
+ setAttr(cNvPr, "id", String(spec.id));
3515
+ setAttr(cNvPr, "name", spec.name);
3516
+ const cNvSpPr = appendEl(nvSpPr, "p:cNvSpPr");
3517
+ setAttr(cNvSpPr, "txBox", "1");
3518
+ appendEl(nvSpPr, "p:nvPr");
3519
+ appendSpPr(sp, spec);
3520
+ const txBody = appendEl(sp, "p:txBody");
3521
+ appendEl(txBody, "a:bodyPr");
3522
+ appendEl(txBody, "a:lstStyle");
3523
+ const p = appendEl(txBody, "a:p");
2779
3524
  if (spec.text !== "") {
2780
- const t = append(append(p, "a:r"), "a:t");
3525
+ const t = appendEl(appendEl(p, "a:r"), "a:t");
2781
3526
  t.textContent = spec.text;
2782
3527
  if (spec.text !== spec.text.trim()) setAttr(t, "xml:space", "preserve");
2783
3528
  }
@@ -2785,35 +3530,19 @@ function buildTextBox(doc, spec) {
2785
3530
  }
2786
3531
  /** Build a minimal, schema-valid `p:pic` element (not yet attached). */
2787
3532
  function buildPicture(doc, spec) {
2788
- const make = (qname) => createElement(doc, qname);
2789
- const append = (parent, qname) => {
2790
- const child = make(qname);
2791
- parent.appendChild(child);
2792
- return child;
2793
- };
2794
- const pic = make("p:pic");
2795
- const nvPicPr = append(pic, "p:nvPicPr");
2796
- const cNvPr = append(nvPicPr, "p:cNvPr");
3533
+ const pic = createElement(doc, "p:pic");
3534
+ const nvPicPr = appendEl(pic, "p:nvPicPr");
3535
+ const cNvPr = appendEl(nvPicPr, "p:cNvPr");
2797
3536
  setAttr(cNvPr, "id", String(spec.id));
2798
3537
  setAttr(cNvPr, "name", spec.name);
2799
- const picLocks = append(append(nvPicPr, "p:cNvPicPr"), "a:picLocks");
3538
+ const picLocks = appendEl(appendEl(nvPicPr, "p:cNvPicPr"), "a:picLocks");
2800
3539
  setAttr(picLocks, "noChangeAspect", "1");
2801
- append(nvPicPr, "p:nvPr");
2802
- const blipFill = append(pic, "p:blipFill");
2803
- const blip = append(blipFill, "a:blip");
3540
+ appendEl(nvPicPr, "p:nvPr");
3541
+ const blipFill = appendEl(pic, "p:blipFill");
3542
+ const blip = appendEl(blipFill, "a:blip");
2804
3543
  setAttr(blip, "r:embed", spec.relId);
2805
- append(append(blipFill, "a:stretch"), "a:fillRect");
2806
- const spPr = append(pic, "p:spPr");
2807
- const xfrm = append(spPr, "a:xfrm");
2808
- const off = append(xfrm, "a:off");
2809
- setAttr(off, "x", String(spec.left));
2810
- setAttr(off, "y", String(spec.top));
2811
- const ext = append(xfrm, "a:ext");
2812
- setAttr(ext, "cx", String(spec.width));
2813
- setAttr(ext, "cy", String(spec.height));
2814
- const prstGeom = append(spPr, "a:prstGeom");
2815
- setAttr(prstGeom, "prst", "rect");
2816
- append(prstGeom, "a:avLst");
3544
+ appendEl(appendEl(blipFill, "a:stretch"), "a:fillRect");
3545
+ appendSpPr(pic, spec);
2817
3546
  return pic;
2818
3547
  }
2819
3548
  //#endregion
@@ -2925,70 +3654,6 @@ function readCustomProperties(opc) {
2925
3654
  return out;
2926
3655
  }
2927
3656
  //#endregion
2928
- //#region src/read/oxml/slide-dom.ts
2929
- /** The `p:cSld@name` of a slide/layout/master part (`''` when absent). */
2930
- function cSldName(part) {
2931
- const root = part?.dom.documentElement;
2932
- const cSld = root && firstChild(root, "p:cSld");
2933
- return (cSld && attr(cSld, "name")) ?? "";
2934
- }
2935
- /** Whether `el` is a `p:spTree`'s own group properties (`p:nvGrpSpPr`/`p:grpSpPr`), not a shape. */
2936
- function isSpTreeProperty(el) {
2937
- return el.namespaceURI === OOXML_NS.p && (el.localName === "nvGrpSpPr" || el.localName === "grpSpPr");
2938
- }
2939
- /** Whether a `p:spTree` child is a placeholder shape (its `*nvPr` carries a `p:ph`). */
2940
- function isPlaceholderShape(shape) {
2941
- const nv = firstChildElement(shape);
2942
- const nvPr = nv && firstChild(nv, "p:nvPr");
2943
- return !!(nvPr && firstChild(nvPr, "p:ph"));
2944
- }
2945
- /** The decorative shapes on a layout/master `p:spTree`: every shape child except placeholders. */
2946
- function carriedDecorations(root) {
2947
- if (!root) return [];
2948
- const cSld = firstChild(root, "p:cSld");
2949
- const spTree = cSld && firstChild(cSld, "p:spTree");
2950
- if (!spTree) return [];
2951
- const out = [];
2952
- for (let node = spTree.firstChild; node; node = node.nextSibling) {
2953
- if (node.nodeType !== 1) continue;
2954
- const el = node;
2955
- if (isSpTreeProperty(el) || isPlaceholderShape(el)) continue;
2956
- out.push(el);
2957
- }
2958
- return out;
2959
- }
2960
- /**
2961
- * The `n`-th shape child of a `p:spTree` in document (z-)order, skipping the
2962
- * tree's own `nvGrpSpPr`/`grpSpPr` and any trailing `p:extLst`. Returns `null`
2963
- * when `n` is past the last shape (the caller then appends).
2964
- */
2965
- function nthShapeChild(spTree, n) {
2966
- let i = 0;
2967
- for (let node = spTree.firstChild; node; node = node.nextSibling) {
2968
- if (node.nodeType !== 1) continue;
2969
- const el = node;
2970
- if (isSpTreeProperty(el)) continue;
2971
- if (el.namespaceURI === OOXML_NS.p && el.localName === "extLst") continue;
2972
- if (i === n) return el;
2973
- i++;
2974
- }
2975
- return null;
2976
- }
2977
- /** The first shape child of a `p:spTree` (skipping `nvGrpSpPr`/`grpSpPr`), or `null`. */
2978
- function firstShapeChild(spTree) {
2979
- for (let node = spTree.firstChild; node; node = node.nextSibling) {
2980
- if (node.nodeType !== 1) continue;
2981
- const el = node;
2982
- if (!isSpTreeProperty(el)) return el;
2983
- }
2984
- return null;
2985
- }
2986
- /** Collect `node` and all its descendant elements (document order) into `out`. */
2987
- function collectElements(node, out) {
2988
- out.push(node);
2989
- for (let child = node.firstChild; child; child = child.nextSibling) if (child.nodeType === 1) collectElements(child, out);
2990
- }
2991
- //#endregion
2992
3657
  //#region src/read/api/ops/rescale.ts
2993
3658
  /** Build the EMU transform mapping source-canvas coordinates onto the target canvas (see {@link ImportSlideOptions.rescale}). */
2994
3659
  function computeRescale(source, target, mode) {
@@ -3081,7 +3746,7 @@ function rescaleTableGrid(graphicFrame, t) {
3081
3746
  const TABLE_STYLES_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles";
3082
3747
  const TABLE_STYLES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
3083
3748
  const TABLE_STYLES_DEFAULT_GUID = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
3084
- const textEncoder$2 = new TextEncoder();
3749
+ const textEncoder$1 = new TextEncoder();
3085
3750
  /**
3086
3751
  * Copy any table style the restyled slide references from the source
3087
3752
  * `tableStyles.xml` into this deck's (the `remapLiterals` table leg). A restyled
@@ -3114,268 +3779,72 @@ function copySourceTableStyles(dest, sourceOpc, slideRoot) {
3114
3779
  if (addTableStyles(destList, referenced)) destPart.markDirty();
3115
3780
  }
3116
3781
  /**
3117
- * Append each source `<a:tblStyle>` this deck does not already define, keyed by
3118
- * `styleId` — the shared core of {@link copySourceTableStyles} (which offers only
3119
- * the styles a restyled slide references) and {@link carryTableStyles} (which
3120
- * offers the source's whole list). Destination-wins per id: a style this deck
3121
- * already defines is left untouched, so a re-call adds nothing. Returns whether
3122
- * anything was added.
3123
- */
3124
- function addTableStyles(destList, styles) {
3125
- const present = new Set(getElements(destList, "a:tblStyle").map((st) => attr(st, "styleId")));
3126
- let added = false;
3127
- for (const style of styles) {
3128
- const id = attr(style, "styleId");
3129
- if (!id || present.has(id)) continue;
3130
- destList.appendChild(ownerDocumentOf(destList).importNode(style, true));
3131
- present.add(id);
3132
- added = true;
3133
- }
3134
- return added;
3135
- }
3136
- /**
3137
- * Copy the source deck's **whole** `tableStyles.xml` into this deck: every
3138
- * `<a:tblStyle>` it defines, plus its `a:tblStyleLst@def` (the default table
3139
- * style). Presentation-level, so a separate traversal from the master/layout copy
3140
- * chain — and whole-deck, since `tableStyles.xml` does not record which style
3141
- * belongs to which master. See `ImportSlideMastersOptions.tableStyles`.
3142
- *
3143
- * Styles union by `styleId` (destination-wins per id, via {@link addTableStyles}),
3144
- * but `def` is **source-wins**: a caller asking for the source's table styling
3145
- * wants its default too, and the destination's `def` is typically a generator stub
3146
- * rather than a deliberate choice. Carrying the styles without the `def` would be
3147
- * the worse half-measure — the standard default GUID is one most templates also
3148
- * define, so a new table would silently resolve to the wrong style rather than
3149
- * visibly to none. Idempotent: a re-call re-adds nothing and re-sets `def` to the
3150
- * same value.
3151
- */
3152
- function carryTableStyles(dest, sourceOpc) {
3153
- const sourceList = tableStyleList(sourceOpc);
3154
- if (!sourceList) return;
3155
- const destPart = ensureTableStylesPart(dest);
3156
- const destList = destPart.dom.documentElement;
3157
- if (!destList) return;
3158
- let changed = addTableStyles(destList, getElements(sourceList, "a:tblStyle"));
3159
- const sourceDef = attr(sourceList, "def");
3160
- if (sourceDef && attr(destList, "def") !== sourceDef) {
3161
- destList.setAttribute("def", sourceDef);
3162
- changed = true;
3163
- }
3164
- if (changed) destPart.markDirty();
3165
- }
3166
- /** The `a:tblStyleLst` root of a package's `tableStyles.xml`, or `null` when it has none. */
3167
- function tableStyleList(opc) {
3168
- const part = opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3169
- return part ? part.dom.documentElement : null;
3170
- }
3171
- /**
3172
- * The deck's `tableStyles.xml` part, creating an empty one (and wiring its
3173
- * `presentation.xml` relationship + content type) when the deck has none.
3174
- */
3175
- function ensureTableStylesPart(dest) {
3176
- const existing = dest.opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3177
- if (existing) return existing;
3178
- const partName = dest.opc.reservePartNameLike("/ppt/tableStyles.xml");
3179
- 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}"/>`;
3180
- const part = dest.opc.addPart(partName, TABLE_STYLES_CONTENT_TYPE, textEncoder$2.encode(xml));
3181
- dest.opc.relationshipsFor(dest.presentationPart.partName).add(TABLE_STYLES_REL, relativePartName(dest.presentationPart.partName, partName));
3182
- return part;
3183
- }
3184
- //#endregion
3185
- //#region src/read/api/ops/master-registry.ts
3186
- /**
3187
- * Slide-master bookkeeping in `presentation.xml` and in a master's own
3188
- * `p:sldLayoutIdLst`: registering a master so renderers treat it as active,
3189
- * promoting masters to the front of the id list, allocating ids out of the shared
3190
- * master/layout id space, and emptying a freshly-copied master's layout list.
3191
- *
3192
- * These are the destination-side half of the import machinery -- they read and
3193
- * write only this deck, never a source package and never the copy registry -- so
3194
- * they take a structural {@link DeckTarget} rather than a `Presentation`. The
3195
- * source-side half that drives most of them is `part-copy.ts`.
3196
- */
3197
- /**
3198
- * Wire a freshly-copied slide master into `presentation.xml`: add a
3199
- * presentation→master relationship and a `p:sldMasterId` entry in
3200
- * `p:sldMasterIdLst`. A master that is reachable only through the
3201
- * slide→layout→master rel chain but absent from `p:sldMasterIdLst` is treated
3202
- * as inactive by renderers, so its background/graphics never paint. No-op when
3203
- * the master is already registered (shared across imports from one source).
3204
- */
3205
- function registerMaster(dest, masterPartName) {
3206
- const presPart = dest.presentationPart;
3207
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3208
- for (const rel of presRels.byType(SLIDE_MASTER_REL)) if (presRels.resolveTarget(rel.id) === masterPartName) return;
3209
- const relId = presRels.add(SLIDE_MASTER_REL, relativePartName(presPart.partName, masterPartName)).id;
3210
- const root = presPart.dom.documentElement;
3211
- if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a master in");
3212
- const lst = getOrAddChild(root, "p:sldMasterIdLst", [
3213
- "p:notesMasterIdLst",
3214
- "p:handoutMasterIdLst",
3215
- "p:sldIdLst",
3216
- "p:sldSz",
3217
- "p:notesSz",
3218
- "p:embeddedFontLst",
3219
- "p:custShowLst",
3220
- "p:photoAlbum",
3221
- "p:custDataLst",
3222
- "p:kinsoku",
3223
- "p:defaultTextStyle",
3224
- "p:modifyVerifier",
3225
- "p:extLst"
3226
- ]);
3227
- const entry = createElement(presPart.dom, "p:sldMasterId");
3228
- setAttr(entry, "id", String(nextMasterLayoutId(dest)));
3229
- setAttr(entry, "r:id", relId);
3230
- lst.appendChild(entry);
3231
- presPart.markDirty();
3232
- }
3233
- /**
3234
- * Move the `p:sldMasterId` entries for `masterPartNames` to the front of
3235
- * `p:sldMasterIdLst`, preserving their existing relative order, so the first of
3236
- * them becomes the deck's `Designs(1)` — the theme PowerPoint's Design tab shows.
3237
- * Reorders the id list only; relationships, ids, and all other parts are left as
3238
- * they are. A no-op when the named masters already lead (idempotent re-call).
3239
- */
3240
- function promoteMasters(dest, masterPartNames) {
3241
- if (masterPartNames.length === 0) return;
3242
- const presPart = dest.presentationPart;
3243
- const root = presPart.dom.documentElement;
3244
- const lst = root && firstChild(root, "p:sldMasterIdLst");
3245
- if (!lst) return;
3246
- const rels = dest.opc.relationshipsFor(presPart.partName);
3247
- const promote = new Set(masterPartNames);
3248
- const entries = getElements(lst, "p:sldMasterId");
3249
- const isPromoted = (entry) => {
3250
- const relId = attr(entry, "r:id");
3251
- return relId ? promote.has(rels.resolveTarget(relId)) : false;
3252
- };
3253
- const desired = [...entries.filter(isPromoted), ...entries.filter((e) => !isPromoted(e))];
3254
- if (desired.every((entry, i) => entry === entries[i])) return;
3255
- for (const entry of desired) lst.appendChild(entry);
3256
- presPart.markDirty();
3257
- }
3258
- /**
3259
- * The next free id in the presentation-wide slide-master / slide-layout id space.
3260
- *
3261
- * `p:sldMasterId/@id` and every master's `p:sldLayoutId/@id` draw from ONE shared
3262
- * id space (both ST_SlideMasterId/ST_SlideLayoutId, floor 2147483648) and must be
3263
- * unique across the WHOLE presentation, not just within their own list. A duplicate
3264
- * anywhere in this space makes PowerPoint report the file as corrupt on open
3265
- * ("found a problem with content" / 0x80070570), even though LibreOffice silently
3266
- * tolerates it — so an imported master AND each of its layouts must take an id past
3267
- * the max of both the master-id list and every layout-id list. Recomputed per
3268
- * allocation so ids appended earlier in the same import are counted.
3269
- */
3270
- function nextMasterLayoutId(dest) {
3271
- let max = 2147483647;
3272
- const presPart = dest.presentationPart;
3273
- const root = presPart.dom.documentElement;
3274
- const masterLst = root && firstChild(root, "p:sldMasterIdLst");
3275
- if (masterLst) for (const entry of getElements(masterLst, "p:sldMasterId")) {
3276
- const id = intValue(attr(entry, "id"));
3277
- if (id !== null && id > max) max = id;
3278
- }
3279
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3280
- for (const rel of presRels.byType(SLIDE_MASTER_REL)) {
3281
- const masterRoot = dest.opc.part(presRels.resolveTarget(rel.id))?.dom.documentElement;
3282
- const layoutLst = masterRoot && firstChild(masterRoot, "p:sldLayoutIdLst");
3283
- if (!layoutLst) continue;
3284
- for (const entry of getElements(layoutLst, "p:sldLayoutId")) {
3285
- const id = intValue(attr(entry, "id"));
3286
- if (id !== null && id > max) max = id;
3287
- }
3288
- }
3289
- return max + 1;
3290
- }
3291
- /** Empty a freshly-copied master's `p:sldLayoutIdLst`; copied layouts repopulate it. */
3292
- function clearLayoutIdList(dest, masterPartName) {
3293
- const masterPart = dest.opc.part(masterPartName);
3294
- const root = masterPart?.dom.documentElement;
3295
- const lst = root && firstChild(root, "p:sldLayoutIdLst");
3296
- if (!masterPart || !lst) return;
3297
- removeChildrenByQName(lst, ["p:sldLayoutId"]);
3298
- masterPart.markDirty();
3299
- }
3300
- /**
3301
- * Append a `p:sldLayoutId` entry for `layoutPartName` to `masterPartName`'s
3302
- * `p:sldLayoutIdLst`, adding the master→layout relationship it points at. The
3303
- * destination-side half of linking a copied layout into its copied master; the
3304
- * caller resolves which master that is from the source-side rels.
3305
- */
3306
- function addLayoutToMaster(dest, masterPartName, layoutPartName) {
3307
- const masterPart = dest.opc.part(masterPartName);
3308
- const root = masterPart?.dom.documentElement;
3309
- if (!masterPart || !root) return;
3310
- const relId = dest.opc.relationshipsFor(masterPartName).add(SLIDE_LAYOUT_REL, relativePartName(masterPartName, layoutPartName)).id;
3311
- const lst = getOrAddChild(root, "p:sldLayoutIdLst", [
3312
- "p:transition",
3313
- "p:timing",
3314
- "p:hf",
3315
- "p:txStyles",
3316
- "p:extLst"
3317
- ]);
3318
- const entry = createElement(masterPart.dom, "p:sldLayoutId");
3319
- setAttr(entry, "id", String(nextMasterLayoutId(dest)));
3320
- setAttr(entry, "r:id", relId);
3321
- lst.appendChild(entry);
3322
- masterPart.markDirty();
3323
- }
3324
- //#endregion
3325
- //#region src/read/api/ops/part-copy.ts
3326
- const SLIDE_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
3327
- const SLIDE_LAYOUT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
3328
- /**
3329
- * Copy `sourcePartName` (and, recursively, every internal part it references)
3330
- * from `ctx.source` into `ctx.dest`, returning the new partname. Idempotent
3331
- * per source package via the copy registry. Relationship ids are preserved so
3332
- * the copied part body's `r:id`/`r:embed` references stay valid; targets are
3333
- * rewritten to the freshly-allocated partnames. Notes relationships are
3334
- * dropped. A copied `slideMaster` does not drag in all its sibling layouts —
3335
- * each imported `slideLayout` wires itself into the master instead (see
3336
- * {@link linkLayoutIntoMaster}).
3782
+ * Append each source `<a:tblStyle>` this deck does not already define, keyed by
3783
+ * `styleId` — the shared core of {@link copySourceTableStyles} (which offers only
3784
+ * the styles a restyled slide references) and {@link carryTableStyles} (which
3785
+ * offers the source's whole list). Destination-wins per id: a style this deck
3786
+ * already defines is left untouched, so a re-call adds nothing. Returns whether
3787
+ * anything was added.
3337
3788
  */
3338
- function copyPart(ctx, sourcePartName) {
3339
- const existing = ctx.registry.get(sourcePartName);
3340
- if (existing) return existing;
3341
- const sourcePart = ctx.source.part(sourcePartName);
3342
- if (!sourcePart) throw new PackageReadError("package/part-missing", `importSlide: source package has no part ${sourcePartName}`);
3343
- const newPartName = ctx.dest.opc.reservePartNameLike(sourcePartName);
3344
- ctx.dest.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
3345
- ctx.registry.set(sourcePartName, newPartName);
3346
- const isMaster = sourcePart.contentType === SLIDE_MASTER_CONTENT_TYPE;
3347
- const sourceRels = ctx.source.relationshipsFor(sourcePartName);
3348
- const targetRels = ctx.dest.opc.relationshipsFor(newPartName);
3349
- for (const rel of sourceRels) {
3350
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
3351
- if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
3352
- if (rel.targetMode === "External") {
3353
- targetRels.addWithId(rel.id, rel.type, rel.target, "External");
3354
- continue;
3355
- }
3356
- const newTargetPartName = copyPart(ctx, sourceRels.resolveTarget(rel.id));
3357
- targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTargetPartName));
3789
+ function addTableStyles(destList, styles) {
3790
+ const present = new Set(getElements(destList, "a:tblStyle").map((st) => attr(st, "styleId")));
3791
+ let added = false;
3792
+ for (const style of styles) {
3793
+ const id = attr(style, "styleId");
3794
+ if (!id || present.has(id)) continue;
3795
+ destList.appendChild(ownerDocumentOf(destList).importNode(style, true));
3796
+ present.add(id);
3797
+ added = true;
3358
3798
  }
3359
- if (isMaster) {
3360
- clearLayoutIdList(ctx.dest, newPartName);
3361
- registerMaster(ctx.dest, newPartName);
3799
+ return added;
3800
+ }
3801
+ /**
3802
+ * Copy the source deck's **whole** `tableStyles.xml` into this deck: every
3803
+ * `<a:tblStyle>` it defines, plus its `a:tblStyleLst@def` (the default table
3804
+ * style). Presentation-level, so a separate traversal from the master/layout copy
3805
+ * chain — and whole-deck, since `tableStyles.xml` does not record which style
3806
+ * belongs to which master. See `ImportSlideMastersOptions.tableStyles`.
3807
+ *
3808
+ * Styles union by `styleId` (destination-wins per id, via {@link addTableStyles}),
3809
+ * but `def` is **source-wins**: a caller asking for the source's table styling
3810
+ * wants its default too, and the destination's `def` is typically a generator stub
3811
+ * rather than a deliberate choice. Carrying the styles without the `def` would be
3812
+ * the worse half-measure — the standard default GUID is one most templates also
3813
+ * define, so a new table would silently resolve to the wrong style rather than
3814
+ * visibly to none. Idempotent: a re-call re-adds nothing and re-sets `def` to the
3815
+ * same value.
3816
+ */
3817
+ function carryTableStyles(dest, sourceOpc) {
3818
+ const sourceList = tableStyleList(sourceOpc);
3819
+ if (!sourceList) return;
3820
+ const destPart = ensureTableStylesPart(dest);
3821
+ const destList = destPart.dom.documentElement;
3822
+ if (!destList) return;
3823
+ let changed = addTableStyles(destList, getElements(sourceList, "a:tblStyle"));
3824
+ const sourceDef = attr(sourceList, "def");
3825
+ if (sourceDef && attr(destList, "def") !== sourceDef) {
3826
+ destList.setAttribute("def", sourceDef);
3827
+ changed = true;
3362
3828
  }
3363
- if (sourcePart.contentType === SLIDE_LAYOUT_CONTENT_TYPE) linkLayoutIntoMaster(ctx, sourceRels, newPartName);
3364
- return newPartName;
3829
+ if (changed) destPart.markDirty();
3830
+ }
3831
+ /** The `a:tblStyleLst` root of a package's `tableStyles.xml`, or `null` when it has none. */
3832
+ function tableStyleList(opc) {
3833
+ const part = opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3834
+ return part ? part.dom.documentElement : null;
3365
3835
  }
3366
3836
  /**
3367
- * Wire a just-copied layout into its (already-copied) master. Resolves which
3368
- * destination master that is by running the layout's *source* master rel through
3369
- * the copy registry, then hands the destination-side wiring to
3370
- * {@link addLayoutToMaster}. Called once per copied layout, so the master
3371
- * accumulates exactly the imported layouts.
3837
+ * The deck's `tableStyles.xml` part, creating an empty one (and wiring its
3838
+ * `presentation.xml` relationship + content type) when the deck has none.
3372
3839
  */
3373
- function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
3374
- const masterRel = layoutSourceRels.byType(SLIDE_MASTER_REL)[0];
3375
- if (!masterRel) return;
3376
- const masterPartName = ctx.registry.get(layoutSourceRels.resolveTarget(masterRel.id));
3377
- if (!masterPartName) return;
3378
- addLayoutToMaster(ctx.dest, masterPartName, layoutPartName);
3840
+ function ensureTableStylesPart(dest) {
3841
+ const existing = dest.opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
3842
+ if (existing) return existing;
3843
+ const partName = dest.opc.reservePartNameLike("/ppt/tableStyles.xml");
3844
+ 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}"/>`;
3845
+ const part = dest.opc.addPart(partName, TABLE_STYLES_CONTENT_TYPE, textEncoder$1.encode(xml));
3846
+ dest.opc.relationshipsFor(dest.presentationPart.partName).add(TABLE_STYLES_REL, relativePartName(dest.presentationPart.partName, partName));
3847
+ return part;
3379
3848
  }
3380
3849
  //#endregion
3381
3850
  //#region src/read/api/ops/carried-rels.ts
@@ -3401,8 +3870,9 @@ function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
3401
3870
  * @param {string} destPartName - partname of the destination part the subtree is being placed in
3402
3871
  * @param {Relationships} destRels - relationships of that destination part
3403
3872
  * @param {Map<string, string>} relIdMap - per-call cache (source part + source id → new id) deduping references shared within one import
3873
+ * @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
3404
3874
  */
3405
- function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap) {
3875
+ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap, owned) {
3406
3876
  const elements = [];
3407
3877
  collectElements(node, elements);
3408
3878
  for (const el of elements) {
@@ -3417,7 +3887,7 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
3417
3887
  id: a.value
3418
3888
  });
3419
3889
  }
3420
- for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap));
3890
+ for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned));
3421
3891
  }
3422
3892
  }
3423
3893
  /**
@@ -3428,16 +3898,20 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
3428
3898
  * @param {string} destPartName - partname of the destination part
3429
3899
  * @param {Relationships} destRels - relationships of that destination part
3430
3900
  * @param {Map<string, string>} relIdMap - per-call dedupe cache
3901
+ * @param {OwnedScope} [owned] - ownership scope of the carried content, for the parts it owns
3431
3902
  * @return {string} the new relationship id on the destination part
3432
3903
  */
3433
- function carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap) {
3434
- const key = `${sourceRels.sourcePartName}|${id}`;
3435
- const cached = relIdMap.get(key);
3436
- if (cached) return cached;
3904
+ function carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned) {
3437
3905
  const rel = sourceRels.get(id);
3438
3906
  if (!rel) throw new InvalidOptionError("relationship/not-found", `Relationships of ${sourceRels.sourcePartName}: no relationship with id ${id}`);
3439
- 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;
3440
- relIdMap.set(key, newId);
3907
+ const ownsIt = owned !== void 0 && rel.targetMode !== "External" && !isSharedByPageCopies(rel.type);
3908
+ const key = `${sourceRels.sourcePartName}|${id}`;
3909
+ if (!ownsIt) {
3910
+ const cached = relIdMap.get(key);
3911
+ if (cached) return cached;
3912
+ }
3913
+ 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;
3914
+ if (!ownsIt) relIdMap.set(key, newId);
3441
3915
  return newId;
3442
3916
  }
3443
3917
  //#endregion
@@ -3590,7 +4064,7 @@ function mergeEmbeddedFontEntries(dest, entries) {
3590
4064
  const presPart = dest.presentationPart;
3591
4065
  const presRoot = presPart.dom.documentElement;
3592
4066
  if (!presRoot) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to carry embedded fonts into");
3593
- const presRels = dest.opc.relationshipsFor(presPart.partName);
4067
+ const presRels = presentationRels(dest);
3594
4068
  const targetLst = getOrAddChild(presRoot, "p:embeddedFontLst", PRESENTATION_EMBEDDED_FONT_LST_SUCCESSORS);
3595
4069
  const targetByTypeface = /* @__PURE__ */ new Map();
3596
4070
  for (const entry of getElements(targetLst, "p:embeddedFont")) {
@@ -3637,21 +4111,16 @@ function mergeEmbeddedFontEntries(dest, entries) {
3637
4111
  * Pure with respect to the destination deck: it reads only the source package. It was a
3638
4112
  * private method on `Presentation` that used no instance state.
3639
4113
  */
3640
- /** The `p:cSld/p:bg` element of a slide/layout/master root, or `null`. */
3641
- function backgroundOf(root) {
3642
- const cSld = firstChild(root, "p:cSld");
3643
- return cSld ? firstChild(cSld, "p:bg") : null;
3644
- }
3645
4114
  /**
3646
4115
  * The background the slide effectively inherits from its source subgraph: the layout's
3647
4116
  * `p:bg`, else the master's. Returns `null` when the slide carries its own `p:bg` (it stays
3648
4117
  * on the slide and is flattened directly) or when none exists.
3649
4118
  */
3650
4119
  function effectiveBackground(sourceOpc, slideRoot, layoutPartName, masterPartName) {
3651
- if (slideRoot && backgroundOf(slideRoot)) return null;
4120
+ if (slideRoot && backgroundElementOf(slideRoot)) return null;
3652
4121
  const layoutRoot = layoutPartName ? sourceOpc.part(layoutPartName)?.dom.documentElement ?? null : null;
3653
4122
  const masterRoot = masterPartName ? sourceOpc.part(masterPartName)?.dom.documentElement ?? null : null;
3654
- return (layoutRoot && backgroundOf(layoutRoot)) ?? (masterRoot && backgroundOf(masterRoot)) ?? null;
4123
+ return (layoutRoot && backgroundElementOf(layoutRoot)) ?? (masterRoot && backgroundElementOf(masterRoot)) ?? null;
3655
4124
  }
3656
4125
  /**
3657
4126
  * Gather the flatten context for a source slide: walk slide → layout → master → theme,
@@ -3819,6 +4288,7 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3819
4288
  if (!slideRoot) throw new PackageReadError("package/part-has-no-root", `Imported slide ${newPartName} has no root element`);
3820
4289
  const sourceRels = source.opc.relationshipsFor(sourceSlide.partName);
3821
4290
  const targetRels = dest.opc.relationshipsFor(newPartName);
4291
+ const owned = newOwnedScope();
3822
4292
  for (const rel of sourceRels) {
3823
4293
  if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
3824
4294
  if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") {
@@ -3829,10 +4299,10 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3829
4299
  targetRels.addWithId(rel.id, rel.type, rel.target, "External");
3830
4300
  continue;
3831
4301
  }
3832
- const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id));
4302
+ const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
3833
4303
  targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTarget));
3834
4304
  }
3835
- if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName);
4305
+ if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName, owned);
3836
4306
  return {
3837
4307
  newPartName,
3838
4308
  slideRoot,
@@ -3853,13 +4323,13 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
3853
4323
  * @param {Element} slideRoot - root element of the new slide part (mutated in place)
3854
4324
  * @param {string} newPartName - partname of the new slide part
3855
4325
  * @param {string} slidePartName - partname of the source slide, for resolving its layout/master
4326
+ * @param {OwnedScope} owned - the page's ownership scope, so a decoration's own parts land in it
3856
4327
  */
3857
- function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
4328
+ function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName, owned) {
3858
4329
  const sourceOpc = ctx.source;
3859
4330
  const layoutPartName = resolveSingleRel(sourceOpc, slidePartName, SLIDE_LAYOUT_REL);
3860
4331
  const masterPartName = layoutPartName ? resolveSingleRel(sourceOpc, layoutPartName, SLIDE_MASTER_REL) : null;
3861
- const cSld = firstChild(slideRoot, "p:cSld");
3862
- const spTree = cSld && firstChild(cSld, "p:spTree");
4332
+ const spTree = spTreeOf(slideRoot);
3863
4333
  if (!spTree) return;
3864
4334
  const doc = ownerDocumentOf(slideRoot);
3865
4335
  const slideRels = dest.opc.relationshipsFor(newPartName);
@@ -3872,7 +4342,7 @@ function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
3872
4342
  const sourceRels = sourceOpc.relationshipsFor(partName);
3873
4343
  for (const deco of decorations) {
3874
4344
  const imported = doc.importNode(deco, true);
3875
- rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap);
4345
+ rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap, owned);
3876
4346
  spTree.insertBefore(imported, anchor);
3877
4347
  }
3878
4348
  }
@@ -3895,138 +4365,6 @@ function destinationLayoutPartName(dest) {
3895
4365
  return masterRels.resolveTarget(layoutRel.id);
3896
4366
  }
3897
4367
  //#endregion
3898
- //#region src/read/api/ops/notes-master.ts
3899
- /**
3900
- * Carrying speaker notes and the deck's single notes master across an import or an append.
3901
- *
3902
- * A `notesSlide` is not self-contained: it back-references the slide it annotates and forward-
3903
- * references the deck's notes master. `CT_NotesMasterIdList` allows at most one master per
3904
- * presentation, so a deck that already has one keeps it — the destination's notes styling wins —
3905
- * and a source master is copied only into a deck that has none. That single rule is why both the
3906
- * `Presentation`-to-`Presentation` path and the generator-XML path funnel through
3907
- * `registerNotesMaster`: they differ only in how the *part* comes into being.
3908
- */
3909
- const NOTES_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
3910
- const THEME_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml";
3911
- const textEncoder$1 = new TextEncoder();
3912
- /**
3913
- * Carry the source slide's speaker notes onto the just-imported slide (the
3914
- * `importNotes` option). The slide copy itself dropped the `notesSlide` rel, so
3915
- * this copies the source `notesSlide` part into a fresh partname, wires a
3916
- * `slide → notesSlide` rel on the new slide, and rebuilds the copied notesSlide's
3917
- * own relationships:
3918
- *
3919
- * - its `slide` back-rel is repointed at the new slide (`newSlidePartName`) — the
3920
- * source slide is *not* copied (that would be circular);
3921
- * - its `notesMaster` rel is resolved through {@link ensureNotesMaster}, which
3922
- * reuses this deck's notesMaster when it has one and copies the source's only
3923
- * when it has none (a deck may have at most one notesMaster);
3924
- * - any other internal target (media, etc.) is copied via {@link copyPart}.
3925
- *
3926
- * No-op when the source slide has no notes. Content-type registration for the
3927
- * copied parts is handled by `addPart`/{@link copyPart}.
3928
- */
3929
- function carryNotes(dest, source, ctx, sourceSlidePartName, newSlidePartName) {
3930
- const sourceSlideRels = source.opc.relationshipsFor(sourceSlidePartName);
3931
- const notesRel = sourceSlideRels.byType(NOTES_SLIDE_REL)[0];
3932
- if (!notesRel) return;
3933
- const sourceNotesPartName = sourceSlideRels.resolveTarget(notesRel.id);
3934
- const sourceNotesPart = source.opc.part(sourceNotesPartName);
3935
- if (!sourceNotesPart) return;
3936
- const newNotesPartName = dest.opc.reservePartNameLike(sourceNotesPartName);
3937
- dest.opc.addPart(newNotesPartName, sourceNotesPart.contentType, sourceNotesPart.bytes);
3938
- dest.opc.relationshipsFor(newSlidePartName).add(NOTES_SLIDE_REL, relativePartName(newSlidePartName, newNotesPartName));
3939
- const notesSourceRels = source.opc.relationshipsFor(sourceNotesPartName);
3940
- const notesTargetRels = dest.opc.relationshipsFor(newNotesPartName);
3941
- for (const rel of notesSourceRels) {
3942
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") {
3943
- notesTargetRels.addWithId(rel.id, SLIDE_REL, relativePartName(newNotesPartName, newSlidePartName));
3944
- continue;
3945
- }
3946
- if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster") {
3947
- const notesMaster = ensureNotesMaster(dest, ctx, notesSourceRels.resolveTarget(rel.id));
3948
- notesTargetRels.addWithId(rel.id, NOTES_MASTER_REL, relativePartName(newNotesPartName, notesMaster));
3949
- continue;
3950
- }
3951
- if (rel.targetMode === "External") {
3952
- notesTargetRels.addWithId(rel.id, rel.type, rel.target, "External");
3953
- continue;
3954
- }
3955
- const newTarget = copyPart(ctx, notesSourceRels.resolveTarget(rel.id));
3956
- notesTargetRels.addWithId(rel.id, rel.type, relativePartName(newNotesPartName, newTarget));
3957
- }
3958
- }
3959
- /**
3960
- * Resolve the notesMaster an imported `notesSlide` should bind to, honouring the
3961
- * single-notesMaster-per-presentation rule (`p:notesMasterIdLst` holds 0..1
3962
- * `p:notesMasterId`). If this deck already has a notesMaster it is reused and the
3963
- * source's is *not* copied (the destination's notes styling wins); otherwise the
3964
- * source notesMaster (and, via {@link copyPart}, its theme) is copied and
3965
- * registered in `presentation.xml`. Returns the destination notesMaster partname.
3966
- */
3967
- function ensureNotesMaster(dest, ctx, sourceNotesMasterPartName) {
3968
- const presPart = dest.presentationPart;
3969
- const presRels = dest.opc.relationshipsFor(presPart.partName);
3970
- const existing = presRels.byType(NOTES_MASTER_REL)[0];
3971
- if (existing) return presRels.resolveTarget(existing.id);
3972
- return registerNotesMaster(dest, copyPart(ctx, sourceNotesMasterPartName));
3973
- }
3974
- /**
3975
- * Wire an already-added notesMaster part into `presentation.xml`: a `notesMaster`
3976
- * relationship plus the single `p:notesMasterId` entry that `CT_NotesMasterIdList`
3977
- * allows. Returns the partname, so callers can use it as a rel target.
3978
- *
3979
- * Split out of {@link ensureNotesMaster} because the two ways a notesMaster arrives
3980
- * — copied from another `Presentation`, or authored by a generator and injected by
3981
- * {@link Presentation.appendSlides} — differ only in how the *part* is created, not in how it is
3982
- * registered.
3983
- */
3984
- function registerNotesMaster(dest, notesMasterPartName) {
3985
- const presPart = dest.presentationPart;
3986
- const relId = dest.opc.relationshipsFor(presPart.partName).add(NOTES_MASTER_REL, relativePartName(presPart.partName, notesMasterPartName)).id;
3987
- const root = presPart.dom.documentElement;
3988
- if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a notes master in");
3989
- const lst = getOrAddChild(root, "p:notesMasterIdLst", [
3990
- "p:handoutMasterIdLst",
3991
- "p:sldIdLst",
3992
- "p:sldSz",
3993
- "p:notesSz",
3994
- "p:embeddedFontLst",
3995
- "p:custShowLst",
3996
- "p:photoAlbum",
3997
- "p:custDataLst",
3998
- "p:kinsoku",
3999
- "p:defaultTextStyle",
4000
- "p:modifyVerifier",
4001
- "p:extLst"
4002
- ]);
4003
- removeChildrenByQName(lst, ["p:notesMasterId"]);
4004
- const entry = createElement(presPart.dom, "p:notesMasterId");
4005
- setAttr(entry, "r:id", relId);
4006
- lst.appendChild(entry);
4007
- presPart.markDirty();
4008
- return notesMasterPartName;
4009
- }
4010
- /**
4011
- * Resolve the notesMaster an *appended* slide's notes should bind to. Same
4012
- * single-notesMaster rule as {@link ensureNotesMaster}: this deck's own wins when it
4013
- * has one, so the destination's notes styling is preserved and `master.xml` is
4014
- * discarded. Otherwise the generator's notes master is installed, together with the
4015
- * theme its `.rels` requires (the normal write path emits that as `theme2.xml`).
4016
- */
4017
- function ensureNotesMasterFromXml(dest, master) {
4018
- const presPart = dest.presentationPart;
4019
- const presRels = dest.opc.relationshipsFor(presPart.partName);
4020
- const existing = presRels.byType(NOTES_MASTER_REL)[0];
4021
- if (existing) return presRels.resolveTarget(existing.id);
4022
- const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
4023
- dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$1.encode(master.xml));
4024
- const themePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
4025
- dest.opc.addPart(themePartName, THEME_CONTENT_TYPE, textEncoder$1.encode(master.themeXml));
4026
- dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, themePartName));
4027
- return registerNotesMaster(dest, masterPartName);
4028
- }
4029
- //#endregion
4030
4368
  //#region src/read/api/ops/prune.ts
4031
4369
  /**
4032
4370
  * Orphan pruning after a part stops being referenced.
@@ -4044,11 +4382,11 @@ function ensureNotesMasterFromXml(dest, master) {
4044
4382
  * orphan, even while momentarily unreferenced.
4045
4383
  */
4046
4384
  const SHARED_CHROME_CONTENT_TYPES = /* @__PURE__ */ new Set([
4047
- "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml",
4048
- "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml",
4049
- "application/vnd.openxmlformats-officedocument.theme+xml",
4385
+ SLIDE_MASTER_CONTENT_TYPE,
4386
+ SLIDE_LAYOUT_CONTENT_TYPE,
4387
+ THEME_CONTENT_TYPE,
4050
4388
  "application/vnd.openxmlformats-officedocument.themeOverride+xml",
4051
- "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml",
4389
+ NOTES_MASTER_CONTENT_TYPE,
4052
4390
  "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml",
4053
4391
  "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml",
4054
4392
  "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml",
@@ -4121,9 +4459,7 @@ function rescalePartGeometry(dest, rescaledParts, partName, transform) {
4121
4459
  if (rescaledParts.has(partName)) return;
4122
4460
  rescaledParts.add(partName);
4123
4461
  const part = dest.opc.part(partName);
4124
- const root = part?.dom.documentElement;
4125
- const cSld = root && firstChild(root, "p:cSld");
4126
- const spTree = cSld && firstChild(cSld, "p:spTree");
4462
+ const spTree = spTreeOf(part?.dom.documentElement);
4127
4463
  if (!part || !spTree) return;
4128
4464
  rescaleSpTree(spTree, transform);
4129
4465
  part.markDirty();
@@ -4144,13 +4480,18 @@ function rescalePartGeometry(dest, rescaledParts, partName, transform) {
4144
4480
  */
4145
4481
  const HYPERLINK_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
4146
4482
  const CHART_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
4483
+ const CHARTEX_REL = "http://schemas.microsoft.com/office/2014/relationships/chartEx";
4484
+ const CHART_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartStyle";
4485
+ const CHART_COLOR_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
4147
4486
  const PACKAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
4148
4487
  const AUDIO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
4149
4488
  const VIDEO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
4150
4489
  const MS_MEDIA_REL = "http://schemas.microsoft.com/office/2007/relationships/media";
4151
4490
  const SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
4152
- const NOTES_SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
4153
4491
  const CHART_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
4492
+ const CHARTEX_CONTENT_TYPE = "application/vnd.ms-office.chartex+xml";
4493
+ const CHART_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartstyle+xml";
4494
+ const CHART_COLOR_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartcolorstyle+xml";
4154
4495
  const XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
4155
4496
  /** Content type of the main part in an editable `.pptx` package. */
4156
4497
  const PRESENTATION_MAIN_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
@@ -4380,12 +4721,17 @@ var Presentation = class Presentation {
4380
4721
  * Duplicate the slide at `index` and insert the copy at `options.at` (deck
4381
4722
  * order; `0` = first), defaulting to appending at the end when `at` is omitted
4382
4723
  * or out of range. Returns the new slide. The new slide part copies the source
4383
- * bytes verbatim and shares the source's relationship targets (layout, images,
4384
- * …) by copying its `.rels`; a new presentation→slide relationship and a
4385
- * `p:sldId` entry are wired up. Marks the presentation part dirty.
4724
+ * bytes verbatim and shares the source's deck-wide relationship targets
4725
+ * (layout, images, media) by copying its `.rels`; a new presentation→slide
4726
+ * relationship and a `p:sldId` entry are wired up. Marks the presentation part
4727
+ * dirty.
4386
4728
  *
4387
- * Note: relationships are copied as-is, so a source slide that owns a
4388
- * one-to-one part (e.g. a notes slide) would end up shared with the clone.
4729
+ * What the source page *owns* is copied rather than shared: its notes slide,
4730
+ * charts, SmartArt diagrams and OLE embeddings, each with the subtree under it
4731
+ * (a chart's embedded workbook comes along; the image inside its user-shapes
4732
+ * drawing stays shared). PowerPoint refuses to open a deck where two slides
4733
+ * resolve to one chart or diagram, so this is not tidiness — see
4734
+ * `page-owned.ts` for the rule and the evidence behind it.
4389
4735
  */
4390
4736
  cloneSlide(index, options = {}) {
4391
4737
  const source = this.slides[index];
@@ -4394,8 +4740,10 @@ var Presentation = class Presentation {
4394
4740
  const sourcePart = source.part;
4395
4741
  const newPartName = opc.reservePartNameLike(sourcePart.partName);
4396
4742
  const newPart = opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
4397
- const sourceRels = opc.part(relsPartNameFor(sourcePart.partName));
4398
- if (sourceRels) opc.addPart(relsPartNameFor(newPartName), sourceRels.contentType, sourceRels.bytes);
4743
+ const sourceRels = opc.relationshipsFor(sourcePart.partName);
4744
+ const cloneRels = opc.relationshipsFor(newPartName);
4745
+ for (const rel of sourceRels) cloneRels.addWithId(rel.id, rel.type, rel.target, rel.targetMode);
4746
+ duplicateOwnedTargets(opc, sourcePart.partName, newPartName);
4399
4747
  return this.#insertSlidePart(newPart, options.at);
4400
4748
  }
4401
4749
  /**
@@ -4417,7 +4765,7 @@ var Presentation = class Presentation {
4417
4765
  const slideRels = this.opc.relationshipsFor(partName);
4418
4766
  const formerTargets = [...slideRels].filter((rel) => rel.targetMode !== "External").map((rel) => slideRels.resolveTarget(rel.id));
4419
4767
  const presPart = this.presentationPart;
4420
- const presRels = this.opc.relationshipsFor(presPart.partName);
4768
+ const presRels = presentationRels(this);
4421
4769
  const root = presPart.dom.documentElement;
4422
4770
  const sldIdLst = root && firstChild(root, "p:sldIdLst");
4423
4771
  if (sldIdLst) for (const sldId of getElements(sldIdLst, "p:sldId")) {
@@ -4448,7 +4796,10 @@ var Presentation = class Presentation {
4448
4796
  * Only the layout(s) actually used by imported slides are copied; the imported
4449
4797
  * master's `p:sldLayoutIdLst` is pruned to exactly those, mirroring how
4450
4798
  * PowerPoint's "Reuse Slides" brings a slide across. Parts shared by repeated
4451
- * imports from the same source deck are copied once and reused.
4799
+ * imports from the same source deck are copied once and reused — every part
4800
+ * except the page itself. Importing the same source slide twice yields two
4801
+ * independent copies over one shared layout/master/theme, which is what lets a
4802
+ * deck show one page twice (verbatim, then edited).
4452
4803
  *
4453
4804
  * With `{ theme: 'preserve' }` the slide's source theme is instead *flattened*
4454
4805
  * into the slide XML and the slide is bound to this deck's existing
@@ -4473,7 +4824,7 @@ var Presentation = class Presentation {
4473
4824
  }
4474
4825
  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)");
4475
4826
  const importCtx = this.#importContext(source.opc);
4476
- 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);
4827
+ 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);
4477
4828
  const newPart = this.opc.part(newPartName);
4478
4829
  if (!newPart) throw new InternalError("import/part-went-missing", `Imported slide part went missing: ${newPartName}`);
4479
4830
  if (sizesDiffer && options.rescale && target && incoming) rescaleImportedGeometry(this, this.#rescaledParts, newPartName, options.theme, incoming, target, options.rescale === true ? "fit" : options.rescale);
@@ -4483,6 +4834,140 @@ var Presentation = class Presentation {
4483
4834
  return slide;
4484
4835
  }
4485
4836
  /**
4837
+ * Import selected pages from one or more loaded source presentations as one
4838
+ * batch. Each imported page lands at its `outputIndex` in the complete
4839
+ * destination slide list after the batch, and the returned array is parallel
4840
+ * to `requests` — `result[i]` is the page `requests[i]` asked for, whatever
4841
+ * order the output positions were given in.
4842
+ *
4843
+ * Everything is checked before a single byte of this deck moves: every request
4844
+ * names an existing source page, final output positions are unique, each source
4845
+ * has this deck's slide size, and a read-only dry run of the copy proves every
4846
+ * part it would reach is present. A batch therefore either applies in full or
4847
+ * leaves this deck byte-identical, where a per-page loop of
4848
+ * {@link importSlide} can leave a half-stitched deck behind.
4849
+ *
4850
+ * The batch also decides what a `slide → slide` link means: an internal link
4851
+ * on a selected page must target another **selected** page (or one this deck
4852
+ * already contains via an earlier import from that source), and is rewritten
4853
+ * to the fresh partname — importing page 3 of 10 does not drag pages 1–2
4854
+ * across as dependencies, and never strands the link. This mirrors
4855
+ * `appendSlides`' `import/unresolved-slide-link`, which the write side already
4856
+ * enforces for generator decks.
4857
+ *
4858
+ * One request is one output page, so naming the same source page in several
4859
+ * requests is how you ask for several independent copies of it — the page part
4860
+ * is the one thing an import never shares, exactly as in {@link importSlide}.
4861
+ * Everything under it (layout, master, theme, media) is still copied once and
4862
+ * shared. Where such a page is the target of a `slide → slide` link, the link
4863
+ * resolves to one of its copies: pages duplicated together are copied in
4864
+ * lockstep and link to their round-mates, and a link into a page requested only
4865
+ * once always lands on that single copy.
4866
+ *
4867
+ * Speaker notes travel per request: `{ importNotes: true }` carries that page's
4868
+ * `notesSlide` part across, wired to the new page and bound to a single
4869
+ * `notesMaster` under the same 0..1 rule {@link importSlide} and
4870
+ * {@link appendSlides} follow — the destination's own master wins when it has
4871
+ * one. Notes are part of the up-front dry run too, so a batch that would fail
4872
+ * carrying them is refused with the deck still byte-identical. A page named in
4873
+ * several requests gets its own copy of its notes each time, as of everything
4874
+ * else that page owns.
4875
+ *
4876
+ * Scope: pages come across under `'copy'` theme semantics (their own layout →
4877
+ * master → theme subgraph, shared parts deduped via the copy registry).
4878
+ * Embedded fonts are not carried, and sizes must match, since there is no batch
4879
+ * spelling for `embedFonts` or `rescale`. Use {@link importSlide} when you need
4880
+ * either of those.
4881
+ */
4882
+ importSlides(requests) {
4883
+ const resolved = requests.map((request, requestIndex) => {
4884
+ 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}`);
4885
+ 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}`);
4886
+ const sourceSlide = request.source.slides[request.sourceIndex];
4887
+ if (!sourceSlide) throw new InvalidOptionError("slide/index-out-of-range", `importSlides: no slide at index ${request.sourceIndex} to import`);
4888
+ return {
4889
+ ...request,
4890
+ requestIndex,
4891
+ sourceSlide
4892
+ };
4893
+ });
4894
+ if (resolved.length === 0) return [];
4895
+ if (!this.presentationPart.dom.documentElement) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to insert slides into");
4896
+ const finalSlideCount = this.slides.length + resolved.length;
4897
+ const selectedPages = /* @__PURE__ */ new Map();
4898
+ const notesPages = /* @__PURE__ */ new Map();
4899
+ for (const request of resolved) {
4900
+ let pages = selectedPages.get(request.source.opc);
4901
+ if (!pages) {
4902
+ pages = /* @__PURE__ */ new Set();
4903
+ selectedPages.set(request.source.opc, pages);
4904
+ }
4905
+ pages.add(request.sourceSlide.partName);
4906
+ if (!request.importNotes) continue;
4907
+ let withNotes = notesPages.get(request.source.opc);
4908
+ if (!withNotes) {
4909
+ withNotes = /* @__PURE__ */ new Set();
4910
+ notesPages.set(request.source.opc, withNotes);
4911
+ }
4912
+ withNotes.add(request.sourceSlide.partName);
4913
+ }
4914
+ const target = this.slideSize;
4915
+ const outputIndexes = /* @__PURE__ */ new Set();
4916
+ for (const request of resolved) {
4917
+ 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`);
4918
+ if (outputIndexes.has(request.outputIndex)) throw new InvalidOptionError("import/output-index-conflict", `importSlides: outputIndex ${request.outputIndex} is requested more than once`);
4919
+ outputIndexes.add(request.outputIndex);
4920
+ const incoming = request.source.slideSize;
4921
+ if (!target || !incoming || target.widthEmu !== incoming.widthEmu || target.heightEmu !== incoming.heightEmu) {
4922
+ const fmt = (size) => size ? `${size.widthEmu}×${size.heightEmu} EMU` : "unknown";
4923
+ throw new InvalidOptionError("import/slide-size-mismatch", `importSlides requires equal slide sizes; target is ${fmt(target)}, source is ${fmt(incoming)}`);
4924
+ }
4925
+ }
4926
+ const copyMaster = this.opc.relationshipsFor(this.presentationPart.partName).byType(NOTES_MASTER_REL).length === 0;
4927
+ for (const [sourceOpc, pages] of selectedPages) checkSelectionCopyable(sourceOpc, this.#importContext(sourceOpc).registry, pages, {
4928
+ pages: notesPages.get(sourceOpc) ?? /* @__PURE__ */ new Set(),
4929
+ copyMaster
4930
+ });
4931
+ const destinationsBySource = /* @__PURE__ */ new Map();
4932
+ const planned = resolved.map((request) => {
4933
+ let destinations = destinationsBySource.get(request.source.opc);
4934
+ if (!destinations) {
4935
+ destinations = /* @__PURE__ */ new Map();
4936
+ destinationsBySource.set(request.source.opc, destinations);
4937
+ }
4938
+ const sourcePart = request.sourceSlide.part;
4939
+ const newPartName = this.opc.reservePartNameLike(request.sourceSlide.partName);
4940
+ const destPart = this.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
4941
+ const reserved = destinations.get(request.sourceSlide.partName);
4942
+ if (reserved) reserved.push(newPartName);
4943
+ else destinations.set(request.sourceSlide.partName, [newPartName]);
4944
+ return {
4945
+ ...request,
4946
+ destPart
4947
+ };
4948
+ });
4949
+ for (const [sourceOpc, destinations] of destinationsBySource) {
4950
+ const base = this.#importContext(sourceOpc);
4951
+ const rounds = Math.max(...[...destinations.values()].map((reserved) => reserved.length));
4952
+ for (let round = 0; round < rounds; round++) {
4953
+ const plan = /* @__PURE__ */ new Map();
4954
+ for (const [sourcePartName, reserved] of destinations) plan.set(sourcePartName, reserved[round] ?? reserved[0]);
4955
+ const ctx = {
4956
+ ...base,
4957
+ selection: { destinations: plan }
4958
+ };
4959
+ for (const [sourcePartName, reserved] of destinations) if (round < reserved.length) copyPart(ctx, sourcePartName);
4960
+ }
4961
+ }
4962
+ for (const request of planned) {
4963
+ if (!request.importNotes) continue;
4964
+ carryNotes(this, request.source, this.#importContext(request.source.opc), request.sourceSlide.partName, request.destPart.partName);
4965
+ }
4966
+ const added = [];
4967
+ for (const request of [...planned].sort((left, right) => left.outputIndex - right.outputIndex)) added[request.requestIndex] = this.#insertSlidePart(request.destPart, request.outputIndex);
4968
+ return added;
4969
+ }
4970
+ /**
4486
4971
  * Graft slide master(s) from another open package into this one and return what
4487
4972
  * was copied. Unlike {@link importSlide} — which brings a master across only as
4488
4973
  * the dependency of an imported *slide* and prunes it to the one layout that
@@ -4592,7 +5077,10 @@ var Presentation = class Presentation {
4592
5077
  *
4593
5078
  * Charts and internal slide-to-slide hyperlinks are carried across: chart parts
4594
5079
  * (chart XML + `.rels` + embedded workbook) are injected under fresh names, and a
4595
- * `slide:N` link is repointed at the Nth appended slide's new partname.
5080
+ * `slide:N` link is repointed at the Nth appended slide's new partname. A chartEx
5081
+ * (Office 2016 — waterfall, funnel, treemap, ...) chart carries too, as its own
5082
+ * `chartEx{N}.xml` part behind the MS chartEx rel, with the style and color-style
5083
+ * sidecars PowerPoint requires alongside it.
4596
5084
  *
4597
5085
  * The generator's presentation-level embedded fonts (`pptx.embedFont`) are also
4598
5086
  * carried into this deck and merged into its `p:embeddedFontLst`, de-duped by
@@ -4688,13 +5176,23 @@ var Presentation = class Presentation {
4688
5176
  for (const h of slide.notes.hyperlinks) notesRels.addWithId(`rId${h.rId}`, HYPERLINK_REL, h.target, "External");
4689
5177
  }
4690
5178
  for (const c of slide.charts) {
4691
- const chartPartName = this.opc.reservePartNameLike("/ppt/charts/chart1.xml");
4692
- this.opc.addPart(chartPartName, CHART_CONTENT_TYPE, textEncoder.encode(c.chartXml));
5179
+ const isChartEx = c.chartEx !== void 0;
5180
+ const chartPartName = this.opc.reservePartNameLike(isChartEx ? "/ppt/charts/chartEx1.xml" : "/ppt/charts/chart1.xml");
5181
+ this.opc.addPart(chartPartName, isChartEx ? CHARTEX_CONTENT_TYPE : CHART_CONTENT_TYPE, textEncoder.encode(c.chartXml));
4693
5182
  const embeddingPartName = this.opc.reservePartNameLike("/ppt/embeddings/Microsoft_Excel_Worksheet1.xlsx");
4694
5183
  this.opc.contentTypes.ensureDefault("xlsx", XLSX_CONTENT_TYPE);
4695
5184
  this.opc.addPart(embeddingPartName, XLSX_CONTENT_TYPE, c.embeddingBytes);
4696
- this.opc.relationshipsFor(chartPartName).addWithId("rId1", PACKAGE_REL, relativePartName(chartPartName, embeddingPartName));
4697
- rels.addWithId(`rId${c.rId}`, CHART_REL, relativePartName(partName, chartPartName));
5185
+ const chartRels = this.opc.relationshipsFor(chartPartName);
5186
+ chartRels.addWithId("rId1", PACKAGE_REL, relativePartName(chartPartName, embeddingPartName));
5187
+ if (c.chartEx) {
5188
+ const colorsPartName = this.opc.reservePartNameLike("/ppt/charts/colors1.xml");
5189
+ this.opc.addPart(colorsPartName, CHART_COLOR_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.colorsXml));
5190
+ chartRels.addWithId("rId2", CHART_COLOR_STYLE_REL, relativePartName(chartPartName, colorsPartName));
5191
+ const stylePartName = this.opc.reservePartNameLike("/ppt/charts/style1.xml");
5192
+ this.opc.addPart(stylePartName, CHART_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.styleXml));
5193
+ chartRels.addWithId("rId3", CHART_STYLE_REL, relativePartName(chartPartName, stylePartName));
5194
+ }
5195
+ rels.addWithId(`rId${c.rId}`, isChartEx ? CHARTEX_REL : CHART_REL, relativePartName(partName, chartPartName));
4698
5196
  }
4699
5197
  for (const link of slide.slideLinks) {
4700
5198
  const targetPartName = partBySourceNumber.get(link.sourceSlideNumber);
@@ -4777,7 +5275,7 @@ var Presentation = class Presentation {
4777
5275
  const result = [];
4778
5276
  for (const shapeEl of sourceElements) {
4779
5277
  const imported = targetDoc.importNode(shapeEl, true);
4780
- rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap);
5278
+ rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap, newOwnedScope());
4781
5279
  if (ctx) {
4782
5280
  const holder = createElement(targetDoc, "p:spTree");
4783
5281
  holder.appendChild(imported);
@@ -4839,7 +5337,13 @@ var Presentation = class Presentation {
4839
5337
  */
4840
5338
  #insertSlidePart(newPart, at) {
4841
5339
  const presPart = this.presentationPart;
4842
- const relId = this.opc.relationshipsFor(presPart.partName).add(SLIDE_REL, relativePartName(presPart.partName, newPart.partName)).id;
5340
+ const presRels = presentationRels(this);
5341
+ for (const rel of presRels) {
5342
+ if (rel.type !== "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" || rel.targetMode === "External") continue;
5343
+ if (presRels.resolveTarget(rel.id) !== newPart.partName) continue;
5344
+ 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`);
5345
+ }
5346
+ const relId = presRels.add(SLIDE_REL, relativePartName(presPart.partName, newPart.partName)).id;
4843
5347
  const root = presPart.dom.documentElement;
4844
5348
  if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to append a slide to");
4845
5349
  const sldIdLst = getOrAddChild(root, "p:sldIdLst", PRESENTATION_SLD_ID_LST_SUCCESSORS);
@@ -4873,4 +5377,4 @@ var Presentation = class Presentation {
4873
5377
  //#endregion
4874
5378
  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 };
4875
5379
 
4876
- //# sourceMappingURL=presentation-CxnUVhK9.js.map
5380
+ //# sourceMappingURL=presentation-3rAKKRfm.js.map