@shbernal/ts-pptx 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -20
- package/dist/{autopage-QQyfRtoK.js → autopage-D1sqK6j7.js} +16 -15
- package/dist/{autopage-QQyfRtoK.js.map → autopage-D1sqK6j7.js.map} +1 -1
- package/dist/browser.d.ts +6 -6
- package/dist/browser.js +5 -6
- package/dist/browser.js.map +1 -1
- package/dist/{diagnostics-Chjz_4_Q.d.ts → diagnostics-B2yWpcjB.d.ts} +31 -14
- package/dist/{diagnostics-Chjz_4_Q.d.ts.map → diagnostics-B2yWpcjB.d.ts.map} +1 -1
- package/dist/{diagnostics-DYQC7xLV.js → diagnostics-H1QzgRSw.js} +0 -0
- package/dist/{diagnostics-DYQC7xLV.js.map → diagnostics-H1QzgRSw.js.map} +1 -1
- package/dist/{enums-CoXnxiCI.d.ts → enums-CvRYniUA.d.ts} +13 -3
- package/dist/{enums-CoXnxiCI.d.ts.map → enums-CvRYniUA.d.ts.map} +1 -1
- package/dist/{errors-BVLCFr-t.d.ts → errors-fB1KDJKQ.d.ts} +5 -5
- package/dist/{errors-BVLCFr-t.d.ts.map → errors-fB1KDJKQ.d.ts.map} +1 -1
- package/dist/{fit-DhMbQG5O.js → fit-CYJ1jkQI.js} +0 -0
- package/dist/fit-CYJ1jkQI.js.map +1 -0
- package/dist/{html-dom-DajBuobe.js → html-dom-CzewEdE1.js} +4 -5
- package/dist/{html-dom-DajBuobe.js.map → html-dom-CzewEdE1.js.map} +1 -1
- package/dist/html.d.ts +3 -3
- package/dist/html.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +1 -1
- package/dist/inspect.js +4 -4
- package/dist/math.d.ts +1 -1
- package/dist/measure.d.ts +96 -6
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +2 -2
- package/dist/node.d.ts +6 -6
- package/dist/node.js +4 -5
- package/dist/node.js.map +1 -1
- package/dist/notes-SCvpJz0l.js +1646 -0
- package/dist/notes-SCvpJz0l.js.map +1 -0
- package/dist/{pres-C-EU3SkN.d.ts → pres-Cb-1xDne.d.ts} +4 -4
- package/dist/{pres-C-EU3SkN.d.ts.map → pres-Cb-1xDne.d.ts.map} +1 -1
- package/dist/{presentation-CxnUVhK9.js → presentation-DeKTJGHf.js} +1002 -422
- package/dist/presentation-DeKTJGHf.js.map +1 -0
- package/dist/{presentation-Osx1cDdJ.d.ts → presentation-types-B50aOUFI.d.ts} +679 -11
- package/dist/presentation-types-B50aOUFI.d.ts.map +1 -0
- package/dist/read.d.ts +4 -5
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +2 -2
- package/dist/script.d.ts +2 -2
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +0 -0
- package/dist/script.js.map +1 -1
- package/dist/{shapes-DoJBPrwN.js → shapes-BXVgsk0F.js} +3 -3
- package/dist/{shapes-DoJBPrwN.js.map → shapes-BXVgsk0F.js.map} +1 -1
- package/dist/{slide-BTfn1Pyp.d.ts → slide-DWw9UoI6.d.ts} +39 -3
- package/dist/slide-DWw9UoI6.d.ts.map +1 -0
- package/dist/{st-enums-evQG2A7Z.js → st-enums-bVnezkMJ.js} +33 -12
- package/dist/{st-enums-evQG2A7Z.js.map → st-enums-bVnezkMJ.js.map} +1 -1
- package/dist/{text-C4P9MNdf.js → text-uM-zGton.js} +219 -1668
- package/dist/text-uM-zGton.js.map +1 -0
- package/dist/{units-internal-CR8s8YQa.js → units-internal-CGf4VrXy.js} +156 -4
- package/dist/units-internal-CGf4VrXy.js.map +1 -0
- package/dist/zip.d.ts +2 -2
- package/package.json +36 -27
- package/dist/embedded-fonts-MaynAHa9.js +0 -94
- package/dist/embedded-fonts-MaynAHa9.js.map +0 -1
- package/dist/fit-DhMbQG5O.js.map +0 -1
- package/dist/presentation-CxnUVhK9.js.map +0 -1
- package/dist/presentation-Osx1cDdJ.d.ts.map +0 -1
- package/dist/presentation-types-Dj4riuDs.d.ts +0 -538
- package/dist/presentation-types-Dj4riuDs.d.ts.map +0 -1
- package/dist/slide-BTfn1Pyp.d.ts.map +0 -1
- package/dist/text-C4P9MNdf.js.map +0 -1
- package/dist/units-C7QM1KgH.js +0 -144
- package/dist/units-C7QM1KgH.js.map +0 -1
- 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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { ft as emuToInches } from "./units-internal-CGf4VrXy.js";
|
|
3
|
+
import { E as voidEl, T as raw, _ as FONT_DATA_EXTENSION, g as FONT_DATA_CONTENT_TYPE, h as EMBEDDED_FONT_SLOTS, n as makeXmlNotesMaster, o as makeXmlNotesSlideSkeleton, v as FONT_REL_TYPE, w as el } from "./notes-SCvpJz0l.js";
|
|
4
|
+
import { _ as SLIDE_LAYOUT_REL, b as THEME_REL, d as IMAGE_REL, f as NOTES_MASTER_REL, h as OFFICE_REL, m as OFFICE_DOCUMENT_REL, p as NOTES_SLIDE_REL, v as SLIDE_MASTER_REL, y as SLIDE_REL } from "./st-enums-bVnezkMJ.js";
|
|
5
5
|
import { ZipWriter, readZip } from "./zip.js";
|
|
6
|
-
import { $ as 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-
|
|
6
|
+
import { $ as parseClrScheme, A as readGradientFill, At as getElements, B as placeholderInheritedAnchor, Bt as setAttr, Ct as boolValue, D as spPrXfrmEmu, Dt as escapeXmlAttribute, E as TextFrame, Et as descendantsByTag, F as resolveSlideColorContext, Ft as parseXml, G as placeholderOf, H as placeholderInheritedListStyles, I as resolveSlideThemeParts, It as removeAttr, J as SCHEME_SLOTS, K as slideDefinesColor, Lt as removeChildrenByQName, M as resolveLayoutColorContext, Mt as insertInOrder, N as resolveMasterColorContext, Nt as intValue, O as readPatternFill, Ot as firstChild, P as resolveNotesColorContext, Pt as ownerDocumentOf, Q as parseClrMap, R as LST_STYLE_LEVELS, Rt as replaceInParent, S as AutoShape, St as attr, Tt as createElement, U as placeholderInheritedRunProps, V as placeholderInheritedColor, W as placeholderInheritedXfrm, X as intAttr, Y as fmtEntry, Z as isA, _t as relsPartNameFor, at as FILL_CHOICES, bt as zipPathToPartName, ct as SPPR_AFTER_XFRM, dt as SPPR_LN_AFTER, et as resolveColor, ft as SPPR_SCENE3D_AFTER, gt as relativePartName, ht as partNameToZipPath, it as substitutePhClr, j as resolveColorElement, jt as getOrAddChild, k as readPictureFill, kt as firstChildElement, l as wrapShapeElement, lt as SPPR_EFFECT_AFTER, mt as partNameExtension, n as buildShapes, nt as styleRefFill, ot as RPR_FILL_AFTER, pt as SPPR_SP3D_AFTER, q as slideDefinesProp, r as findShapeByIdDeep, rt as styleRefLine, st as SHAPE_AFTER_SPPR, t as GroupShape, tt as resolveSchemeToken, u as GraphicFrame, ut as SPPR_FILL_AFTER, vt as resolveRelativePartName, wt as childElements, x as Picture, xt as OOXML_NS, yt as resolveSingleRel, z as RUN_PROP_NAMES, zt as serializeXml } from "./shapes-BXVgsk0F.js";
|
|
7
7
|
//#region src/read/opc/content-types.ts
|
|
8
8
|
/**
|
|
9
9
|
* Overlay over `[Content_Types].xml`, used to resolve part content types at
|
|
@@ -78,7 +78,7 @@ var ContentTypes = class ContentTypes {
|
|
|
78
78
|
//#endregion
|
|
79
79
|
//#region src/read/opc/part.ts
|
|
80
80
|
const textDecoder$1 = new TextDecoder("utf-8");
|
|
81
|
-
const textEncoder$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
}
|
|
@@ -1146,12 +1146,716 @@ var NotesSlide = class {
|
|
|
1146
1146
|
#byType(type) {
|
|
1147
1147
|
return this.placeholders.find((ph) => ph.type === type) ?? null;
|
|
1148
1148
|
}
|
|
1149
|
-
#spTree() {
|
|
1150
|
-
const root = this.part.dom.documentElement;
|
|
1151
|
-
const cSld = root && firstChild(root, "p:cSld");
|
|
1152
|
-
return cSld ? firstChild(cSld, "p:spTree") : null;
|
|
1149
|
+
#spTree() {
|
|
1150
|
+
const root = this.part.dom.documentElement;
|
|
1151
|
+
const cSld = root && firstChild(root, "p:cSld");
|
|
1152
|
+
return cSld ? firstChild(cSld, "p:spTree") : null;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/read/api/ops/master-registry.ts
|
|
1157
|
+
/**
|
|
1158
|
+
* Slide-master bookkeeping in `presentation.xml` and in a master's own
|
|
1159
|
+
* `p:sldLayoutIdLst`: registering a master so renderers treat it as active,
|
|
1160
|
+
* promoting masters to the front of the id list, allocating ids out of the shared
|
|
1161
|
+
* master/layout id space, and emptying a freshly-copied master's layout list.
|
|
1162
|
+
*
|
|
1163
|
+
* These are the destination-side half of the import machinery -- they read and
|
|
1164
|
+
* write only this deck, never a source package and never the copy registry -- so
|
|
1165
|
+
* they take a structural {@link DeckTarget} rather than a `Presentation`. The
|
|
1166
|
+
* source-side half that drives most of them is `part-copy.ts`.
|
|
1167
|
+
*/
|
|
1168
|
+
/**
|
|
1169
|
+
* Wire a freshly-copied slide master into `presentation.xml`: add a
|
|
1170
|
+
* presentation→master relationship and a `p:sldMasterId` entry in
|
|
1171
|
+
* `p:sldMasterIdLst`. A master that is reachable only through the
|
|
1172
|
+
* slide→layout→master rel chain but absent from `p:sldMasterIdLst` is treated
|
|
1173
|
+
* as inactive by renderers, so its background/graphics never paint. No-op when
|
|
1174
|
+
* the master is already registered (shared across imports from one source).
|
|
1175
|
+
*/
|
|
1176
|
+
function registerMaster(dest, masterPartName) {
|
|
1177
|
+
const presPart = dest.presentationPart;
|
|
1178
|
+
const presRels = dest.opc.relationshipsFor(presPart.partName);
|
|
1179
|
+
for (const rel of presRels.byType(SLIDE_MASTER_REL)) if (presRels.resolveTarget(rel.id) === masterPartName) return;
|
|
1180
|
+
const relId = presRels.add(SLIDE_MASTER_REL, relativePartName(presPart.partName, masterPartName)).id;
|
|
1181
|
+
const root = presPart.dom.documentElement;
|
|
1182
|
+
if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a master in");
|
|
1183
|
+
const lst = getOrAddChild(root, "p:sldMasterIdLst", [
|
|
1184
|
+
"p:notesMasterIdLst",
|
|
1185
|
+
"p:handoutMasterIdLst",
|
|
1186
|
+
"p:sldIdLst",
|
|
1187
|
+
"p:sldSz",
|
|
1188
|
+
"p:notesSz",
|
|
1189
|
+
"p:embeddedFontLst",
|
|
1190
|
+
"p:custShowLst",
|
|
1191
|
+
"p:photoAlbum",
|
|
1192
|
+
"p:custDataLst",
|
|
1193
|
+
"p:kinsoku",
|
|
1194
|
+
"p:defaultTextStyle",
|
|
1195
|
+
"p:modifyVerifier",
|
|
1196
|
+
"p:extLst"
|
|
1197
|
+
]);
|
|
1198
|
+
const entry = createElement(presPart.dom, "p:sldMasterId");
|
|
1199
|
+
setAttr(entry, "id", String(nextMasterLayoutId(dest)));
|
|
1200
|
+
setAttr(entry, "r:id", relId);
|
|
1201
|
+
lst.appendChild(entry);
|
|
1202
|
+
presPart.markDirty();
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Move the `p:sldMasterId` entries for `masterPartNames` to the front of
|
|
1206
|
+
* `p:sldMasterIdLst`, preserving their existing relative order, so the first of
|
|
1207
|
+
* them becomes the deck's `Designs(1)` — the theme PowerPoint's Design tab shows.
|
|
1208
|
+
* Reorders the id list only; relationships, ids, and all other parts are left as
|
|
1209
|
+
* they are. A no-op when the named masters already lead (idempotent re-call).
|
|
1210
|
+
*/
|
|
1211
|
+
function promoteMasters(dest, masterPartNames) {
|
|
1212
|
+
if (masterPartNames.length === 0) return;
|
|
1213
|
+
const presPart = dest.presentationPart;
|
|
1214
|
+
const root = presPart.dom.documentElement;
|
|
1215
|
+
const lst = root && firstChild(root, "p:sldMasterIdLst");
|
|
1216
|
+
if (!lst) return;
|
|
1217
|
+
const rels = dest.opc.relationshipsFor(presPart.partName);
|
|
1218
|
+
const promote = new Set(masterPartNames);
|
|
1219
|
+
const entries = getElements(lst, "p:sldMasterId");
|
|
1220
|
+
const isPromoted = (entry) => {
|
|
1221
|
+
const relId = attr(entry, "r:id");
|
|
1222
|
+
return relId ? promote.has(rels.resolveTarget(relId)) : false;
|
|
1223
|
+
};
|
|
1224
|
+
const desired = [...entries.filter(isPromoted), ...entries.filter((e) => !isPromoted(e))];
|
|
1225
|
+
if (desired.every((entry, i) => entry === entries[i])) return;
|
|
1226
|
+
for (const entry of desired) lst.appendChild(entry);
|
|
1227
|
+
presPart.markDirty();
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* The next free id in the presentation-wide slide-master / slide-layout id space.
|
|
1231
|
+
*
|
|
1232
|
+
* `p:sldMasterId/@id` and every master's `p:sldLayoutId/@id` draw from ONE shared
|
|
1233
|
+
* id space (both ST_SlideMasterId/ST_SlideLayoutId, floor 2147483648) and must be
|
|
1234
|
+
* unique across the WHOLE presentation, not just within their own list. A duplicate
|
|
1235
|
+
* anywhere in this space makes PowerPoint report the file as corrupt on open
|
|
1236
|
+
* ("found a problem with content" / 0x80070570), even though LibreOffice silently
|
|
1237
|
+
* tolerates it — so an imported master AND each of its layouts must take an id past
|
|
1238
|
+
* the max of both the master-id list and every layout-id list. Recomputed per
|
|
1239
|
+
* allocation so ids appended earlier in the same import are counted.
|
|
1240
|
+
*/
|
|
1241
|
+
function nextMasterLayoutId(dest) {
|
|
1242
|
+
let max = 2147483647;
|
|
1243
|
+
const presPart = dest.presentationPart;
|
|
1244
|
+
const root = presPart.dom.documentElement;
|
|
1245
|
+
const masterLst = root && firstChild(root, "p:sldMasterIdLst");
|
|
1246
|
+
if (masterLst) for (const entry of getElements(masterLst, "p:sldMasterId")) {
|
|
1247
|
+
const id = intValue(attr(entry, "id"));
|
|
1248
|
+
if (id !== null && id > max) max = id;
|
|
1249
|
+
}
|
|
1250
|
+
const presRels = dest.opc.relationshipsFor(presPart.partName);
|
|
1251
|
+
for (const rel of presRels.byType(SLIDE_MASTER_REL)) {
|
|
1252
|
+
const masterRoot = dest.opc.part(presRels.resolveTarget(rel.id))?.dom.documentElement;
|
|
1253
|
+
const layoutLst = masterRoot && firstChild(masterRoot, "p:sldLayoutIdLst");
|
|
1254
|
+
if (!layoutLst) continue;
|
|
1255
|
+
for (const entry of getElements(layoutLst, "p:sldLayoutId")) {
|
|
1256
|
+
const id = intValue(attr(entry, "id"));
|
|
1257
|
+
if (id !== null && id > max) max = id;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return max + 1;
|
|
1261
|
+
}
|
|
1262
|
+
/** Empty a freshly-copied master's `p:sldLayoutIdLst`; copied layouts repopulate it. */
|
|
1263
|
+
function clearLayoutIdList(dest, masterPartName) {
|
|
1264
|
+
const masterPart = dest.opc.part(masterPartName);
|
|
1265
|
+
const root = masterPart?.dom.documentElement;
|
|
1266
|
+
const lst = root && firstChild(root, "p:sldLayoutIdLst");
|
|
1267
|
+
if (!masterPart || !lst) return;
|
|
1268
|
+
removeChildrenByQName(lst, ["p:sldLayoutId"]);
|
|
1269
|
+
masterPart.markDirty();
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Append a `p:sldLayoutId` entry for `layoutPartName` to `masterPartName`'s
|
|
1273
|
+
* `p:sldLayoutIdLst`, adding the master→layout relationship it points at. The
|
|
1274
|
+
* destination-side half of linking a copied layout into its copied master; the
|
|
1275
|
+
* caller resolves which master that is from the source-side rels.
|
|
1276
|
+
*/
|
|
1277
|
+
function addLayoutToMaster(dest, masterPartName, layoutPartName) {
|
|
1278
|
+
const masterPart = dest.opc.part(masterPartName);
|
|
1279
|
+
const root = masterPart?.dom.documentElement;
|
|
1280
|
+
if (!masterPart || !root) return;
|
|
1281
|
+
const relId = dest.opc.relationshipsFor(masterPartName).add(SLIDE_LAYOUT_REL, relativePartName(masterPartName, layoutPartName)).id;
|
|
1282
|
+
const lst = getOrAddChild(root, "p:sldLayoutIdLst", [
|
|
1283
|
+
"p:transition",
|
|
1284
|
+
"p:timing",
|
|
1285
|
+
"p:hf",
|
|
1286
|
+
"p:txStyles",
|
|
1287
|
+
"p:extLst"
|
|
1288
|
+
]);
|
|
1289
|
+
const entry = createElement(masterPart.dom, "p:sldLayoutId");
|
|
1290
|
+
setAttr(entry, "id", String(nextMasterLayoutId(dest)));
|
|
1291
|
+
setAttr(entry, "r:id", relId);
|
|
1292
|
+
lst.appendChild(entry);
|
|
1293
|
+
masterPart.markDirty();
|
|
1294
|
+
}
|
|
1295
|
+
//#endregion
|
|
1296
|
+
//#region src/read/api/ops/page-owned.ts
|
|
1297
|
+
/** Relationship types whose target two copies of one page may point at together. */
|
|
1298
|
+
const SHARED_BY_PAGE_COPIES = /* @__PURE__ */ new Set([
|
|
1299
|
+
SLIDE_LAYOUT_REL,
|
|
1300
|
+
SLIDE_MASTER_REL,
|
|
1301
|
+
NOTES_MASTER_REL,
|
|
1302
|
+
THEME_REL,
|
|
1303
|
+
OFFICE_REL + "themeOverride",
|
|
1304
|
+
OFFICE_REL + "handoutMaster",
|
|
1305
|
+
OFFICE_REL + "presProps",
|
|
1306
|
+
OFFICE_REL + "viewProps",
|
|
1307
|
+
OFFICE_REL + "tableStyles",
|
|
1308
|
+
OFFICE_REL + "commentAuthors",
|
|
1309
|
+
"http://schemas.microsoft.com/office/2018/10/relationships/authors",
|
|
1310
|
+
IMAGE_REL,
|
|
1311
|
+
OFFICE_REL + "audio",
|
|
1312
|
+
OFFICE_REL + "video",
|
|
1313
|
+
"http://schemas.microsoft.com/office/2007/relationships/media",
|
|
1314
|
+
"http://schemas.microsoft.com/office/2017/06/relationships/model3d",
|
|
1315
|
+
OFFICE_REL + "font",
|
|
1316
|
+
SLIDE_REL,
|
|
1317
|
+
OFFICE_REL + "hyperlink"
|
|
1318
|
+
]);
|
|
1319
|
+
/**
|
|
1320
|
+
* Whether a page copy may point at this relationship's target rather than taking
|
|
1321
|
+
* a copy of it. Everything not listed is owned by the page and copied — see the
|
|
1322
|
+
* module comment for why that is the safe default.
|
|
1323
|
+
*/
|
|
1324
|
+
function isSharedByPageCopies(relType) {
|
|
1325
|
+
return SHARED_BY_PAGE_COPIES.has(relType);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Give a just-cloned page its own copy of every part the source page owned,
|
|
1329
|
+
* within one package. The clone's relationships start as a byte copy of the
|
|
1330
|
+
* source page's, so this walks them and repoints the owned ones at fresh parts;
|
|
1331
|
+
* shared targets are left alone.
|
|
1332
|
+
*
|
|
1333
|
+
* The subtree under each owned part is copied the same way, so a chart's workbook
|
|
1334
|
+
* and user-shapes drawing come along while the image inside that drawing stays
|
|
1335
|
+
* shared. A relationship *back* to the source page — a notes slide names the
|
|
1336
|
+
* slide it annotates — is repointed at the clone, which is what makes the copied
|
|
1337
|
+
* notes belong to it.
|
|
1338
|
+
*
|
|
1339
|
+
* A dangling relationship is left dangling rather than made to throw: cloning a
|
|
1340
|
+
* damaged deck is not this function's problem to discover.
|
|
1341
|
+
*
|
|
1342
|
+
* @param opc the package both pages live in
|
|
1343
|
+
* @param sourcePartName partname of the page that was cloned
|
|
1344
|
+
* @param clonePartName partname of the clone, whose rels are still the source's
|
|
1345
|
+
*/
|
|
1346
|
+
function duplicateOwnedTargets(opc, sourcePartName, clonePartName) {
|
|
1347
|
+
const copies = /* @__PURE__ */ new Map([[sourcePartName, clonePartName]]);
|
|
1348
|
+
const rels = opc.relationshipsFor(clonePartName);
|
|
1349
|
+
for (const rel of Array.from(rels)) {
|
|
1350
|
+
if (rel.targetMode === "External" || isSharedByPageCopies(rel.type)) continue;
|
|
1351
|
+
const target = rels.resolveTarget(rel.id);
|
|
1352
|
+
const fresh = duplicateSubtree(opc, target, copies);
|
|
1353
|
+
if (fresh === target) continue;
|
|
1354
|
+
rels.remove(rel.id);
|
|
1355
|
+
rels.addWithId(rel.id, rel.type, relativePartName(clonePartName, fresh));
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Copy `partName` and, recursively, every part it owns, into fresh partnames.
|
|
1360
|
+
* `copies` dedupes within the one page copy, so a part two of the page's
|
|
1361
|
+
* relationships reach is copied once here even though the next page copy gets its
|
|
1362
|
+
* own. Returns the copy's partname, or `partName` unchanged when there is no such
|
|
1363
|
+
* part to copy.
|
|
1364
|
+
*/
|
|
1365
|
+
function duplicateSubtree(opc, partName, copies) {
|
|
1366
|
+
const already = copies.get(partName);
|
|
1367
|
+
if (already !== void 0) return already;
|
|
1368
|
+
const part = opc.part(partName);
|
|
1369
|
+
if (!part) return partName;
|
|
1370
|
+
const fresh = opc.reservePartNameLike(partName);
|
|
1371
|
+
opc.addPart(fresh, part.contentType, part.bytes);
|
|
1372
|
+
copies.set(partName, fresh);
|
|
1373
|
+
const sourceRels = opc.relationshipsFor(partName);
|
|
1374
|
+
const freshRels = opc.relationshipsFor(fresh);
|
|
1375
|
+
for (const rel of sourceRels) {
|
|
1376
|
+
if (rel.targetMode === "External") {
|
|
1377
|
+
freshRels.addWithId(rel.id, rel.type, rel.target, "External");
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const target = sourceRels.resolveTarget(rel.id);
|
|
1381
|
+
const newTarget = isSharedByPageCopies(rel.type) ? copies.get(target) ?? target : duplicateSubtree(opc, target, copies);
|
|
1382
|
+
freshRels.addWithId(rel.id, rel.type, relativePartName(fresh, newTarget));
|
|
1383
|
+
}
|
|
1384
|
+
return fresh;
|
|
1385
|
+
}
|
|
1386
|
+
//#endregion
|
|
1387
|
+
//#region src/read/api/ops/part-copy.ts
|
|
1388
|
+
const SLIDE_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
|
|
1389
|
+
const SLIDE_LAYOUT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
|
|
1390
|
+
const NOTES_SLIDE_CONTENT_TYPE$2 = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
|
|
1391
|
+
/** Open an ownership scope for one page copy. */
|
|
1392
|
+
function newOwnedScope() {
|
|
1393
|
+
return /* @__PURE__ */ new Map();
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Copy one slide page across as a part of its own, deduping everything under it
|
|
1397
|
+
* but never the page itself. This is the `theme: 'copy'` arm of
|
|
1398
|
+
* {@link Presentation.importSlide}.
|
|
1399
|
+
*
|
|
1400
|
+
* The page is the one part an import is *not* allowed to share. `copyPart`'s
|
|
1401
|
+
* registry idempotence is right for a theme, master, layout or image, where a
|
|
1402
|
+
* second copy is waste; applied to the slide it made a repeated import of the
|
|
1403
|
+
* same source page return the first copy's partname, and the caller then wired a
|
|
1404
|
+
* second `p:sldId` to a part that already had one — a package PowerPoint refuses
|
|
1405
|
+
* to open (0x80070570), with nothing in the read model to show for it. So the
|
|
1406
|
+
* page goes into the selection plan `copyPart` already honours for a batch, which
|
|
1407
|
+
* re-materializes exactly the named pages and leaves the dedup of their
|
|
1408
|
+
* dependencies alone.
|
|
1409
|
+
*
|
|
1410
|
+
* @param ctx the open import out of the source package
|
|
1411
|
+
* @param sourcePartName partname of the source slide to bring across
|
|
1412
|
+
* @return partname of the new slide part in `ctx.dest`
|
|
1413
|
+
*/
|
|
1414
|
+
function copySlidePart(ctx, sourcePartName) {
|
|
1415
|
+
const destinations = /* @__PURE__ */ new Map([[sourcePartName, ctx.dest.opc.reservePartNameLike(sourcePartName)]]);
|
|
1416
|
+
return copyPart({
|
|
1417
|
+
...ctx,
|
|
1418
|
+
selection: { destinations }
|
|
1419
|
+
}, sourcePartName);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Copy `sourcePartName` (and, recursively, every internal part it references)
|
|
1423
|
+
* from `ctx.source` into `ctx.dest`, returning the new partname. Idempotent
|
|
1424
|
+
* per source package via the copy registry. Relationship ids are preserved so
|
|
1425
|
+
* the copied part body's `r:id`/`r:embed` references stay valid; targets are
|
|
1426
|
+
* rewritten to the freshly-allocated partnames. Notes relationships are
|
|
1427
|
+
* dropped. A copied `slideMaster` does not drag in all its sibling layouts —
|
|
1428
|
+
* each imported `slideLayout` wires itself into the master instead (see
|
|
1429
|
+
* {@link linkLayoutIntoMaster}).
|
|
1430
|
+
*
|
|
1431
|
+
* With `ctx.selection`, partnames for the selected slides were already reserved
|
|
1432
|
+
* by the caller ({@link Presentation.importSlides}); this traversal wires their
|
|
1433
|
+
* relationships to each other instead of re-copying them. The batch's rule that
|
|
1434
|
+
* a `slide → slide` link may not leave the selection is enforced ahead of the
|
|
1435
|
+
* copy by {@link checkSelectionCopyable}, which also proves every part this
|
|
1436
|
+
* traversal will reach exists — so once copying starts there is nothing left to
|
|
1437
|
+
* throw, and a rejected batch never leaves a half-copied deck behind.
|
|
1438
|
+
*
|
|
1439
|
+
* Idempotence stops at the page's own parts. Reaching a page opens an
|
|
1440
|
+
* {@link OwnedScope}, and everything under it that {@link isSharedByPageCopies}
|
|
1441
|
+
* does not clear for sharing — a chart, a diagram, an OLE embedding, each with
|
|
1442
|
+
* its own subtree — is copied into that scope instead of the registry, so the
|
|
1443
|
+
* next copy of the page gets parts of its own. Pass `owned` to open the scope
|
|
1444
|
+
* from outside, as the rebinding import paths do for the page they build
|
|
1445
|
+
* themselves.
|
|
1446
|
+
*/
|
|
1447
|
+
function copyPart(ctx, sourcePartName, owned) {
|
|
1448
|
+
if (owned) {
|
|
1449
|
+
const alreadyOwned = owned.get(sourcePartName);
|
|
1450
|
+
if (alreadyOwned !== void 0) return alreadyOwned;
|
|
1451
|
+
} else {
|
|
1452
|
+
const existing = ctx.registry.get(sourcePartName);
|
|
1453
|
+
const selectedDest = ctx.selection?.destinations.get(sourcePartName);
|
|
1454
|
+
if (existing && (selectedDest === void 0 || existing === selectedDest)) return existing;
|
|
1455
|
+
}
|
|
1456
|
+
const sourcePart = ctx.source.part(sourcePartName);
|
|
1457
|
+
if (!sourcePart) throw new PackageReadError("package/part-missing", `importSlide: source package has no part ${sourcePartName}`);
|
|
1458
|
+
const newPartName = owned ? ctx.dest.opc.reservePartNameLike(sourcePartName) : ctx.selection?.destinations.get(sourcePartName) ?? ctx.dest.opc.reservePartNameLike(sourcePartName);
|
|
1459
|
+
if (!ctx.dest.opc.part(newPartName)) ctx.dest.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
|
|
1460
|
+
if (owned) owned.set(sourcePartName, newPartName);
|
|
1461
|
+
else ctx.registry.set(sourcePartName, newPartName);
|
|
1462
|
+
const scope = owned ?? (ctx.selection?.destinations.has(sourcePartName) ? newOwnedScope() : void 0);
|
|
1463
|
+
const isMaster = sourcePart.contentType === SLIDE_MASTER_CONTENT_TYPE;
|
|
1464
|
+
const sourceRels = ctx.source.relationshipsFor(sourcePartName);
|
|
1465
|
+
const targetRels = ctx.dest.opc.relationshipsFor(newPartName);
|
|
1466
|
+
for (const rel of sourceRels) {
|
|
1467
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
|
|
1468
|
+
if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
|
|
1469
|
+
if (rel.targetMode === "External") {
|
|
1470
|
+
targetRels.addWithId(rel.id, rel.type, rel.target, "External");
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
const newTargetPartName = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : scope);
|
|
1474
|
+
targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTargetPartName));
|
|
1475
|
+
}
|
|
1476
|
+
if (isMaster) {
|
|
1477
|
+
clearLayoutIdList(ctx.dest, newPartName);
|
|
1478
|
+
registerMaster(ctx.dest, newPartName);
|
|
1479
|
+
}
|
|
1480
|
+
if (sourcePart.contentType === SLIDE_LAYOUT_CONTENT_TYPE) linkLayoutIntoMaster(ctx, sourceRels, newPartName);
|
|
1481
|
+
return newPartName;
|
|
1482
|
+
}
|
|
1483
|
+
const NO_NOTES = {
|
|
1484
|
+
pages: /* @__PURE__ */ new Set(),
|
|
1485
|
+
copyMaster: false
|
|
1486
|
+
};
|
|
1487
|
+
/**
|
|
1488
|
+
* Dry-run {@link copyPart} over one source's batch selection, reading only the
|
|
1489
|
+
* *source* package: walk the graph the copy will walk, under the same
|
|
1490
|
+
* relationship-skipping rules, and throw what the copy would throw — a source
|
|
1491
|
+
* part that is missing, XML that will not parse, or a `slide → slide` link that
|
|
1492
|
+
* escapes the selection.
|
|
1493
|
+
*
|
|
1494
|
+
* This is what makes a rejected batch leave the destination byte-identical.
|
|
1495
|
+
* `importSlides` reserves partnames and mutates the destination only after this
|
|
1496
|
+
* passes, at which point the copy has no reachable failure left; a check that
|
|
1497
|
+
* lived inside the traversal instead would fire with parts already added, a
|
|
1498
|
+
* master already registered in `presentation.xml`, and no way back.
|
|
1499
|
+
*
|
|
1500
|
+
* Keep this in step with `copyPart`: the two must skip the same relationships
|
|
1501
|
+
* and stop at the same registry hits, or the guarantee is only as good as the
|
|
1502
|
+
* drift between them. With `notes` the same obligation extends to `carryNotes`,
|
|
1503
|
+
* which runs after the copy and is the batch's other way to reach a source part.
|
|
1504
|
+
*
|
|
1505
|
+
* @param source the package the pages are coming from
|
|
1506
|
+
* @param registry the copy registry for that source (parts already in `dest`)
|
|
1507
|
+
* @param selected source partnames of the pages this batch selected
|
|
1508
|
+
* @param notes the pages of `selected` whose notes travel too; none by default
|
|
1509
|
+
*/
|
|
1510
|
+
function checkSelectionCopyable(source, registry, selected, notes = NO_NOTES) {
|
|
1511
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1512
|
+
const walk = (partName) => {
|
|
1513
|
+
if (visited.has(partName)) return;
|
|
1514
|
+
if (registry.has(partName) && !selected.has(partName)) return;
|
|
1515
|
+
visited.add(partName);
|
|
1516
|
+
const part = source.part(partName);
|
|
1517
|
+
if (!part) throw new PackageReadError("package/part-missing", `importSlides: source package has no part ${partName}`);
|
|
1518
|
+
const isMaster = part.contentType === SLIDE_MASTER_CONTENT_TYPE;
|
|
1519
|
+
const isNotesSlide = part.contentType === NOTES_SLIDE_CONTENT_TYPE$2;
|
|
1520
|
+
if (isMaster || part.contentType === SLIDE_LAYOUT_CONTENT_TYPE) part.dom;
|
|
1521
|
+
const rels = source.relationshipsFor(partName);
|
|
1522
|
+
for (const rel of rels) {
|
|
1523
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") {
|
|
1524
|
+
if (notes.pages.has(partName)) walk(rels.resolveTarget(rel.id));
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
if (isNotesSlide) {
|
|
1528
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") continue;
|
|
1529
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" && !notes.copyMaster) continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (isMaster && rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") continue;
|
|
1532
|
+
if (rel.targetMode === "External") continue;
|
|
1533
|
+
const targetPartName = rels.resolveTarget(rel.id);
|
|
1534
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" && !selected.has(targetPartName) && !registry.has(targetPartName)) throw new InvalidOptionError("import/unresolved-slide-link", `importSlides: source slide ${partName} links to ${targetPartName}, which is not among the selected imported pages`);
|
|
1535
|
+
walk(targetPartName);
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
for (const partName of selected) walk(partName);
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Wire a just-copied layout into its (already-copied) master. Resolves which
|
|
1542
|
+
* destination master that is by running the layout's *source* master rel through
|
|
1543
|
+
* the copy registry, then hands the destination-side wiring to
|
|
1544
|
+
* {@link addLayoutToMaster}. Called once per copied layout, so the master
|
|
1545
|
+
* accumulates exactly the imported layouts.
|
|
1546
|
+
*/
|
|
1547
|
+
function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
|
|
1548
|
+
const masterRel = layoutSourceRels.byType(SLIDE_MASTER_REL)[0];
|
|
1549
|
+
if (!masterRel) return;
|
|
1550
|
+
const masterPartName = ctx.registry.get(layoutSourceRels.resolveTarget(masterRel.id));
|
|
1551
|
+
if (!masterPartName) return;
|
|
1552
|
+
addLayoutToMaster(ctx.dest, masterPartName, layoutPartName);
|
|
1553
|
+
}
|
|
1554
|
+
//#endregion
|
|
1555
|
+
//#region src/read/api/ops/notes-master.ts
|
|
1556
|
+
/**
|
|
1557
|
+
* Carrying speaker notes and the deck's single notes master across an import or an append.
|
|
1558
|
+
*
|
|
1559
|
+
* A `notesSlide` is not self-contained: it back-references the slide it annotates and forward-
|
|
1560
|
+
* references the deck's notes master. `CT_NotesMasterIdList` allows at most one master per
|
|
1561
|
+
* presentation, so a deck that already has one keeps it — the destination's notes styling wins —
|
|
1562
|
+
* and a source master is copied only into a deck that has none. That single rule is why both the
|
|
1563
|
+
* `Presentation`-to-`Presentation` path and the generator-XML path funnel through
|
|
1564
|
+
* `registerNotesMaster`: they differ only in how the *part* comes into being.
|
|
1565
|
+
*/
|
|
1566
|
+
const NOTES_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
|
|
1567
|
+
const THEME_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml";
|
|
1568
|
+
const textEncoder$3 = new TextEncoder();
|
|
1569
|
+
/**
|
|
1570
|
+
* Carry the source slide's speaker notes onto the just-imported slide (the
|
|
1571
|
+
* `importNotes` option). The slide copy itself dropped the `notesSlide` rel, so
|
|
1572
|
+
* this copies the source `notesSlide` part into a fresh partname, wires a
|
|
1573
|
+
* `slide → notesSlide` rel on the new slide, and rebuilds the copied notesSlide's
|
|
1574
|
+
* own relationships:
|
|
1575
|
+
*
|
|
1576
|
+
* - its `slide` back-rel is repointed at the new slide (`newSlidePartName`) — the
|
|
1577
|
+
* source slide is *not* copied (that would be circular);
|
|
1578
|
+
* - its `notesMaster` rel is resolved through {@link ensureNotesMaster}, which
|
|
1579
|
+
* reuses this deck's notesMaster when it has one and copies the source's only
|
|
1580
|
+
* when it has none (a deck may have at most one notesMaster);
|
|
1581
|
+
* - any other internal target (media, etc.) is copied via {@link copyPart}, under an
|
|
1582
|
+
* ownership scope so a second copy of the page gets notes parts of its own.
|
|
1583
|
+
*
|
|
1584
|
+
* No-op when the source slide has no notes. Content-type registration for the
|
|
1585
|
+
* copied parts is handled by `addPart`/{@link copyPart}.
|
|
1586
|
+
*/
|
|
1587
|
+
function carryNotes(dest, source, ctx, sourceSlidePartName, newSlidePartName) {
|
|
1588
|
+
const sourceSlideRels = source.opc.relationshipsFor(sourceSlidePartName);
|
|
1589
|
+
const notesRel = sourceSlideRels.byType(NOTES_SLIDE_REL)[0];
|
|
1590
|
+
if (!notesRel) return;
|
|
1591
|
+
const sourceNotesPartName = sourceSlideRels.resolveTarget(notesRel.id);
|
|
1592
|
+
const sourceNotesPart = source.opc.part(sourceNotesPartName);
|
|
1593
|
+
if (!sourceNotesPart) return;
|
|
1594
|
+
const newNotesPartName = dest.opc.reservePartNameLike(sourceNotesPartName);
|
|
1595
|
+
dest.opc.addPart(newNotesPartName, sourceNotesPart.contentType, sourceNotesPart.bytes);
|
|
1596
|
+
dest.opc.relationshipsFor(newSlidePartName).add(NOTES_SLIDE_REL, relativePartName(newSlidePartName, newNotesPartName));
|
|
1597
|
+
const owned = newOwnedScope();
|
|
1598
|
+
owned.set(sourceNotesPartName, newNotesPartName);
|
|
1599
|
+
const notesSourceRels = source.opc.relationshipsFor(sourceNotesPartName);
|
|
1600
|
+
const notesTargetRels = dest.opc.relationshipsFor(newNotesPartName);
|
|
1601
|
+
for (const rel of notesSourceRels) {
|
|
1602
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") {
|
|
1603
|
+
notesTargetRels.addWithId(rel.id, SLIDE_REL, relativePartName(newNotesPartName, newSlidePartName));
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster") {
|
|
1607
|
+
const notesMaster = ensureNotesMaster(dest, ctx, notesSourceRels.resolveTarget(rel.id));
|
|
1608
|
+
notesTargetRels.addWithId(rel.id, NOTES_MASTER_REL, relativePartName(newNotesPartName, notesMaster));
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
if (rel.targetMode === "External") {
|
|
1612
|
+
notesTargetRels.addWithId(rel.id, rel.type, rel.target, "External");
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
const newTarget = copyPart(ctx, notesSourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
|
|
1616
|
+
notesTargetRels.addWithId(rel.id, rel.type, relativePartName(newNotesPartName, newTarget));
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Resolve the notesMaster an imported `notesSlide` should bind to, honouring the
|
|
1621
|
+
* single-notesMaster-per-presentation rule (`p:notesMasterIdLst` holds 0..1
|
|
1622
|
+
* `p:notesMasterId`). If this deck already has a notesMaster it is reused and the
|
|
1623
|
+
* source's is *not* copied (the destination's notes styling wins); otherwise the
|
|
1624
|
+
* source notesMaster (and, via {@link copyPart}, its theme) is copied and
|
|
1625
|
+
* registered in `presentation.xml`. Returns the destination notesMaster partname.
|
|
1626
|
+
*/
|
|
1627
|
+
function ensureNotesMaster(dest, ctx, sourceNotesMasterPartName) {
|
|
1628
|
+
const presPart = dest.presentationPart;
|
|
1629
|
+
const presRels = dest.opc.relationshipsFor(presPart.partName);
|
|
1630
|
+
const existing = presRels.byType(NOTES_MASTER_REL)[0];
|
|
1631
|
+
if (existing) return presRels.resolveTarget(existing.id);
|
|
1632
|
+
return registerNotesMaster(dest, copyPart(ctx, sourceNotesMasterPartName));
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Wire an already-added notesMaster part into `presentation.xml`: a `notesMaster`
|
|
1636
|
+
* relationship plus the single `p:notesMasterId` entry that `CT_NotesMasterIdList`
|
|
1637
|
+
* allows. Returns the partname, so callers can use it as a rel target.
|
|
1638
|
+
*
|
|
1639
|
+
* Split out of {@link ensureNotesMaster} because the two ways a notesMaster arrives
|
|
1640
|
+
* — copied from another `Presentation`, or authored by a generator and injected by
|
|
1641
|
+
* {@link Presentation.appendSlides} — differ only in how the *part* is created, not in how it is
|
|
1642
|
+
* registered.
|
|
1643
|
+
*/
|
|
1644
|
+
function registerNotesMaster(dest, notesMasterPartName) {
|
|
1645
|
+
const presPart = dest.presentationPart;
|
|
1646
|
+
const relId = dest.opc.relationshipsFor(presPart.partName).add(NOTES_MASTER_REL, relativePartName(presPart.partName, notesMasterPartName)).id;
|
|
1647
|
+
const root = presPart.dom.documentElement;
|
|
1648
|
+
if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to register a notes master in");
|
|
1649
|
+
const lst = getOrAddChild(root, "p:notesMasterIdLst", [
|
|
1650
|
+
"p:handoutMasterIdLst",
|
|
1651
|
+
"p:sldIdLst",
|
|
1652
|
+
"p:sldSz",
|
|
1653
|
+
"p:notesSz",
|
|
1654
|
+
"p:embeddedFontLst",
|
|
1655
|
+
"p:custShowLst",
|
|
1656
|
+
"p:photoAlbum",
|
|
1657
|
+
"p:custDataLst",
|
|
1658
|
+
"p:kinsoku",
|
|
1659
|
+
"p:defaultTextStyle",
|
|
1660
|
+
"p:modifyVerifier",
|
|
1661
|
+
"p:extLst"
|
|
1662
|
+
]);
|
|
1663
|
+
removeChildrenByQName(lst, ["p:notesMasterId"]);
|
|
1664
|
+
const entry = createElement(presPart.dom, "p:notesMasterId");
|
|
1665
|
+
setAttr(entry, "r:id", relId);
|
|
1666
|
+
lst.appendChild(entry);
|
|
1667
|
+
presPart.markDirty();
|
|
1668
|
+
return notesMasterPartName;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Resolve the notesMaster an *appended* slide's notes should bind to. Same
|
|
1672
|
+
* single-notesMaster rule as {@link ensureNotesMaster}: this deck's own wins when it
|
|
1673
|
+
* has one, so the destination's notes styling is preserved and `master.xml` is
|
|
1674
|
+
* discarded. Otherwise the generator's notes master is installed, together with the
|
|
1675
|
+
* theme its `.rels` requires (the normal write path emits that as `theme2.xml`).
|
|
1676
|
+
*/
|
|
1677
|
+
function ensureNotesMasterFromXml(dest, master) {
|
|
1678
|
+
const presPart = dest.presentationPart;
|
|
1679
|
+
const presRels = dest.opc.relationshipsFor(presPart.partName);
|
|
1680
|
+
const existing = presRels.byType(NOTES_MASTER_REL)[0];
|
|
1681
|
+
if (existing) return presRels.resolveTarget(existing.id);
|
|
1682
|
+
const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
|
|
1683
|
+
dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$3.encode(master.xml));
|
|
1684
|
+
const themePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
|
|
1685
|
+
dest.opc.addPart(themePartName, THEME_CONTENT_TYPE, textEncoder$3.encode(master.themeXml));
|
|
1686
|
+
dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, themePartName));
|
|
1687
|
+
return registerNotesMaster(dest, masterPartName);
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Resolve the notesMaster a *newly authored* notes slide should bind to
|
|
1691
|
+
* ({@link Slide.addNotes} on a loaded deck). Same single-notesMaster rule as
|
|
1692
|
+
* {@link ensureNotesMaster} and {@link ensureNotesMasterFromXml}: this deck's own
|
|
1693
|
+
* wins when it has one. The third way a notesMaster arrives, and the only one with
|
|
1694
|
+
* no source deck and no generator instance behind it, so it has to build both parts
|
|
1695
|
+
* itself.
|
|
1696
|
+
*
|
|
1697
|
+
* The *theme* is where it differs from the append path. `ensureNotesMasterFromXml`
|
|
1698
|
+
* installs the generator's `theme2.xml` because a generator deck's notes were
|
|
1699
|
+
* authored against it; there is no such theme here, so the deck's **own** theme
|
|
1700
|
+
* (reached through the annotated slide's layout → master → theme chain) is cloned
|
|
1701
|
+
* into a fresh part instead. That is both the closer match to "the destination's
|
|
1702
|
+
* notes styling wins" and the safer package: the notesMaster gets a theme part of
|
|
1703
|
+
* its own rather than a second relationship onto the slide master's, which is the
|
|
1704
|
+
* arrangement PowerPoint writes (`theme1.xml` for the master, `theme2.xml` for the
|
|
1705
|
+
* notes master).
|
|
1706
|
+
*
|
|
1707
|
+
* Throws when the deck has no theme to clone — a package that malformed cannot be
|
|
1708
|
+
* given a valid notesMaster, and silently emitting one without a resolvable theme
|
|
1709
|
+
* is what PowerPoint reports as a repair prompt rather than a bad edit.
|
|
1710
|
+
* @param {Presentation} dest - the deck being authored onto
|
|
1711
|
+
* @param {string} slidePartName - the slide whose notes are being authored, used to find the theme
|
|
1712
|
+
* @return {string} the destination notesMaster partname
|
|
1713
|
+
*/
|
|
1714
|
+
function ensureNotesMasterForAuthoring(dest, slidePartName) {
|
|
1715
|
+
const presPart = dest.presentationPart;
|
|
1716
|
+
const presRels = dest.opc.relationshipsFor(presPart.partName);
|
|
1717
|
+
const existing = presRels.byType(NOTES_MASTER_REL)[0];
|
|
1718
|
+
if (existing) return presRels.resolveTarget(existing.id);
|
|
1719
|
+
const themePartName = resolveSlideThemeParts(dest.opc, slidePartName).themePartName;
|
|
1720
|
+
const themePart = themePartName ? dest.opc.part(themePartName) : void 0;
|
|
1721
|
+
if (!themePart) throw new PackageReadError("package/part-missing", `addNotes: no theme reachable from ${slidePartName} to bind a new notes master to`);
|
|
1722
|
+
const masterPartName = dest.opc.reservePartNameLike("/ppt/notesMasters/notesMaster1.xml");
|
|
1723
|
+
dest.opc.addPart(masterPartName, NOTES_MASTER_CONTENT_TYPE, textEncoder$3.encode(makeXmlNotesMaster()));
|
|
1724
|
+
const notesThemePartName = dest.opc.reservePartNameLike("/ppt/theme/theme1.xml");
|
|
1725
|
+
dest.opc.addPart(notesThemePartName, themePart.contentType, themePart.bytes);
|
|
1726
|
+
dest.opc.relationshipsFor(masterPartName).add(THEME_REL, relativePartName(masterPartName, notesThemePartName));
|
|
1727
|
+
return registerNotesMaster(dest, masterPartName);
|
|
1728
|
+
}
|
|
1729
|
+
//#endregion
|
|
1730
|
+
//#region src/read/api/ops/notes-author.ts
|
|
1731
|
+
/**
|
|
1732
|
+
* Authoring speaker notes onto a slide of a *loaded* deck ({@link Slide.addNotes}).
|
|
1733
|
+
*
|
|
1734
|
+
* The third way a notes slide comes into being, after the two in `notes-master.ts`
|
|
1735
|
+
* (carried from another `Presentation` by `importSlide({ importNotes: true })`, or
|
|
1736
|
+
* emitted by the generator and spliced on by `appendSlides`). Those two both *move*
|
|
1737
|
+
* an existing part; this one has no source part to move, so it builds one.
|
|
1738
|
+
*
|
|
1739
|
+
* The part it builds is the generator's, not a second design: the fixed three-
|
|
1740
|
+
* placeholder frame comes from {@link makeXmlNotesSlideSkeleton}, and the body
|
|
1741
|
+
* paragraphs carry the same elements and attributes the write path emits for a plain
|
|
1742
|
+
* `pptx.addSlide().addNotes(text)`. `test/read/add-notes.test.js` asserts that
|
|
1743
|
+
* equivalence, so the two cannot drift apart unnoticed — the same guarantee
|
|
1744
|
+
* `src/ooxml/` gives the constants both halves share.
|
|
1745
|
+
*
|
|
1746
|
+
* The one difference is empty-element *spelling*: the write path emits `a:rPr` as an
|
|
1747
|
+
* open/close pair and this emits it self-closed, because a part the read model
|
|
1748
|
+
* authors is reserialized through `read/oxml/dom.ts`, which self-closes every empty
|
|
1749
|
+
* element. Writing the open/close form here would not survive the first save, so the
|
|
1750
|
+
* form that does is written in the first place and the test normalizes the spelling
|
|
1751
|
+
* rather than pretending the bytes match.
|
|
1752
|
+
*
|
|
1753
|
+
* Notes text is split on `\n` into paragraphs, matching the write-side `addNotes`;
|
|
1754
|
+
* runs carry no formatting of their own. Per-run formatting, hyperlinks, and
|
|
1755
|
+
* multi-run paragraphs are reached afterwards through `slide.notesTextFrame`, which
|
|
1756
|
+
* is the read model's normal text-editing surface.
|
|
1757
|
+
*/
|
|
1758
|
+
const NOTES_SLIDE_CONTENT_TYPE$1 = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
|
|
1759
|
+
const A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
1760
|
+
const textEncoder$2 = new TextEncoder();
|
|
1761
|
+
/**
|
|
1762
|
+
* The `a:p` children of a notes body, one per `\n`-separated line: the write path's
|
|
1763
|
+
* output for an unformatted note, modulo the empty-element spelling described above.
|
|
1764
|
+
* An empty line yields a paragraph with no run, exactly as `genXmlNotesParagraphs`
|
|
1765
|
+
* does.
|
|
1766
|
+
* @param {string} text - the note, `\n` separating paragraphs
|
|
1767
|
+
* @return {string} concatenated `a:p` XML
|
|
1768
|
+
*/
|
|
1769
|
+
function notesParagraphsXml(text) {
|
|
1770
|
+
return text.split("\n").map((line) => line.replace(/\r/g, "")).map((line) => el("a:p", null, [raw(line === "" ? "" : el("a:r", null, [raw(voidEl("a:rPr", {
|
|
1771
|
+
lang: "en-US",
|
|
1772
|
+
dirty: 0
|
|
1773
|
+
})), raw(el("a:t", null, line))])), raw(voidEl("a:endParaRPr", {
|
|
1774
|
+
lang: "en-US",
|
|
1775
|
+
dirty: 0
|
|
1776
|
+
}))])).join("");
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Replace a notes body's paragraphs with `text`, in the body's own document.
|
|
1780
|
+
*
|
|
1781
|
+
* The path taken when the slide *already* has a notes part — an imported one, or one
|
|
1782
|
+
* an earlier `addNotes` wrote. Only the `a:p` children are replaced, so the part's
|
|
1783
|
+
* geometry, its `a:bodyPr`/`a:lstStyle`, and its other two placeholders survive. The
|
|
1784
|
+
* body's hyperlink relationships are deliberately *not* pruned: a caller replacing
|
|
1785
|
+
* text has not asked for its rels to be rewritten, and a now-unreferenced notes
|
|
1786
|
+
* hyperlink rel is inert (nothing resolves it on save).
|
|
1787
|
+
* @param {Element} txBody - the body placeholder's `p:txBody`
|
|
1788
|
+
* @param {string} text - the note, `\n` separating paragraphs
|
|
1789
|
+
*/
|
|
1790
|
+
function replaceNotesParagraphs(txBody, text) {
|
|
1791
|
+
const doc = txBody.ownerDocument;
|
|
1792
|
+
if (!doc) throw new InternalError("oxml/node-has-no-document", "Notes body has no owner document");
|
|
1793
|
+
removeChildrenByQName(txBody, ["a:p"]);
|
|
1794
|
+
const wrapper = parseXml(`<w xmlns:a="${A_NS}">${notesParagraphsXml(text)}</w>`).documentElement;
|
|
1795
|
+
if (!wrapper) throw new InternalError("oxml/node-has-no-document", "Authored notes paragraphs did not parse to a root");
|
|
1796
|
+
for (const paragraph of Array.from(wrapper.childNodes)) txBody.appendChild(doc.importNode(paragraph, true));
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* The `p:txBody` of a notes slide's body placeholder (`p:ph type="body"`), or `null`
|
|
1800
|
+
* when the part carries none. A raw walk rather than a `NotesSlide`/`TextFrame` hop,
|
|
1801
|
+
* because the caller needs the element itself to rewrite and the modeled surface
|
|
1802
|
+
* would resolve a notes theme context it has no use for.
|
|
1803
|
+
* @param {Element | null} root - the notes slide's `p:notes` root
|
|
1804
|
+
* @return {Element | null} the body placeholder's text body
|
|
1805
|
+
*/
|
|
1806
|
+
function notesBodyTxBody(root) {
|
|
1807
|
+
const cSld = root && firstChild(root, "p:cSld");
|
|
1808
|
+
const spTree = cSld && firstChild(cSld, "p:spTree");
|
|
1809
|
+
if (!spTree) return null;
|
|
1810
|
+
for (const sp of getElements(spTree, "p:sp")) {
|
|
1811
|
+
const nvSpPr = firstChild(sp, "p:nvSpPr");
|
|
1812
|
+
const nvPr = nvSpPr && firstChild(nvSpPr, "p:nvPr");
|
|
1813
|
+
const ph = nvPr && firstChild(nvPr, "p:ph");
|
|
1814
|
+
if (ph && attr(ph, "type") === "body") return firstChild(sp, "p:txBody");
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Give `slide` the speaker notes `text`, creating its notes slide part when it has
|
|
1820
|
+
* none, and return the notes part's name.
|
|
1821
|
+
*
|
|
1822
|
+
* When the slide already has a notes part, only the body paragraphs are rewritten
|
|
1823
|
+
* (see {@link replaceNotesParagraphs}). When it has none, the part is built and
|
|
1824
|
+
* wired: `slide → notesSlide`, and on the new part the two relationships a notes
|
|
1825
|
+
* slide always carries — `notesMaster` as rId1 and a `slide` back-reference as rId2,
|
|
1826
|
+
* the ids and order the write path reserves.
|
|
1827
|
+
*
|
|
1828
|
+
* The notes master is resolved through {@link ensureNotesMasterForAuthoring}: this
|
|
1829
|
+
* deck's own when it has one, otherwise a fresh one bound to a clone of the deck's
|
|
1830
|
+
* theme. That is the single-notesMaster-per-presentation rule the import and append
|
|
1831
|
+
* paths already follow, so the three cannot produce a second master between them.
|
|
1832
|
+
* @param {Slide} slide - the slide to annotate
|
|
1833
|
+
* @param {string} text - the note, `\n` separating paragraphs
|
|
1834
|
+
* @return {string} the notes slide's partname
|
|
1835
|
+
*/
|
|
1836
|
+
function authorNotes(slide, text) {
|
|
1837
|
+
const opc = slide.presentation.opc;
|
|
1838
|
+
const existingRel = slide.relationships.byType(NOTES_SLIDE_REL)[0];
|
|
1839
|
+
if (existingRel) {
|
|
1840
|
+
const partName = slide.relationships.resolveTarget(existingRel.id);
|
|
1841
|
+
const part = opc.part(partName);
|
|
1842
|
+
if (!part) throw new PackageReadError("package/part-missing", `addNotes: slide ${slide.partName} has a notesSlide rel to a missing part ${partName}`);
|
|
1843
|
+
const txBody = notesBodyTxBody(part.dom.documentElement);
|
|
1844
|
+
if (!txBody) throw new PackageReadError("package/part-has-no-root", `addNotes: notes slide ${partName} has no body placeholder to write into`);
|
|
1845
|
+
replaceNotesParagraphs(txBody, text);
|
|
1846
|
+
part.markDirty();
|
|
1847
|
+
return partName;
|
|
1153
1848
|
}
|
|
1154
|
-
|
|
1849
|
+
const slideNum = slide.index + 1;
|
|
1850
|
+
const partName = opc.reservePartNameLike("/ppt/notesSlides/notesSlide1.xml");
|
|
1851
|
+
opc.addPart(partName, NOTES_SLIDE_CONTENT_TYPE$1, textEncoder$2.encode(makeXmlNotesSlideSkeleton(notesParagraphsXml(text), slideNum)));
|
|
1852
|
+
slide.relationships.add(NOTES_SLIDE_REL, relativePartName(slide.partName, partName));
|
|
1853
|
+
const notesRels = opc.relationshipsFor(partName);
|
|
1854
|
+
const masterPartName = ensureNotesMasterForAuthoring(slide.presentation, slide.partName);
|
|
1855
|
+
notesRels.addWithId("rId1", NOTES_MASTER_REL, relativePartName(partName, masterPartName));
|
|
1856
|
+
notesRels.addWithId("rId2", SLIDE_REL, relativePartName(partName, slide.partName));
|
|
1857
|
+
return partName;
|
|
1858
|
+
}
|
|
1155
1859
|
//#endregion
|
|
1156
1860
|
//#region src/read/api/comments.ts
|
|
1157
1861
|
/** The slide → comments-part relationship type (legacy comments). */
|
|
@@ -2700,6 +3404,34 @@ var Slide = class {
|
|
|
2700
3404
|
return new Picture(pic, this);
|
|
2701
3405
|
}
|
|
2702
3406
|
/**
|
|
3407
|
+
* Give this slide the speaker notes `text`, and return the resulting
|
|
3408
|
+
* {@link NotesSlide}. A `
|
|
3409
|
+
` starts a new paragraph, matching the write-side
|
|
3410
|
+
* `addNotes`; the runs carry no formatting of their own, so style them
|
|
3411
|
+
* afterwards through {@link notesTextFrame}.
|
|
3412
|
+
*
|
|
3413
|
+
* The counterpart to the read side's `notesText`/`notesTextFrame`/`notesSlide`
|
|
3414
|
+
* getters, and the way to annotate a slide that has **no notes part at all** —
|
|
3415
|
+
* the state an `importSlide` without `{ importNotes: true }` leaves behind, and
|
|
3416
|
+
* the one a `notesTextFrame` edit cannot reach because there is no frame to
|
|
3417
|
+
* hand back. Calling it on a slide that already has notes replaces the body
|
|
3418
|
+
* text and leaves the rest of the part (its geometry, its other two
|
|
3419
|
+
* placeholders) alone.
|
|
3420
|
+
*
|
|
3421
|
+
* Creating the part pulls in what a notes slide must bind to: a `notesMaster`,
|
|
3422
|
+
* of which a presentation may hold at most one. This deck's own is reused when
|
|
3423
|
+
* it has one; otherwise one is installed, bound to a clone of this deck's theme
|
|
3424
|
+
* so it resolves against the destination palette. That is the same
|
|
3425
|
+
* single-master rule `importSlide({ importNotes: true })` and `appendSlides`
|
|
3426
|
+
* follow, so mixing the three cannot produce a second notes master.
|
|
3427
|
+
*/
|
|
3428
|
+
addNotes(text) {
|
|
3429
|
+
const partName = authorNotes(this, text);
|
|
3430
|
+
const part = this.presentation.opc.part(partName);
|
|
3431
|
+
if (!part) throw new InternalError("import/part-went-missing", `Authored notes part went missing: ${partName}`);
|
|
3432
|
+
return new NotesSlide(this.presentation.opc, part);
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
2703
3435
|
* @internal The slide's shape tree (`p:cSld/p:spTree`), or `null` if absent.
|
|
2704
3436
|
* Exposed for cross-slide composition (`Presentation.importShape`).
|
|
2705
3437
|
*/
|
|
@@ -3081,7 +3813,7 @@ function rescaleTableGrid(graphicFrame, t) {
|
|
|
3081
3813
|
const TABLE_STYLES_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles";
|
|
3082
3814
|
const TABLE_STYLES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
|
|
3083
3815
|
const TABLE_STYLES_DEFAULT_GUID = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
|
|
3084
|
-
const textEncoder$
|
|
3816
|
+
const textEncoder$1 = new TextEncoder();
|
|
3085
3817
|
/**
|
|
3086
3818
|
* Copy any table style the restyled slide references from the source
|
|
3087
3819
|
* `tableStyles.xml` into this deck's (the `remapLiterals` table leg). A restyled
|
|
@@ -3122,260 +3854,64 @@ function copySourceTableStyles(dest, sourceOpc, slideRoot) {
|
|
|
3122
3854
|
* anything was added.
|
|
3123
3855
|
*/
|
|
3124
3856
|
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();
|
|
3857
|
+
const present = new Set(getElements(destList, "a:tblStyle").map((st) => attr(st, "styleId")));
|
|
3858
|
+
let added = false;
|
|
3859
|
+
for (const style of styles) {
|
|
3860
|
+
const id = attr(style, "styleId");
|
|
3861
|
+
if (!id || present.has(id)) continue;
|
|
3862
|
+
destList.appendChild(ownerDocumentOf(destList).importNode(style, true));
|
|
3863
|
+
present.add(id);
|
|
3864
|
+
added = true;
|
|
3865
|
+
}
|
|
3866
|
+
return added;
|
|
3323
3867
|
}
|
|
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
3868
|
/**
|
|
3329
|
-
* Copy
|
|
3330
|
-
*
|
|
3331
|
-
*
|
|
3332
|
-
*
|
|
3333
|
-
*
|
|
3334
|
-
*
|
|
3335
|
-
*
|
|
3336
|
-
*
|
|
3869
|
+
* Copy the source deck's **whole** `tableStyles.xml` into this deck: every
|
|
3870
|
+
* `<a:tblStyle>` it defines, plus its `a:tblStyleLst@def` (the default table
|
|
3871
|
+
* style). Presentation-level, so a separate traversal from the master/layout copy
|
|
3872
|
+
* chain — and whole-deck, since `tableStyles.xml` does not record which style
|
|
3873
|
+
* belongs to which master. See `ImportSlideMastersOptions.tableStyles`.
|
|
3874
|
+
*
|
|
3875
|
+
* Styles union by `styleId` (destination-wins per id, via {@link addTableStyles}),
|
|
3876
|
+
* but `def` is **source-wins**: a caller asking for the source's table styling
|
|
3877
|
+
* wants its default too, and the destination's `def` is typically a generator stub
|
|
3878
|
+
* rather than a deliberate choice. Carrying the styles without the `def` would be
|
|
3879
|
+
* the worse half-measure — the standard default GUID is one most templates also
|
|
3880
|
+
* define, so a new table would silently resolve to the wrong style rather than
|
|
3881
|
+
* visibly to none. Idempotent: a re-call re-adds nothing and re-sets `def` to the
|
|
3882
|
+
* same value.
|
|
3337
3883
|
*/
|
|
3338
|
-
function
|
|
3339
|
-
const
|
|
3340
|
-
if (
|
|
3341
|
-
const
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
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));
|
|
3358
|
-
}
|
|
3359
|
-
if (isMaster) {
|
|
3360
|
-
clearLayoutIdList(ctx.dest, newPartName);
|
|
3361
|
-
registerMaster(ctx.dest, newPartName);
|
|
3884
|
+
function carryTableStyles(dest, sourceOpc) {
|
|
3885
|
+
const sourceList = tableStyleList(sourceOpc);
|
|
3886
|
+
if (!sourceList) return;
|
|
3887
|
+
const destPart = ensureTableStylesPart(dest);
|
|
3888
|
+
const destList = destPart.dom.documentElement;
|
|
3889
|
+
if (!destList) return;
|
|
3890
|
+
let changed = addTableStyles(destList, getElements(sourceList, "a:tblStyle"));
|
|
3891
|
+
const sourceDef = attr(sourceList, "def");
|
|
3892
|
+
if (sourceDef && attr(destList, "def") !== sourceDef) {
|
|
3893
|
+
destList.setAttribute("def", sourceDef);
|
|
3894
|
+
changed = true;
|
|
3362
3895
|
}
|
|
3363
|
-
if (
|
|
3364
|
-
|
|
3896
|
+
if (changed) destPart.markDirty();
|
|
3897
|
+
}
|
|
3898
|
+
/** The `a:tblStyleLst` root of a package's `tableStyles.xml`, or `null` when it has none. */
|
|
3899
|
+
function tableStyleList(opc) {
|
|
3900
|
+
const part = opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
|
|
3901
|
+
return part ? part.dom.documentElement : null;
|
|
3365
3902
|
}
|
|
3366
3903
|
/**
|
|
3367
|
-
*
|
|
3368
|
-
*
|
|
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.
|
|
3904
|
+
* The deck's `tableStyles.xml` part, creating an empty one (and wiring its
|
|
3905
|
+
* `presentation.xml` relationship + content type) when the deck has none.
|
|
3372
3906
|
*/
|
|
3373
|
-
function
|
|
3374
|
-
const
|
|
3375
|
-
if (
|
|
3376
|
-
const
|
|
3377
|
-
|
|
3378
|
-
|
|
3907
|
+
function ensureTableStylesPart(dest) {
|
|
3908
|
+
const existing = dest.opc.partsByContentType(TABLE_STYLES_CONTENT_TYPE)[0];
|
|
3909
|
+
if (existing) return existing;
|
|
3910
|
+
const partName = dest.opc.reservePartNameLike("/ppt/tableStyles.xml");
|
|
3911
|
+
const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n<a:tblStyleLst xmlns:a="${OOXML_NS.a}" def="${TABLE_STYLES_DEFAULT_GUID}"/>`;
|
|
3912
|
+
const part = dest.opc.addPart(partName, TABLE_STYLES_CONTENT_TYPE, textEncoder$1.encode(xml));
|
|
3913
|
+
dest.opc.relationshipsFor(dest.presentationPart.partName).add(TABLE_STYLES_REL, relativePartName(dest.presentationPart.partName, partName));
|
|
3914
|
+
return part;
|
|
3379
3915
|
}
|
|
3380
3916
|
//#endregion
|
|
3381
3917
|
//#region src/read/api/ops/carried-rels.ts
|
|
@@ -3401,8 +3937,9 @@ function linkLayoutIntoMaster(ctx, layoutSourceRels, layoutPartName) {
|
|
|
3401
3937
|
* @param {string} destPartName - partname of the destination part the subtree is being placed in
|
|
3402
3938
|
* @param {Relationships} destRels - relationships of that destination part
|
|
3403
3939
|
* @param {Map<string, string>} relIdMap - per-call cache (source part + source id → new id) deduping references shared within one import
|
|
3940
|
+
* @param {OwnedScope} [owned] - ownership scope of the thing being carried, so the chart or diagram under a carried shape is copied for it rather than shared with an earlier copy
|
|
3404
3941
|
*/
|
|
3405
|
-
function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap) {
|
|
3942
|
+
function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relIdMap, owned) {
|
|
3406
3943
|
const elements = [];
|
|
3407
3944
|
collectElements(node, elements);
|
|
3408
3945
|
for (const el of elements) {
|
|
@@ -3417,7 +3954,7 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
|
|
|
3417
3954
|
id: a.value
|
|
3418
3955
|
});
|
|
3419
3956
|
}
|
|
3420
|
-
for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap));
|
|
3957
|
+
for (const { local, id } of refs) setAttr(el, `r:${local}`, carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned));
|
|
3421
3958
|
}
|
|
3422
3959
|
}
|
|
3423
3960
|
/**
|
|
@@ -3428,16 +3965,20 @@ function rewriteCarriedRels(node, ctx, sourceRels, destPartName, destRels, relId
|
|
|
3428
3965
|
* @param {string} destPartName - partname of the destination part
|
|
3429
3966
|
* @param {Relationships} destRels - relationships of that destination part
|
|
3430
3967
|
* @param {Map<string, string>} relIdMap - per-call dedupe cache
|
|
3968
|
+
* @param {OwnedScope} [owned] - ownership scope of the carried content, for the parts it owns
|
|
3431
3969
|
* @return {string} the new relationship id on the destination part
|
|
3432
3970
|
*/
|
|
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;
|
|
3971
|
+
function carryRel(ctx, sourceRels, id, destPartName, destRels, relIdMap, owned) {
|
|
3437
3972
|
const rel = sourceRels.get(id);
|
|
3438
3973
|
if (!rel) throw new InvalidOptionError("relationship/not-found", `Relationships of ${sourceRels.sourcePartName}: no relationship with id ${id}`);
|
|
3439
|
-
const
|
|
3440
|
-
|
|
3974
|
+
const ownsIt = owned !== void 0 && rel.targetMode !== "External" && !isSharedByPageCopies(rel.type);
|
|
3975
|
+
const key = `${sourceRels.sourcePartName}|${id}`;
|
|
3976
|
+
if (!ownsIt) {
|
|
3977
|
+
const cached = relIdMap.get(key);
|
|
3978
|
+
if (cached) return cached;
|
|
3979
|
+
}
|
|
3980
|
+
const newId = rel.targetMode === "External" ? destRels.add(rel.type, rel.target, "External").id : destRels.add(rel.type, relativePartName(destPartName, copyPart(ctx, sourceRels.resolveTarget(id), ownsIt ? owned : void 0))).id;
|
|
3981
|
+
if (!ownsIt) relIdMap.set(key, newId);
|
|
3441
3982
|
return newId;
|
|
3442
3983
|
}
|
|
3443
3984
|
//#endregion
|
|
@@ -3819,6 +4360,7 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
|
|
|
3819
4360
|
if (!slideRoot) throw new PackageReadError("package/part-has-no-root", `Imported slide ${newPartName} has no root element`);
|
|
3820
4361
|
const sourceRels = source.opc.relationshipsFor(sourceSlide.partName);
|
|
3821
4362
|
const targetRels = dest.opc.relationshipsFor(newPartName);
|
|
4363
|
+
const owned = newOwnedScope();
|
|
3822
4364
|
for (const rel of sourceRels) {
|
|
3823
4365
|
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide") continue;
|
|
3824
4366
|
if (rel.type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") {
|
|
@@ -3829,10 +4371,10 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
|
|
|
3829
4371
|
targetRels.addWithId(rel.id, rel.type, rel.target, "External");
|
|
3830
4372
|
continue;
|
|
3831
4373
|
}
|
|
3832
|
-
const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id));
|
|
4374
|
+
const newTarget = copyPart(ctx, sourceRels.resolveTarget(rel.id), isSharedByPageCopies(rel.type) ? void 0 : owned);
|
|
3833
4375
|
targetRels.addWithId(rel.id, rel.type, relativePartName(newPartName, newTarget));
|
|
3834
4376
|
}
|
|
3835
|
-
if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName);
|
|
4377
|
+
if (carryGraphics) carryMasterGraphics(dest, ctx, slideRoot, newPartName, sourceSlide.partName, owned);
|
|
3836
4378
|
return {
|
|
3837
4379
|
newPartName,
|
|
3838
4380
|
slideRoot,
|
|
@@ -3853,8 +4395,9 @@ function importSlideRebind(dest, ctx, source, sourceSlide, carryGraphics) {
|
|
|
3853
4395
|
* @param {Element} slideRoot - root element of the new slide part (mutated in place)
|
|
3854
4396
|
* @param {string} newPartName - partname of the new slide part
|
|
3855
4397
|
* @param {string} slidePartName - partname of the source slide, for resolving its layout/master
|
|
4398
|
+
* @param {OwnedScope} owned - the page's ownership scope, so a decoration's own parts land in it
|
|
3856
4399
|
*/
|
|
3857
|
-
function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
|
|
4400
|
+
function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName, owned) {
|
|
3858
4401
|
const sourceOpc = ctx.source;
|
|
3859
4402
|
const layoutPartName = resolveSingleRel(sourceOpc, slidePartName, SLIDE_LAYOUT_REL);
|
|
3860
4403
|
const masterPartName = layoutPartName ? resolveSingleRel(sourceOpc, layoutPartName, SLIDE_MASTER_REL) : null;
|
|
@@ -3872,7 +4415,7 @@ function carryMasterGraphics(dest, ctx, slideRoot, newPartName, slidePartName) {
|
|
|
3872
4415
|
const sourceRels = sourceOpc.relationshipsFor(partName);
|
|
3873
4416
|
for (const deco of decorations) {
|
|
3874
4417
|
const imported = doc.importNode(deco, true);
|
|
3875
|
-
rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap);
|
|
4418
|
+
rewriteCarriedRels(imported, ctx, sourceRels, newPartName, slideRels, relIdMap, owned);
|
|
3876
4419
|
spTree.insertBefore(imported, anchor);
|
|
3877
4420
|
}
|
|
3878
4421
|
}
|
|
@@ -3895,138 +4438,6 @@ function destinationLayoutPartName(dest) {
|
|
|
3895
4438
|
return masterRels.resolveTarget(layoutRel.id);
|
|
3896
4439
|
}
|
|
3897
4440
|
//#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
4441
|
//#region src/read/api/ops/prune.ts
|
|
4031
4442
|
/**
|
|
4032
4443
|
* Orphan pruning after a part stops being referenced.
|
|
@@ -4144,6 +4555,9 @@ function rescalePartGeometry(dest, rescaledParts, partName, transform) {
|
|
|
4144
4555
|
*/
|
|
4145
4556
|
const HYPERLINK_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
4146
4557
|
const CHART_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
|
|
4558
|
+
const CHARTEX_REL = "http://schemas.microsoft.com/office/2014/relationships/chartEx";
|
|
4559
|
+
const CHART_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartStyle";
|
|
4560
|
+
const CHART_COLOR_STYLE_REL = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle";
|
|
4147
4561
|
const PACKAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
|
|
4148
4562
|
const AUDIO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
|
|
4149
4563
|
const VIDEO_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
|
|
@@ -4151,6 +4565,9 @@ const MS_MEDIA_REL = "http://schemas.microsoft.com/office/2007/relationships/med
|
|
|
4151
4565
|
const SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
|
|
4152
4566
|
const NOTES_SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
|
|
4153
4567
|
const CHART_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
|
|
4568
|
+
const CHARTEX_CONTENT_TYPE = "application/vnd.ms-office.chartex+xml";
|
|
4569
|
+
const CHART_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartstyle+xml";
|
|
4570
|
+
const CHART_COLOR_STYLE_CONTENT_TYPE = "application/vnd.ms-office.chartcolorstyle+xml";
|
|
4154
4571
|
const XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
4155
4572
|
/** Content type of the main part in an editable `.pptx` package. */
|
|
4156
4573
|
const PRESENTATION_MAIN_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
|
|
@@ -4380,12 +4797,17 @@ var Presentation = class Presentation {
|
|
|
4380
4797
|
* Duplicate the slide at `index` and insert the copy at `options.at` (deck
|
|
4381
4798
|
* order; `0` = first), defaulting to appending at the end when `at` is omitted
|
|
4382
4799
|
* 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
|
|
4384
|
-
*
|
|
4385
|
-
* `p:sldId` entry are wired up. Marks the presentation part
|
|
4800
|
+
* bytes verbatim and shares the source's deck-wide relationship targets
|
|
4801
|
+
* (layout, images, media) by copying its `.rels`; a new presentation→slide
|
|
4802
|
+
* relationship and a `p:sldId` entry are wired up. Marks the presentation part
|
|
4803
|
+
* dirty.
|
|
4386
4804
|
*
|
|
4387
|
-
*
|
|
4388
|
-
*
|
|
4805
|
+
* What the source page *owns* is copied rather than shared: its notes slide,
|
|
4806
|
+
* charts, SmartArt diagrams and OLE embeddings, each with the subtree under it
|
|
4807
|
+
* (a chart's embedded workbook comes along; the image inside its user-shapes
|
|
4808
|
+
* drawing stays shared). PowerPoint refuses to open a deck where two slides
|
|
4809
|
+
* resolve to one chart or diagram, so this is not tidiness — see
|
|
4810
|
+
* `page-owned.ts` for the rule and the evidence behind it.
|
|
4389
4811
|
*/
|
|
4390
4812
|
cloneSlide(index, options = {}) {
|
|
4391
4813
|
const source = this.slides[index];
|
|
@@ -4394,8 +4816,10 @@ var Presentation = class Presentation {
|
|
|
4394
4816
|
const sourcePart = source.part;
|
|
4395
4817
|
const newPartName = opc.reservePartNameLike(sourcePart.partName);
|
|
4396
4818
|
const newPart = opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
|
|
4397
|
-
const sourceRels = opc.
|
|
4398
|
-
|
|
4819
|
+
const sourceRels = opc.relationshipsFor(sourcePart.partName);
|
|
4820
|
+
const cloneRels = opc.relationshipsFor(newPartName);
|
|
4821
|
+
for (const rel of sourceRels) cloneRels.addWithId(rel.id, rel.type, rel.target, rel.targetMode);
|
|
4822
|
+
duplicateOwnedTargets(opc, sourcePart.partName, newPartName);
|
|
4399
4823
|
return this.#insertSlidePart(newPart, options.at);
|
|
4400
4824
|
}
|
|
4401
4825
|
/**
|
|
@@ -4448,7 +4872,10 @@ var Presentation = class Presentation {
|
|
|
4448
4872
|
* Only the layout(s) actually used by imported slides are copied; the imported
|
|
4449
4873
|
* master's `p:sldLayoutIdLst` is pruned to exactly those, mirroring how
|
|
4450
4874
|
* PowerPoint's "Reuse Slides" brings a slide across. Parts shared by repeated
|
|
4451
|
-
* imports from the same source deck are copied once and reused
|
|
4875
|
+
* imports from the same source deck are copied once and reused — every part
|
|
4876
|
+
* except the page itself. Importing the same source slide twice yields two
|
|
4877
|
+
* independent copies over one shared layout/master/theme, which is what lets a
|
|
4878
|
+
* deck show one page twice (verbatim, then edited).
|
|
4452
4879
|
*
|
|
4453
4880
|
* With `{ theme: 'preserve' }` the slide's source theme is instead *flattened*
|
|
4454
4881
|
* into the slide XML and the slide is bound to this deck's existing
|
|
@@ -4473,7 +4900,7 @@ var Presentation = class Presentation {
|
|
|
4473
4900
|
}
|
|
4474
4901
|
if (sizesDiffer && options.rescale && (!target || !incoming)) throw new InvalidOptionError("import/slide-size-unknown", "importSlide rescale requires both decks to declare a slide size (p:sldSz)");
|
|
4475
4902
|
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) :
|
|
4903
|
+
const newPartName = options.theme === "preserve" ? importSlidePreserve(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true) : options.theme === "restyle" ? importSlideRestyle(this, importCtx, source, sourceSlide, options.carryMasterGraphics === true, options.remapLiterals === true) : copySlidePart(importCtx, sourceSlide.partName);
|
|
4477
4904
|
const newPart = this.opc.part(newPartName);
|
|
4478
4905
|
if (!newPart) throw new InternalError("import/part-went-missing", `Imported slide part went missing: ${newPartName}`);
|
|
4479
4906
|
if (sizesDiffer && options.rescale && target && incoming) rescaleImportedGeometry(this, this.#rescaledParts, newPartName, options.theme, incoming, target, options.rescale === true ? "fit" : options.rescale);
|
|
@@ -4483,6 +4910,140 @@ var Presentation = class Presentation {
|
|
|
4483
4910
|
return slide;
|
|
4484
4911
|
}
|
|
4485
4912
|
/**
|
|
4913
|
+
* Import selected pages from one or more loaded source presentations as one
|
|
4914
|
+
* batch. Each imported page lands at its `outputIndex` in the complete
|
|
4915
|
+
* destination slide list after the batch, and the returned array is parallel
|
|
4916
|
+
* to `requests` — `result[i]` is the page `requests[i]` asked for, whatever
|
|
4917
|
+
* order the output positions were given in.
|
|
4918
|
+
*
|
|
4919
|
+
* Everything is checked before a single byte of this deck moves: every request
|
|
4920
|
+
* names an existing source page, final output positions are unique, each source
|
|
4921
|
+
* has this deck's slide size, and a read-only dry run of the copy proves every
|
|
4922
|
+
* part it would reach is present. A batch therefore either applies in full or
|
|
4923
|
+
* leaves this deck byte-identical, where a per-page loop of
|
|
4924
|
+
* {@link importSlide} can leave a half-stitched deck behind.
|
|
4925
|
+
*
|
|
4926
|
+
* The batch also decides what a `slide → slide` link means: an internal link
|
|
4927
|
+
* on a selected page must target another **selected** page (or one this deck
|
|
4928
|
+
* already contains via an earlier import from that source), and is rewritten
|
|
4929
|
+
* to the fresh partname — importing page 3 of 10 does not drag pages 1–2
|
|
4930
|
+
* across as dependencies, and never strands the link. This mirrors
|
|
4931
|
+
* `appendSlides`' `import/unresolved-slide-link`, which the write side already
|
|
4932
|
+
* enforces for generator decks.
|
|
4933
|
+
*
|
|
4934
|
+
* One request is one output page, so naming the same source page in several
|
|
4935
|
+
* requests is how you ask for several independent copies of it — the page part
|
|
4936
|
+
* is the one thing an import never shares, exactly as in {@link importSlide}.
|
|
4937
|
+
* Everything under it (layout, master, theme, media) is still copied once and
|
|
4938
|
+
* shared. Where such a page is the target of a `slide → slide` link, the link
|
|
4939
|
+
* resolves to one of its copies: pages duplicated together are copied in
|
|
4940
|
+
* lockstep and link to their round-mates, and a link into a page requested only
|
|
4941
|
+
* once always lands on that single copy.
|
|
4942
|
+
*
|
|
4943
|
+
* Speaker notes travel per request: `{ importNotes: true }` carries that page's
|
|
4944
|
+
* `notesSlide` part across, wired to the new page and bound to a single
|
|
4945
|
+
* `notesMaster` under the same 0..1 rule {@link importSlide} and
|
|
4946
|
+
* {@link appendSlides} follow — the destination's own master wins when it has
|
|
4947
|
+
* one. Notes are part of the up-front dry run too, so a batch that would fail
|
|
4948
|
+
* carrying them is refused with the deck still byte-identical. A page named in
|
|
4949
|
+
* several requests gets its own copy of its notes each time, as of everything
|
|
4950
|
+
* else that page owns.
|
|
4951
|
+
*
|
|
4952
|
+
* Scope: pages come across under `'copy'` theme semantics (their own layout →
|
|
4953
|
+
* master → theme subgraph, shared parts deduped via the copy registry).
|
|
4954
|
+
* Embedded fonts are not carried, and sizes must match, since there is no batch
|
|
4955
|
+
* spelling for `embedFonts` or `rescale`. Use {@link importSlide} when you need
|
|
4956
|
+
* either of those.
|
|
4957
|
+
*/
|
|
4958
|
+
importSlides(requests) {
|
|
4959
|
+
const resolved = requests.map((request, requestIndex) => {
|
|
4960
|
+
if (!Number.isInteger(request.sourceIndex) || request.sourceIndex < 0) throw new InvalidOptionError("slide/index-out-of-range", `importSlides: sourceIndex must be a non-negative integer; received ${request.sourceIndex}`);
|
|
4961
|
+
if (!Number.isInteger(request.outputIndex) || request.outputIndex < 0) throw new InvalidOptionError("import/output-index-out-of-range", `importSlides: outputIndex must be a non-negative integer; received ${request.outputIndex}`);
|
|
4962
|
+
const sourceSlide = request.source.slides[request.sourceIndex];
|
|
4963
|
+
if (!sourceSlide) throw new InvalidOptionError("slide/index-out-of-range", `importSlides: no slide at index ${request.sourceIndex} to import`);
|
|
4964
|
+
return {
|
|
4965
|
+
...request,
|
|
4966
|
+
requestIndex,
|
|
4967
|
+
sourceSlide
|
|
4968
|
+
};
|
|
4969
|
+
});
|
|
4970
|
+
if (resolved.length === 0) return [];
|
|
4971
|
+
if (!this.presentationPart.dom.documentElement) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to insert slides into");
|
|
4972
|
+
const finalSlideCount = this.slides.length + resolved.length;
|
|
4973
|
+
const selectedPages = /* @__PURE__ */ new Map();
|
|
4974
|
+
const notesPages = /* @__PURE__ */ new Map();
|
|
4975
|
+
for (const request of resolved) {
|
|
4976
|
+
let pages = selectedPages.get(request.source.opc);
|
|
4977
|
+
if (!pages) {
|
|
4978
|
+
pages = /* @__PURE__ */ new Set();
|
|
4979
|
+
selectedPages.set(request.source.opc, pages);
|
|
4980
|
+
}
|
|
4981
|
+
pages.add(request.sourceSlide.partName);
|
|
4982
|
+
if (!request.importNotes) continue;
|
|
4983
|
+
let withNotes = notesPages.get(request.source.opc);
|
|
4984
|
+
if (!withNotes) {
|
|
4985
|
+
withNotes = /* @__PURE__ */ new Set();
|
|
4986
|
+
notesPages.set(request.source.opc, withNotes);
|
|
4987
|
+
}
|
|
4988
|
+
withNotes.add(request.sourceSlide.partName);
|
|
4989
|
+
}
|
|
4990
|
+
const target = this.slideSize;
|
|
4991
|
+
const outputIndexes = /* @__PURE__ */ new Set();
|
|
4992
|
+
for (const request of resolved) {
|
|
4993
|
+
if (request.outputIndex >= finalSlideCount) throw new InvalidOptionError("import/output-index-out-of-range", `importSlides: outputIndex ${request.outputIndex} is outside the final slide list of ${finalSlideCount} slides`);
|
|
4994
|
+
if (outputIndexes.has(request.outputIndex)) throw new InvalidOptionError("import/output-index-conflict", `importSlides: outputIndex ${request.outputIndex} is requested more than once`);
|
|
4995
|
+
outputIndexes.add(request.outputIndex);
|
|
4996
|
+
const incoming = request.source.slideSize;
|
|
4997
|
+
if (!target || !incoming || target.widthEmu !== incoming.widthEmu || target.heightEmu !== incoming.heightEmu) {
|
|
4998
|
+
const fmt = (size) => size ? `${size.widthEmu}×${size.heightEmu} EMU` : "unknown";
|
|
4999
|
+
throw new InvalidOptionError("import/slide-size-mismatch", `importSlides requires equal slide sizes; target is ${fmt(target)}, source is ${fmt(incoming)}`);
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
const copyMaster = this.opc.relationshipsFor(this.presentationPart.partName).byType(NOTES_MASTER_REL).length === 0;
|
|
5003
|
+
for (const [sourceOpc, pages] of selectedPages) checkSelectionCopyable(sourceOpc, this.#importContext(sourceOpc).registry, pages, {
|
|
5004
|
+
pages: notesPages.get(sourceOpc) ?? /* @__PURE__ */ new Set(),
|
|
5005
|
+
copyMaster
|
|
5006
|
+
});
|
|
5007
|
+
const destinationsBySource = /* @__PURE__ */ new Map();
|
|
5008
|
+
const planned = resolved.map((request) => {
|
|
5009
|
+
let destinations = destinationsBySource.get(request.source.opc);
|
|
5010
|
+
if (!destinations) {
|
|
5011
|
+
destinations = /* @__PURE__ */ new Map();
|
|
5012
|
+
destinationsBySource.set(request.source.opc, destinations);
|
|
5013
|
+
}
|
|
5014
|
+
const sourcePart = request.sourceSlide.part;
|
|
5015
|
+
const newPartName = this.opc.reservePartNameLike(request.sourceSlide.partName);
|
|
5016
|
+
const destPart = this.opc.addPart(newPartName, sourcePart.contentType, sourcePart.bytes);
|
|
5017
|
+
const reserved = destinations.get(request.sourceSlide.partName);
|
|
5018
|
+
if (reserved) reserved.push(newPartName);
|
|
5019
|
+
else destinations.set(request.sourceSlide.partName, [newPartName]);
|
|
5020
|
+
return {
|
|
5021
|
+
...request,
|
|
5022
|
+
destPart
|
|
5023
|
+
};
|
|
5024
|
+
});
|
|
5025
|
+
for (const [sourceOpc, destinations] of destinationsBySource) {
|
|
5026
|
+
const base = this.#importContext(sourceOpc);
|
|
5027
|
+
const rounds = Math.max(...[...destinations.values()].map((reserved) => reserved.length));
|
|
5028
|
+
for (let round = 0; round < rounds; round++) {
|
|
5029
|
+
const plan = /* @__PURE__ */ new Map();
|
|
5030
|
+
for (const [sourcePartName, reserved] of destinations) plan.set(sourcePartName, reserved[round] ?? reserved[0]);
|
|
5031
|
+
const ctx = {
|
|
5032
|
+
...base,
|
|
5033
|
+
selection: { destinations: plan }
|
|
5034
|
+
};
|
|
5035
|
+
for (const [sourcePartName, reserved] of destinations) if (round < reserved.length) copyPart(ctx, sourcePartName);
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
for (const request of planned) {
|
|
5039
|
+
if (!request.importNotes) continue;
|
|
5040
|
+
carryNotes(this, request.source, this.#importContext(request.source.opc), request.sourceSlide.partName, request.destPart.partName);
|
|
5041
|
+
}
|
|
5042
|
+
const added = [];
|
|
5043
|
+
for (const request of [...planned].sort((left, right) => left.outputIndex - right.outputIndex)) added[request.requestIndex] = this.#insertSlidePart(request.destPart, request.outputIndex);
|
|
5044
|
+
return added;
|
|
5045
|
+
}
|
|
5046
|
+
/**
|
|
4486
5047
|
* Graft slide master(s) from another open package into this one and return what
|
|
4487
5048
|
* was copied. Unlike {@link importSlide} — which brings a master across only as
|
|
4488
5049
|
* the dependency of an imported *slide* and prunes it to the one layout that
|
|
@@ -4592,7 +5153,10 @@ var Presentation = class Presentation {
|
|
|
4592
5153
|
*
|
|
4593
5154
|
* Charts and internal slide-to-slide hyperlinks are carried across: chart parts
|
|
4594
5155
|
* (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.
|
|
5156
|
+
* `slide:N` link is repointed at the Nth appended slide's new partname. A chartEx
|
|
5157
|
+
* (Office 2016 — waterfall, funnel, treemap, ...) chart carries too, as its own
|
|
5158
|
+
* `chartEx{N}.xml` part behind the MS chartEx rel, with the style and color-style
|
|
5159
|
+
* sidecars PowerPoint requires alongside it.
|
|
4596
5160
|
*
|
|
4597
5161
|
* The generator's presentation-level embedded fonts (`pptx.embedFont`) are also
|
|
4598
5162
|
* carried into this deck and merged into its `p:embeddedFontLst`, de-duped by
|
|
@@ -4688,13 +5252,23 @@ var Presentation = class Presentation {
|
|
|
4688
5252
|
for (const h of slide.notes.hyperlinks) notesRels.addWithId(`rId${h.rId}`, HYPERLINK_REL, h.target, "External");
|
|
4689
5253
|
}
|
|
4690
5254
|
for (const c of slide.charts) {
|
|
4691
|
-
const
|
|
4692
|
-
this.opc.
|
|
5255
|
+
const isChartEx = c.chartEx !== void 0;
|
|
5256
|
+
const chartPartName = this.opc.reservePartNameLike(isChartEx ? "/ppt/charts/chartEx1.xml" : "/ppt/charts/chart1.xml");
|
|
5257
|
+
this.opc.addPart(chartPartName, isChartEx ? CHARTEX_CONTENT_TYPE : CHART_CONTENT_TYPE, textEncoder.encode(c.chartXml));
|
|
4693
5258
|
const embeddingPartName = this.opc.reservePartNameLike("/ppt/embeddings/Microsoft_Excel_Worksheet1.xlsx");
|
|
4694
5259
|
this.opc.contentTypes.ensureDefault("xlsx", XLSX_CONTENT_TYPE);
|
|
4695
5260
|
this.opc.addPart(embeddingPartName, XLSX_CONTENT_TYPE, c.embeddingBytes);
|
|
4696
|
-
this.opc.relationshipsFor(chartPartName)
|
|
4697
|
-
|
|
5261
|
+
const chartRels = this.opc.relationshipsFor(chartPartName);
|
|
5262
|
+
chartRels.addWithId("rId1", PACKAGE_REL, relativePartName(chartPartName, embeddingPartName));
|
|
5263
|
+
if (c.chartEx) {
|
|
5264
|
+
const colorsPartName = this.opc.reservePartNameLike("/ppt/charts/colors1.xml");
|
|
5265
|
+
this.opc.addPart(colorsPartName, CHART_COLOR_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.colorsXml));
|
|
5266
|
+
chartRels.addWithId("rId2", CHART_COLOR_STYLE_REL, relativePartName(chartPartName, colorsPartName));
|
|
5267
|
+
const stylePartName = this.opc.reservePartNameLike("/ppt/charts/style1.xml");
|
|
5268
|
+
this.opc.addPart(stylePartName, CHART_STYLE_CONTENT_TYPE, textEncoder.encode(c.chartEx.styleXml));
|
|
5269
|
+
chartRels.addWithId("rId3", CHART_STYLE_REL, relativePartName(chartPartName, stylePartName));
|
|
5270
|
+
}
|
|
5271
|
+
rels.addWithId(`rId${c.rId}`, isChartEx ? CHARTEX_REL : CHART_REL, relativePartName(partName, chartPartName));
|
|
4698
5272
|
}
|
|
4699
5273
|
for (const link of slide.slideLinks) {
|
|
4700
5274
|
const targetPartName = partBySourceNumber.get(link.sourceSlideNumber);
|
|
@@ -4777,7 +5351,7 @@ var Presentation = class Presentation {
|
|
|
4777
5351
|
const result = [];
|
|
4778
5352
|
for (const shapeEl of sourceElements) {
|
|
4779
5353
|
const imported = targetDoc.importNode(shapeEl, true);
|
|
4780
|
-
rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap);
|
|
5354
|
+
rewriteCarriedRels(imported, importCtx, sourceRels, target.partName, targetRels, relIdMap, newOwnedScope());
|
|
4781
5355
|
if (ctx) {
|
|
4782
5356
|
const holder = createElement(targetDoc, "p:spTree");
|
|
4783
5357
|
holder.appendChild(imported);
|
|
@@ -4839,7 +5413,13 @@ var Presentation = class Presentation {
|
|
|
4839
5413
|
*/
|
|
4840
5414
|
#insertSlidePart(newPart, at) {
|
|
4841
5415
|
const presPart = this.presentationPart;
|
|
4842
|
-
const
|
|
5416
|
+
const presRels = this.opc.relationshipsFor(presPart.partName);
|
|
5417
|
+
for (const rel of presRels) {
|
|
5418
|
+
if (rel.type !== "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" || rel.targetMode === "External") continue;
|
|
5419
|
+
if (presRels.resolveTarget(rel.id) !== newPart.partName) continue;
|
|
5420
|
+
throw new InternalError("slide/part-already-in-deck", `Slide part ${newPart.partName} is already in this deck's slide list; a second p:sldId entry for it would make the package unopenable`);
|
|
5421
|
+
}
|
|
5422
|
+
const relId = presRels.add(SLIDE_REL, relativePartName(presPart.partName, newPart.partName)).id;
|
|
4843
5423
|
const root = presPart.dom.documentElement;
|
|
4844
5424
|
if (!root) throw new PackageReadError("package/part-has-no-root", "presentation.xml has no document element to append a slide to");
|
|
4845
5425
|
const sldIdLst = getOrAddChild(root, "p:sldIdLst", PRESENTATION_SLD_ID_LST_SUCCESSORS);
|
|
@@ -4873,4 +5453,4 @@ var Presentation = class Presentation {
|
|
|
4873
5453
|
//#endregion
|
|
4874
5454
|
export { Placeholder as a, Theme as c, NotesSlide as d, OpcPackage as f, ContentTypes as h, Slide as i, readTagsForPart as l, Part as m, readCoreProperties as n, SlideLayout as o, Relationships as p, readCustomProperties as r, SlideMaster as s, Presentation as t, NotesPlaceholder as u };
|
|
4875
5455
|
|
|
4876
|
-
//# sourceMappingURL=presentation-
|
|
5456
|
+
//# sourceMappingURL=presentation-DeKTJGHf.js.map
|