pptx-ts 3.7.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +116 -269
  2. package/dist/autopage-odPz7zZO.js +536 -0
  3. package/dist/autopage-odPz7zZO.js.map +1 -0
  4. package/dist/binary-string-CKVAYI3M.js +24 -0
  5. package/dist/binary-string-CKVAYI3M.js.map +1 -0
  6. package/dist/body-insets-Dw_BeWD_.js +54 -0
  7. package/dist/body-insets-Dw_BeWD_.js.map +1 -0
  8. package/dist/browser.d.ts +29 -9
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +41 -14
  11. package/dist/browser.js.map +1 -1
  12. package/dist/clip-Bl01BPcN.d.ts +67 -0
  13. package/dist/clip-Bl01BPcN.d.ts.map +1 -0
  14. package/dist/diagnostics-D8ot4Ei7.d.ts +244 -0
  15. package/dist/diagnostics-D8ot4Ei7.d.ts.map +1 -0
  16. package/dist/embedded-fonts-DarT9aoa.d.ts +20 -0
  17. package/dist/embedded-fonts-DarT9aoa.d.ts.map +1 -0
  18. package/dist/entry-compose-DCnvBZz2.d.ts +1046 -0
  19. package/dist/entry-compose-DCnvBZz2.d.ts.map +1 -0
  20. package/dist/{enums-CvRYniUA.d.ts → enums-BUKTUB6V.d.ts} +21 -13
  21. package/dist/{enums-CvRYniUA.d.ts.map → enums-BUKTUB6V.d.ts.map} +1 -1
  22. package/dist/{errors-Bvln0H-7.js → errors-C4oENPcu.js} +92 -2
  23. package/dist/errors-C4oENPcu.js.map +1 -0
  24. package/dist/families.d.ts +260 -0
  25. package/dist/families.d.ts.map +1 -0
  26. package/dist/families.js +57 -0
  27. package/dist/families.js.map +1 -0
  28. package/dist/{fit-BKs0zxMI.js → fit-Cb7naFj6.js} +249 -167
  29. package/dist/fit-Cb7naFj6.js.map +1 -0
  30. package/dist/font-metrics-v5fn7s7e.d.ts +613 -0
  31. package/dist/font-metrics-v5fn7s7e.d.ts.map +1 -0
  32. package/dist/grid-DikTjFey.js +187 -0
  33. package/dist/grid-DikTjFey.js.map +1 -0
  34. package/dist/{html-dom-B0I0QSbU.js → html-dom-Iuog2wdK.js} +121 -91
  35. package/dist/html-dom-Iuog2wdK.js.map +1 -0
  36. package/dist/html.d.ts +6 -6
  37. package/dist/html.d.ts.map +1 -1
  38. package/dist/html.js +7 -6
  39. package/dist/html.js.map +1 -1
  40. package/dist/image-size-CvN2iCA4.js +1611 -0
  41. package/dist/image-size-CvN2iCA4.js.map +1 -0
  42. package/dist/index.d.ts +24 -10
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +31 -11
  45. package/dist/index.js.map +1 -1
  46. package/dist/inspect.d.ts +40 -31
  47. package/dist/inspect.d.ts.map +1 -1
  48. package/dist/inspect.js +40 -48
  49. package/dist/inspect.js.map +1 -1
  50. package/dist/math.d.ts +7 -5
  51. package/dist/math.d.ts.map +1 -1
  52. package/dist/math.js +16 -7
  53. package/dist/math.js.map +1 -1
  54. package/dist/measure.d.ts +12 -225
  55. package/dist/measure.d.ts.map +1 -1
  56. package/dist/measure.js +3 -3
  57. package/dist/node.d.ts +22 -8
  58. package/dist/node.d.ts.map +1 -1
  59. package/dist/node.js +27 -7
  60. package/dist/node.js.map +1 -1
  61. package/dist/notes-Dh12JAbn.js +3264 -0
  62. package/dist/notes-Dh12JAbn.js.map +1 -0
  63. package/dist/options-internal-WqgMf720.js +43 -0
  64. package/dist/options-internal-WqgMf720.js.map +1 -0
  65. package/dist/{package-CaOLXSNi.d.ts → package-J49Ck6S2.d.ts} +27 -12
  66. package/dist/package-J49Ck6S2.d.ts.map +1 -0
  67. package/dist/playbtn-CUqQdoDf.js +22 -0
  68. package/dist/playbtn-CUqQdoDf.js.map +1 -0
  69. package/dist/{slide-ZqAdVvHV.d.ts → pres-C6qgOFAa.d.ts} +1021 -1423
  70. package/dist/pres-C6qgOFAa.d.ts.map +1 -0
  71. package/dist/{presentation-DrJtj3-C.js → presentation-DGYlAWhl.js} +3208 -2727
  72. package/dist/presentation-DGYlAWhl.js.map +1 -0
  73. package/dist/{presentation-types-CDjScsev.d.ts → presentation-types-8jLQHI4M.d.ts} +1070 -683
  74. package/dist/presentation-types-8jLQHI4M.d.ts.map +1 -0
  75. package/dist/read.d.ts +9 -9
  76. package/dist/read.d.ts.map +1 -1
  77. package/dist/read.js +4 -4
  78. package/dist/script.d.ts +80 -66
  79. package/dist/script.d.ts.map +1 -1
  80. package/dist/script.js +2399 -1139
  81. package/dist/script.js.map +1 -1
  82. package/dist/{shapes-BemroU-z.js → shapes-re0KyQk_.js} +2807 -1609
  83. package/dist/shapes-re0KyQk_.js.map +1 -0
  84. package/dist/slide-Dx2ZeVZ6.d.ts +1256 -0
  85. package/dist/slide-Dx2ZeVZ6.d.ts.map +1 -0
  86. package/dist/slide-size-BalTjFuv.js +88 -0
  87. package/dist/slide-size-BalTjFuv.js.map +1 -0
  88. package/dist/{units-internal-Dg95w_YD.js → table-cell-margins-EVg0EwxT.js} +235 -273
  89. package/dist/table-cell-margins-EVg0EwxT.js.map +1 -0
  90. package/dist/table-dom-BTDP3lEP.js +21 -0
  91. package/dist/table-dom-BTDP3lEP.js.map +1 -0
  92. package/dist/text-anchor-CrYXAfUu.js +176 -0
  93. package/dist/text-anchor-CrYXAfUu.js.map +1 -0
  94. package/dist/text-sN64Otf_.js +3980 -0
  95. package/dist/text-sN64Otf_.js.map +1 -0
  96. package/dist/transition-speed-kUEWDWTx.js +14 -0
  97. package/dist/transition-speed-kUEWDWTx.js.map +1 -0
  98. package/dist/units-internal-XJlf-hOl.js +649 -0
  99. package/dist/units-internal-XJlf-hOl.js.map +1 -0
  100. package/dist/zip-C0leZZMj.d.ts +67 -0
  101. package/dist/zip-C0leZZMj.d.ts.map +1 -0
  102. package/dist/zip-CKd8ihcf.js +177 -0
  103. package/dist/zip-CKd8ihcf.js.map +1 -0
  104. package/dist/zip.d.ts +3 -68
  105. package/dist/zip.js +3 -163
  106. package/dist/zoom-DPUHh85d.js +8865 -0
  107. package/dist/zoom-DPUHh85d.js.map +1 -0
  108. package/package.json +106 -19
  109. package/skills/ts-pptx-upstream/SKILL.md +58 -57
  110. package/dist/autopage-DkjYZUFD.js +0 -423
  111. package/dist/autopage-DkjYZUFD.js.map +0 -1
  112. package/dist/diagnostics-DfBdDHre.d.ts +0 -829
  113. package/dist/diagnostics-DfBdDHre.d.ts.map +0 -1
  114. package/dist/errors-Bvln0H-7.js.map +0 -1
  115. package/dist/errors-PflMPWBn.d.ts +0 -182
  116. package/dist/errors-PflMPWBn.d.ts.map +0 -1
  117. package/dist/fit-BKs0zxMI.js.map +0 -1
  118. package/dist/html-dom-B0I0QSbU.js.map +0 -1
  119. package/dist/namespaces-BVnNu7sg.js +0 -666
  120. package/dist/namespaces-BVnNu7sg.js.map +0 -1
  121. package/dist/notes-CJFJaG6M.js +0 -1724
  122. package/dist/notes-CJFJaG6M.js.map +0 -1
  123. package/dist/package-CaOLXSNi.d.ts.map +0 -1
  124. package/dist/pres-BcBR3CSM.d.ts +0 -202
  125. package/dist/pres-BcBR3CSM.d.ts.map +0 -1
  126. package/dist/presentation-DrJtj3-C.js.map +0 -1
  127. package/dist/presentation-types-CDjScsev.d.ts.map +0 -1
  128. package/dist/shapes-BemroU-z.js.map +0 -1
  129. package/dist/slide-ZqAdVvHV.d.ts.map +0 -1
  130. package/dist/text-B8SLEaZx.js +0 -11327
  131. package/dist/text-B8SLEaZx.js.map +0 -1
  132. package/dist/units-1kRzYShk.js +0 -231
  133. package/dist/units-1kRzYShk.js.map +0 -1
  134. package/dist/units-internal-Dg95w_YD.js.map +0 -1
  135. package/dist/zip.d.ts.map +0 -1
  136. package/dist/zip.js.map +0 -1
@@ -0,0 +1,3980 @@
1
+ import { l as warn, n as InvalidOptionError, r as MediaError, t as InternalError, u as warnOnce } from "./errors-C4oENPcu.js";
2
+ import { F as DEF_PRES_LAYOUT_NAME, J as SLDNUM_PLACEHOLDER_TEXT, P as DEF_PRES_LAYOUT, R as DEF_SLIDE_MARGIN_IN, U as LEVEL_MARGINS_EMU, V as LAYOUT_IDX_SERIES_BASE, X as MIN_SLIDE_MASTER_ID, a as convertRotationDegrees, n as clampRangedInput, q as SLDNUMFLDID, s as getSmartParseNumber, u as mapStated, v as resolveInsetsEmu } from "./units-internal-XJlf-hOl.js";
3
+ import { A as inchesToEmu, D as emuToInches, F as EMU_PER_INCH, T as STANDARD_LAYOUTS } from "./table-cell-margins-EVg0EwxT.js";
4
+ import { $t as THEME_REL, Bt as PRES_PROPS_CONTENT_TYPE, Dt as MS_MEDIA_REL, E as EMBEDDED_FONT_SLOTS, Ft as OFFICE_DOCUMENT_REL, Gt as SLIDE_LAYOUT_CONTENT_TYPE, Ht as RELATIONSHIPS_CONTENT_TYPE, Jt as SLIDE_MASTER_REL, Kt as SLIDE_LAYOUT_REL, Nt as NOTES_SLIDE_REL, Qt as THEME_CONTENT_TYPE, Vt as PRES_PROPS_REL, Wt as SLIDE_CONTENT_TYPE, Xt as TABLE_STYLES_CONTENT_TYPE, Yt as SLIDE_REL, Zt as TABLE_STYLES_REL, _t as CORE_PROPS_REL, a as textAlignToken, an as PML_ROOT_NS, at as CHARTEX_REL, bt as EXTENDED_PROPS_CONTENT_TYPE, c as checkEnumOrThrow, cn as decodeBase64ToBytes, d as xsdBool, dn as toMediaDataUri, en as VIDEO_REL, f as xsdBoolIfTrue, gt as CORE_PROPS_CONTENT_TYPE, h as stripHash, ht as COMMENT_AUTHORS_REL, in as OOXML_NS, jt as NOTES_MASTER_REL, l as checkEnumOrWarn, lt as CHART_REL, nn as VIEW_PROPS_REL, nt as TRANSITION_VARIANT_ATTRIBUTES, p as isHexColor, pt as COMMENTS_REL, qt as SLIDE_MASTER_CONTENT_TYPE, rt as AUDIO_REL, sn as bytesToBase64, tn as VIEW_PROPS_CONTENT_TYPE, tt as TRANSITION_TYPES, vt as CUSTOM_PROPS_CONTENT_TYPE, w as DEFAULT_COLOR_MAP, wt as IMAGE_REL, xt as EXTENDED_PROPS_REL, yt as CUSTOM_PROPS_REL, zt as PRESENTATION_MAIN_CONTENT_TYPE } from "./image-size-CvN2iCA4.js";
5
+ import { a as avContentType, i as audioExtensionForSubtype, o as dataUriMediaType, s as imageContentType } from "./text-anchor-CrYXAfUu.js";
6
+ import { At as getDuplicateObjectNames, C as targetBetweenPptSubparts, Ct as FONT_DATA_CONTENT_TYPE, D as cNvPrEl, Dt as serializeEmbeddedFontLst, Et as flattenEmbeddedFaces, G as clampParaMarginInchesEmu, It as preencodedPath, K as buAutoNumEl, N as spTreeEl, Nt as isHyperlinkRel, Ot as XML_DECL, Pt as isPreencodedPath, R as prstGeomRect, Rt as encodeXmlAttrValue, S as slidePath, St as voidEl, T as targetFromPresentation, Tt as FONT_REL_TYPE, U as clampFontSizeSz, W as clampParaIndentInchesEmu, _ as fontPath, at as fillNamesPaint, b as relsPath, bt as elOrVoid, c as themeFontDefRPr, f as relationshipsPart, g as commentPath, h as SLIDE_MASTER_PATH, ht as createColorElement, i as makeXmlNotesSlide, j as grpXfrmEl, jt as getNewRelId, kt as bracedGuid, l as externalHyperlinkRel, m as PRESENTATION_PATH, n as makeXmlNotesMaster, ot as genXmlColorSelection, p as NOTES_MASTER_PATH, q as buCharEl, s as lvlPPr, t as buildNotesSlideRels, u as relationshipEl, ut as STRETCH_FILL_RECT, v as notesSlidePath, vt as rejectEmptyColor, w as targetFromPptSubpart, wt as FONT_DATA_EXTENSION, x as slideLayoutPath, xt as raw, y as overrideName, yt as el } from "./notes-Dh12JAbn.js";
7
+ import { A as renderedSlideObjects, B as pushMediaRel, C as msMediaRid, D as findLayoutPlaceholder, E as placeholderOptionsForSlide, F as warnUnauthoredChild, I as SLIDE_METHOD_FAMILIES, L as composeFamilies, M as shapeIdCount, N as addChildDefinition, O as resolveTextAnchor, P as isGroupableObject, R as familyMethodUnavailable, S as alternateContentEl, T as addTextDefinition, V as resolveImageSource, _ as chartPartNames, a as mediaFamily, b as GROUP_SHAPE_LOCK_ATTRS, c as commentsFamily, d as textFamily, f as shapeFamily, g as makeXmlCore, h as groupFamily, i as model3dFamily, j as resolveObjectNameToId, k as collectSlideShapeIds, l as chartFamily, m as imageFamily, n as tableFamily, o as measureFamily, p as notesFamily, r as oleFamily, s as connectorFamily, t as zoomFamily, u as animationsFamily, v as FMT_SCHEME_XML, w as previewRid, x as genXmlObjectLock, y as resolveImageExtent, z as requirePresentationAuthor } from "./zoom-DPUHh85d.js";
8
+ import { n as setOrClear, t as pickDefined } from "./options-internal-WqgMf720.js";
9
+ import { b as isFontCollection, g as FontMetricsRegistry, t as applyMeasuredFit, y as parseFontMetrics } from "./fit-Cb7naFj6.js";
10
+ import { t as ZipWriter } from "./zip-CKd8ihcf.js";
11
+ import { t as transitionSpeedForDuration } from "./transition-speed-kUEWDWTx.js";
12
+ import { n as presentationFormatLabel, r as slideSizeType } from "./slide-size-BalTjFuv.js";
13
+ //#region src/gen/define/background.ts
14
+ /**
15
+ * ts-pptx: Background Definition
16
+ *
17
+ * Registers an `addBackground()` image as a slide media rel (color backgrounds carry no rel);
18
+ * the `<p:bg>` XML is emitted later at slide / layout serialize time.
19
+ */
20
+ /**
21
+ * Adds a background image or color to a slide definition.
22
+ *
23
+ * Only an image background does anything here: it is the one that needs a media relationship.
24
+ * A colour — object or the bare-string shorthand — carries no rel and is emitted straight from
25
+ * `slide.background` at serialize time.
26
+ *
27
+ * The rel id and the part name come from the shared allocators. This used to take the id as
28
+ * `_relsMedia.length + 1`, which a hyperlink or chart rel could already hold, and to name the part
29
+ * after the slide or layout title. Titles are caller-supplied and different ones sanitize alike,
30
+ * so "A B" and "A-B", or a layout titled "Slide 1" beside slide 1, wrote one part that both
31
+ * backgrounds pointed at. `nextMediaTarget` keys the name by slide, layout or master, never
32
+ * by title, so it is unique and needs no sanitizing.
33
+ *
34
+ * Called on every assignment, so it replaces what an earlier one registered. A new image takes
35
+ * over the earlier background's rel in place, keeping its id and its part name, rather than
36
+ * pushing a second one; replacing it by removal would shift the media count every later part name
37
+ * is read from. A colour clears the image rel id, which `slideBackgroundXml` checks before it looks
38
+ * at a colour, so an image replaced by a colour stops painting.
39
+ *
40
+ * The image's rel stays in `_relsMedia` for the same reason, so a package whose image background
41
+ * was replaced by a colour still carries the image part and an `image` relationship nothing
42
+ * references. That is valid OPC, only unused bytes. Dropping it would need either a tombstone the
43
+ * rel and part emitters skip, or media part names that do not depend on the count.
44
+ *
45
+ * An image whose `data` has no base64 header is dropped with a warning, as an image fill's is,
46
+ * and leaves any colour the background states to paint. It used to be written as a part holding
47
+ * the caller's text.
48
+ * @param {BackgroundOption} props - a bare colour, or an object with a colour or image definition
49
+ * @param {SlideLayoutInternal} target - slide or layout that the background is set on
50
+ */
51
+ function addBackgroundDefinition(props, target) {
52
+ if (!(props && typeof props === "object" && (props.path || props.data))) {
53
+ delete target._bkgdImgRid;
54
+ return;
55
+ }
56
+ const source = resolveImageSource(props);
57
+ if (typeof source === "string") {
58
+ warn("background/missing-base64-header", "background `data` value lacks a base64 header (ex: 'image/png;base64,...'); ignoring the background image.");
59
+ delete target._bkgdImgRid;
60
+ return;
61
+ }
62
+ let strImgExtn = source.extn;
63
+ if (strImgExtn === "jpg") strImgExtn = "jpeg";
64
+ const type = imageContentType(strImgExtn);
65
+ const previous = target._relsMedia.findIndex((rel) => rel.rId === target._bkgdImgRid);
66
+ const replaced = target._relsMedia[previous];
67
+ if (replaced) {
68
+ target._relsMedia[previous] = {
69
+ path: source.path || preencodedPath(strImgExtn),
70
+ type,
71
+ extn: strImgExtn,
72
+ data: source.data,
73
+ rId: replaced.rId,
74
+ Target: replaced.Target.replace(/\.[^./]+$/, `.${strImgExtn}`)
75
+ };
76
+ return;
77
+ }
78
+ const slide = target;
79
+ const rId = getNewRelId(slide);
80
+ pushMediaRel(slide, {
81
+ kind: "image",
82
+ extn: strImgExtn,
83
+ type,
84
+ path: source.path,
85
+ data: source.data,
86
+ rId
87
+ });
88
+ target._bkgdImgRid = rId;
89
+ }
90
+ //#endregion
91
+ //#region src/slide.ts
92
+ /**
93
+ * Project one authored render-object onto the public {@link SlideObjectInfo}, recursing into a
94
+ * group's children.
95
+ *
96
+ * The name is the stored one, which is the caller's own spelling: only `cNvPrOpen` escapes it. So
97
+ * a name read here resolves when it is handed back to `groupObjects()`.
98
+ *
99
+ * Every `add*Definition` also *generates* a name when the caller passed none, so the fallback below
100
+ * is unreachable through the public API. It is there because the field is typed non-optional
101
+ * internally only by convention, and an empty name would otherwise read back as `undefined` from a
102
+ * surface that promises a string.
103
+ */
104
+ function toSlideObjectInfo(obj) {
105
+ const stored = obj.options?.objectName;
106
+ return {
107
+ type: obj._type,
108
+ objectName: typeof stored === "string" && stored.length > 0 ? stored : "",
109
+ isPlaceholder: !!obj.options?.placeholder,
110
+ canGroup: isGroupableObject(obj),
111
+ children: (obj._groupObjects ?? []).map(toSlideObjectInfo)
112
+ };
113
+ }
114
+ var SlideBuilder = class {
115
+ _setSlideNum;
116
+ addSlide;
117
+ getSlide;
118
+ getSections;
119
+ _name;
120
+ _presLayout;
121
+ _rels;
122
+ _relsChart;
123
+ _relsMedia;
124
+ _rId;
125
+ _slideId;
126
+ _slideLayout;
127
+ _slideNum;
128
+ _slideNumberProps;
129
+ _slideObjects;
130
+ _comments = [];
131
+ _newAutoPagedSlides = [];
132
+ _animations = [];
133
+ /**
134
+ * The child-descriptor authors this presentation was composed with, for the group family: a
135
+ * group's children resolve through the same table a slide master's `objects` do, so a group can
136
+ * hold exactly the kinds this presentation can author.
137
+ */
138
+ _childAuthors;
139
+ constructor(params) {
140
+ this.addSlide = params.addSlide;
141
+ this.getSlide = params.getSlide;
142
+ this.getSections = params.getSections ?? (() => []);
143
+ this._name = `Slide ${params.slideNumber}`;
144
+ this._presLayout = params.presLayout;
145
+ this._rId = params.slideRId;
146
+ this._rels = [];
147
+ this._relsChart = [];
148
+ this._relsMedia = [];
149
+ this._setSlideNum = params.setSlideNum;
150
+ this._slideId = params.slideId;
151
+ this._slideLayout = params.slideLayout || null;
152
+ this._slideNum = params.slideNumber;
153
+ this._slideObjects = [];
154
+ this._childAuthors = params.childAuthors;
155
+ this.#bindFamilyMethods(params.authors);
156
+ /** NOTE: Slide Numbers: In order for Slide Numbers to function they need to be in all 3 files: master/layout/slide
157
+ * `defineSlideMaster` and `addNewSlide.slideNumber` will add {slideNumber} to `this.masterSlide` and `this.slideLayouts`
158
+ * so, lastly, add to the Slide now.
159
+ */
160
+ this._slideNumberProps = this._slideLayout?._slideNumberProps ? this._slideLayout._slideNumberProps : null;
161
+ }
162
+ /**
163
+ * Put this presentation's family methods on the slide.
164
+ *
165
+ * Per instance rather than on the prototype: the family set belongs to the presentation, not to
166
+ * the class, and two presentations composed differently in one process each get their own. A
167
+ * slide count is small enough that per-instance function properties are not a memory question.
168
+ *
169
+ * Chaining is applied here, once, rather than by each author: every `add*` answers the slide, so
170
+ * an author has nothing to say about the return value and cannot get it wrong.
171
+ */
172
+ #bindFamilyMethods(authors) {
173
+ const slide = this;
174
+ for (const method of Object.keys(SLIDE_METHOD_FAMILIES)) {
175
+ const author = authors[method];
176
+ slide[method] = author ? (...args) => {
177
+ author(this, ...args);
178
+ return this;
179
+ } : () => familyMethodUnavailable(`slide.${method}`, SLIDE_METHOD_FAMILIES[method]);
180
+ }
181
+ }
182
+ /**
183
+ * Background color or image
184
+ * @type {BackgroundProps}
185
+ * @example solid color `background: { color:'FF0000' }`
186
+ * @example color+trans `background: { color:'FF0000', transparency:0.5 }`
187
+ * @example base64 `background: { data:'image/png;base64,ABC[...]123' }`
188
+ * @example url `background: { path:'https://some.url/image.jpg'}`
189
+ */
190
+ _background;
191
+ set background(props) {
192
+ this._background = props;
193
+ addBackgroundDefinition(props, this);
194
+ }
195
+ get background() {
196
+ return this._background;
197
+ }
198
+ /**
199
+ * Default font color
200
+ * @type {HexColor}
201
+ */
202
+ _color;
203
+ set color(value) {
204
+ this._color = value;
205
+ }
206
+ get color() {
207
+ return this._color;
208
+ }
209
+ /**
210
+ * @type {boolean}
211
+ */
212
+ _hidden = false;
213
+ set hidden(value) {
214
+ this._hidden = value;
215
+ }
216
+ get hidden() {
217
+ return this._hidden;
218
+ }
219
+ /**
220
+ * Slide-show transition (`p:transition`) played when advancing to this slide.
221
+ * @type {TransitionProps}
222
+ */
223
+ _transition;
224
+ set transition(value) {
225
+ this._transition = value;
226
+ }
227
+ get transition() {
228
+ return this._transition;
229
+ }
230
+ /**
231
+ * @type {SlideNumberProps}
232
+ */
233
+ set slideNumber(value) {
234
+ this._slideNumberProps = value;
235
+ this._setSlideNum(value);
236
+ }
237
+ get slideNumber() {
238
+ return this._slideNumberProps ?? void 0;
239
+ }
240
+ /**
241
+ * Every continuation slide this slide's tables have spilled onto, in call order, each once.
242
+ *
243
+ * Across calls, not per call: a slide can carry more than one `addTable`, and each auto-paging
244
+ * one appends whatever it spilled onto. The list was assigned rather than appended, so a second
245
+ * table erased the first table's report while its slides stayed in the deck. "Spilled onto"
246
+ * rather than "created" because a later table lands on the earlier one's continuations instead
247
+ * of making its own, and those are reported once.
248
+ */
249
+ get newAutoPagedSlides() {
250
+ return this._newAutoPagedSlides;
251
+ }
252
+ /**
253
+ * The objects authored on this slide so far, bottom-to-top in z-order.
254
+ *
255
+ * Built fresh on every access rather than cached: the underlying list is mutated in place by
256
+ * `addGroup`/`groupObjects` as well as appended to, so a cache would have to be invalidated from
257
+ * every authoring path and would go stale the first time one was added without remembering.
258
+ * Building it costs a walk of a list a slide-sized deck keeps in the dozens.
259
+ */
260
+ get objects() {
261
+ return renderedSlideObjects(this._slideObjects).map(toSlideObjectInfo);
262
+ }
263
+ /** Slide width in inches (resolved from the active presentation layout). */
264
+ get width() {
265
+ return emuToInches(this._presLayout.width);
266
+ }
267
+ /** Slide height in inches (resolved from the active presentation layout). */
268
+ get height() {
269
+ return emuToInches(this._presLayout.height);
270
+ }
271
+ };
272
+ //#endregion
273
+ //#region src/font-source.ts
274
+ /**
275
+ * ts-pptx: getting font bytes out of whatever the caller handed over.
276
+ *
277
+ * `registerFontMetrics` and `embedFont` are two public methods on one class, forty lines apart,
278
+ * and each resolved its source itself. They agreed on `Uint8Array` and `ArrayBuffer` and
279
+ * disagreed about what a `string` means: `registerFontMetrics('X', someBase64)` handed the
280
+ * base64 to the runtime's file loader and failed to open it, while
281
+ * `embedFont({ data: someBase64 })` decoded it. Neither doc said which, because each was only
282
+ * describing its own half.
283
+ *
284
+ * The two really do want different string semantics -- `embedFont` names its path in `path` and
285
+ * its bytes in `data`, so a string in `data` cannot be a filename -- so the answer is one
286
+ * resolver with the difference stated as a flag rather than two resolvers with the difference
287
+ * left implicit.
288
+ */
289
+ /**
290
+ * Resolve a caller's font source to bytes.
291
+ *
292
+ * A `Uint8Array` is used as it stands, an `ArrayBuffer` is wrapped, and a `string` is read
293
+ * according to `base64`: **false or absent** means a path or URL for the runtime's loader (what
294
+ * `registerFontMetrics(face, source)` and `embedFont({ path })` take), **true** means base64
295
+ * text (what `embedFont({ data })` takes, with or without a `data:` prefix).
296
+ * @param source - the caller's value
297
+ * @param runtime - the entry point's adapter, for the path/URL case
298
+ * @param opts - `base64: true` to read a string as base64 rather than as a path
299
+ * @param opts.base64 - see above
300
+ * @param label - how to name the offending argument if the source is unusable
301
+ */
302
+ async function resolveFontBytes(source, runtime, opts, label) {
303
+ if (source instanceof Uint8Array) return source;
304
+ if (source instanceof ArrayBuffer) return new Uint8Array(source);
305
+ if (typeof source === "string") {
306
+ if (!opts?.base64) return await runtime.loadFontData(source);
307
+ const decoded = decodeBase64ToBytes(source);
308
+ if (!decoded) throw new InvalidOptionError("font/invalid-base64", `${label} is not valid base64`);
309
+ return decoded;
310
+ }
311
+ throw new InvalidOptionError("font/missing-source", `${label} must be a font path, raw bytes, or base64 data`);
312
+ }
313
+ //#endregion
314
+ //#region src/gen/define/master.ts
315
+ /**
316
+ * Transforms a slide definition to a slide object that is then passed to the XML transformation process.
317
+ * @param {SlideMasterProps} props - slide definition
318
+ * @param {PresSlideInternal|SlideLayoutInternal} target - empty slide object that should be updated by the passed definition
319
+ * @param {ChildAuthors} children - the child-descriptor authors this presentation was composed with
320
+ */
321
+ function createSlideMaster(props, target, children) {
322
+ if (props.objects && Array.isArray(props.objects) && props.objects.length > 0) props.objects.forEach((object, idx) => {
323
+ const tgt = target;
324
+ if (addChildDefinition(tgt, object, children)) {} else if ("placeholder" in object) {
325
+ const placeholder = object.placeholder;
326
+ const { name, type, ...rawPlaceholderOptions } = placeholder.options;
327
+ const placeholderOptions = rawPlaceholderOptions;
328
+ placeholderOptions.placeholder = name;
329
+ placeholderOptions._placeholderType = type;
330
+ placeholderOptions._placeholderIdx = 100 + idx;
331
+ const placeholderRun = placeholder.text === void 0 ? {} : { text: placeholder.text };
332
+ addTextDefinition(tgt, [placeholderRun], placeholderOptions, true);
333
+ } else warnUnauthoredChild(object, "defineSlideMaster()");
334
+ });
335
+ if (props.slideNumber && typeof props.slideNumber === "object") target._slideNumberProps = props.slideNumber;
336
+ }
337
+ //#endregion
338
+ //#region src/gen/define/placeholder.ts
339
+ /**
340
+ * Adds placeholder objects to slide
341
+ * @param {PresSlideInternal} slide - slide object containing layouts
342
+ */
343
+ function addPlaceholdersToSlideLayouts(slide) {
344
+ if (!slide._slideLayout) return;
345
+ (slide._slideLayout._slideObjects || []).forEach((slideLayoutObj) => {
346
+ if (slideLayoutObj._type === "placeholder") {
347
+ const slideLayoutOptions = slideLayoutObj.options || {};
348
+ if (!slide._slideObjects.some((slideObj) => slideObj.options && slideObj.options.placeholder === slideLayoutOptions.placeholder)) addTextDefinition(slide, [{ text: "" }], placeholderOptionsForSlide(slideLayoutOptions), true);
349
+ }
350
+ });
351
+ }
352
+ //#endregion
353
+ //#region src/media/placeholders.ts
354
+ /**
355
+ * The base64 placeholder images the library falls back to when a real asset is unavailable.
356
+ *
357
+ * Their own module because of where the bundler puts them, not because they group naturally.
358
+ * The chunker splits by module, so a constant is pulled into every chunk that reaches the module
359
+ * declaring it — and these lived in `constants-internal.ts` beside `XML_DECL`, `DEF_FONT_SIZE`
360
+ * and the rest of the vocabulary all of `src/gen/` imports. The rule that follows: a payload
361
+ * measured in kilobytes does not share a module with vocabulary, however tidy that grouping reads.
362
+ *
363
+ * The default video poster obeys the same rule one size up. At 14 kB it does not share a module
364
+ * with these two either: it lives alone in `src/media/playbtn.ts`, which is what lets the media
365
+ * pass reach it dynamically instead of linking it into every consumer's entry chunk.
366
+ */
367
+ /** Shown in place of media that failed to load, under `onMediaError: 'placeholder'`. */
368
+ const IMG_BROKEN = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAB3CAYAAAD1oOVhAAAGAUlEQVR4Xu2dT0xcRRzHf7tAYSsc0EBSIq2xEg8mtTGebVzEqOVIolz0siRE4gGTStqKwdpWsXoyGhMuyAVJOHBgqyvLNgonDkabeCBYW/8kTUr0wsJC+Wfm0bfuvn37Znbem9mR9303mJnf/Pb7ed95M7PDI5JIJPYJV5EC7e3t1N/fT62trdqViQCIu+bVgpIHEo/Hqbe3V/sdYVKHyWSSZmZm8ilVA0oeyNjYmEnaVC2Xvr6+qg5fAOJAz4DU1dURGzFSqZRVqtMpAFIGyMjICC0vL9PExIRWKADiAYTNshYWFrRCARAOEFZcCKWtrY0GBgaUTYkBRACIE4rKZwqACALR5RQAqQCIDqcASIVAVDsFQCSAqHQKgEgCUeUUAPEBRIVTAMQnEBvK5OQkbW9vk991CoAEAMQJxc86BUACAhKUUwAkQCBBOAVAAgbi1ykAogCIH6cAiCIgsk4BEIVAZJwCIIqBVLqiBxANQFgXS0tLND4+zl08AogmIG5OSSQS1gGKwgtANAIRcQqAaAbCe6YASBWA2E6xDyeyDUl7+AKQMkDYYevm5mZHabA/Li4uUiaTsYLau8QA4gLE/hU7wajyYtv1hReDAiAOxQcHBymbzark4BkbQKom/X8dp9Npmpqasn4BIAYAYSnYp+4BBEAMUcCwNOCQsAKZnp62NtQOw8WmwT09PUo+ijaHsOMx7GppaaH6+nolH0Z10K2tLVpdXbW6UfV3mNqBdHd3U1NTk2rtlMRfW1uj2dlZAFGirkRQAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAGHqrm8caPzQ0WC1logbeiC7X3xJm0PvUmRzh45cuki1588FAmVn9BO6P3yF9utrqGH0MtW82S8UN9RA9v/4k7InjhcJFTs/TLVXLwmJV67S7vD7tHF5pKi46fYdosdOcOOGG8j1OcqefbFEJD9Q3GCwDhqT31HklS4A8VRgfYM2Op6k3bt/BQJl58J7lPvwg5JYNccepaMry0LPqFA7hCm39+NNyp2J0172b19QysGINj5CsRtpij57musOViH0QPJQXn6J9u7dlYJSFkbrMYolrwvDAJAC+WWdEpQz7FTgECeUCpzi6YxvvqXoM6eEhqnCSgDikEzUKUE7Aw7xuHctKB5OYU3dZlNR9syQdAaAcAYTC0pXF+39c09o2Ik+3EqxVKqiB7hbYAxZkk4pbBaEM+AQofv+wTrFwylBOQNABIGwavdfe4O2pg5elO+86l99nY58/VUF0byrYsjiSFluNlXYrOHcBar7+EogUADEQ0YRGHbzoKAASBkg2+9cpM1rV0tK2QOcXW7bLEFAARAXIF4w2DrDWoeUWaf4hQIgDiA8GPZ2iNfi0Q8UACkAIgrDbrJ385eDxaPLLrEsFAB5oG6lMPJQPLZZZKAACBGVhcG2Q+bmuLu2nk55e4jqPv1IeEoceiBeX7s2zCa5MAqdstl91vfXwaEGsv/rb5TtOFk6tWXOuJGh6KmnhO9sayrMninPx103JBtXblHkice58cINZP4Hyr5wpkgkdiChEmc4FWazLzenNKa/p0jncwDiqcD6BuWePk07t1asatZGoYQzSqA4nFJ7soNiP/+EUyfc25GI2GG53dHPrKo1g/1Cw4pIXLrzO+1c+/wg7tBbFDle/EbQcjFCPWQJCau5EoBoFpzXHYDwFNJcDiCaBed1ByA8hTSXA4hmwXndAQhPIc3lAKJZcF53AMJTSHM5gGgWnNcdgPAU0lwOIJoF53UHIDyFNJcfSiCdnZ0Ui8U0SxlMd7lcjubn561gh+Y1scFIU/0o/3sgeLO12E2k7UXKYumgFoAYdg8ACIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6ZQ4JB6PKzviYthnNy4d9h+1M5mMlVckkUjsG5dhiBMCEMPg/wuOfrZZ/RSywQAAAABJRU5ErkJggg==";
369
+ /** 1x1 transparent PNG, standing in for an SVG the runtime could not rasterize. */
370
+ const IMG_SVG_PLACEHOLDER = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
371
+ //#endregion
372
+ //#region src/gen/media.ts
373
+ /**
374
+ * ts-pptx: Media Methods
375
+ */
376
+ function hasEncodingPath(rel) {
377
+ return typeof rel.path === "string" && rel.path.length > 0 && !isPreencodedPath(rel.path);
378
+ }
379
+ /**
380
+ * Write an SVG rel's PNG fallback, applying `onMediaError` to a preview that fails.
381
+ *
382
+ * The one place that policy is applied to a preview. The adapter has already stamped the
383
+ * placeholder and resolved with the failure, so only the PNG fallback is ever the placeholder:
384
+ * the SVG part keeps its bytes under either policy. A preview used to reject instead, and an SVG
385
+ * given inline then failed the whole write whatever `onMediaError` said, while one given by path
386
+ * was reported as `media/load-failed` and had its SVG bytes replaced with a broken image.
387
+ */
388
+ async function writeSvgPreview(rel, runtime, onMediaError) {
389
+ const failure = await runtime.createSvgPngPreview(rel);
390
+ if (failure === null) return "done";
391
+ if (onMediaError === "placeholder") {
392
+ warn("media/svg-preview-failed", `${failure.message}; the SVG is kept and its PNG fallback is a placeholder.`);
393
+ return "done";
394
+ }
395
+ throw failure;
396
+ }
397
+ /**
398
+ * Encode Image/Audio/Video into base64
399
+ * @param {PresSlideInternal | SlideLayoutInternal | SlideMasterInternal} layout - slide, layout or master
400
+ * @param {RuntimeAdapter} runtime - runtime adapter (Node/browser media loader)
401
+ * @param {'throw' | 'placeholder'} onMediaError - failure policy: reject the export (default) or substitute a placeholder and warn
402
+ * @return {Promise} promise
403
+ */
404
+ function encodeSlideMediaRels(layout, runtime, onMediaError = "throw") {
405
+ const imageProms = [];
406
+ const defaultCoverRels = layout._relsMedia.filter((rel) => rel.isDefaultCover && !rel.data);
407
+ if (defaultCoverRels.length > 0) imageProms.push((async () => {
408
+ const { IMG_PLAYBTN } = await import("./playbtn-CUqQdoDf.js");
409
+ for (const rel of defaultCoverRels) rel.data = IMG_PLAYBTN;
410
+ return "done";
411
+ })());
412
+ const candidateRels = layout._relsMedia.filter((rel) => rel.type !== "online" && !rel.data && hasEncodingPath(rel));
413
+ const unqPaths = [];
414
+ candidateRels.forEach((rel) => {
415
+ if (!unqPaths.includes(rel.path)) {
416
+ rel.isDuplicate = false;
417
+ unqPaths.push(rel.path);
418
+ } else rel.isDuplicate = true;
419
+ });
420
+ candidateRels.filter((rel) => !rel.isDuplicate).forEach((rel) => {
421
+ imageProms.push((async () => {
422
+ const dupes = candidateRels.filter((dupe) => dupe.isDuplicate && dupe.path === rel.path);
423
+ let data;
424
+ try {
425
+ data = toMediaDataUri(await runtime.loadMedia(rel), rel.type);
426
+ } catch (ex) {
427
+ if (Boolean(rel.oleRelType || rel.model3dRelType)) throw new MediaError("media/load-failed", `Failed to load the embedded payload "${rel.path}" during export; a placeholder picture cannot stand in for an OLE object or a 3D model.`, { cause: ex });
428
+ if (onMediaError === "placeholder") {
429
+ warn("media/load-failed", `Failed to load media "${rel.path}"; embedding a broken-image placeholder. (${String(ex)})`);
430
+ rel.data = IMG_BROKEN;
431
+ dupes.forEach((dupe) => dupe.data = IMG_BROKEN);
432
+ return "done";
433
+ }
434
+ throw new MediaError("media/load-failed", `Failed to load media "${rel.path}" during export.`, { cause: ex });
435
+ }
436
+ rel.data = data;
437
+ dupes.forEach((dupe) => dupe.data = data);
438
+ if (rel.isSvgPng) await writeSvgPreview(rel, runtime, onMediaError);
439
+ await Promise.all(dupes.filter((dupe) => dupe.isSvgPng).map((dupe) => writeSvgPreview(dupe, runtime, onMediaError)));
440
+ return "done";
441
+ })());
442
+ });
443
+ layout._relsMedia.filter((rel) => rel.isSvgPng && rel.data).forEach((rel) => {
444
+ imageProms.push(writeSvgPreview(rel, runtime, onMediaError));
445
+ });
446
+ return imageProms;
447
+ }
448
+ //#endregion
449
+ //#region src/gen/prepare.ts
450
+ /**
451
+ * ts-pptx: the pre-serialization pass
452
+ *
453
+ * Everything that must happen to authored slide state *before* any XML is built. Two callers
454
+ * reach serialization and both need it:
455
+ *
456
+ * - `package/assemble.ts` — the normal `write`/`stream`/`writeFile` path, building a
457
+ * complete `.pptx`.
458
+ * - `PresentationCore.extractSlides()` — serializes slide bodies only, for splicing into
459
+ * an already-loaded deck via `Presentation.appendSlides()` (`pptx-ts/read`).
460
+ *
461
+ * The second used to reimplement the first inline, kept in step by a comment that said
462
+ * "exactly as ... does". Nothing failed when they drifted: `extractSlides` would simply emit
463
+ * slides that did not match what a normal write produces, and no test compares the two.
464
+ *
465
+ * Three steps rather than one combined pass, because the package path has work of its own that
466
+ * must land between the last two: it de-duplicates identical media across the deck (which needs
467
+ * the encoded bytes {@link encodeMediaForTargets} produces) and assigns package-unique chart part
468
+ * filenames, both before any text is measured. Exposing the steps lets `assemble` keep that work
469
+ * in place while still sharing the code.
470
+ *
471
+ * The order is load-bearing:
472
+ *
473
+ * 1. **Backfill placeholders first**, so a slide carries its layout's placeholder objects before
474
+ * anything reads its media. A seeded placeholder can register media of its own, an image fill,
475
+ * and when backfill ran after encoding that image was never loaded and its rel named a part
476
+ * that was never written.
477
+ * 2. **Media.** It is the only asynchronous step, and the sync XML pass reads the `rel.data` it
478
+ * populates.
479
+ * 3. **Bake measured fit last**, because it measures the text step 1 may have just added and
480
+ * writes the `fontScale` the sync XML pass then reads. The caller undoes it once that pass is
481
+ * done, so the next write measures from the authored values.
482
+ *
483
+ * The functions take different target sets on purpose: media lives on layouts and the master as
484
+ * well as on slides, while placeholder backfill and measured fit apply only to slides.
485
+ * `extractSlides` passes only its slides to all three, because it emits no layout or master
486
+ * parts.
487
+ */
488
+ /**
489
+ * Before any step: refuse a link to a slide the deck does not have.
490
+ *
491
+ * A `hyperlink: { slide }` and a Slide Zoom's numeric `target` are checked for being slide numbers
492
+ * when they are authored, but whether the slide exists is only known once the deck is complete: a
493
+ * link to slide 3 can be authored before slide 3 is added. Both serializing callers check here,
494
+ * before anything is built. Otherwise the link is written as a relationship to a part the package
495
+ * never has.
496
+ *
497
+ * A layout and the master carry objects too, and an object given to `defineSlideLayout({ objects })`
498
+ * or `defineSlideMaster({ objects })` takes the same `hyperlink: { slide }`. This used to read the
499
+ * slides' relationships only, so a layout's number went unchecked and a link past the last slide
500
+ * reached the package from there. `extractSlides` emits no layout or master part and so passes
501
+ * neither, the same way it passes only slides to the three preparation steps below.
502
+ * @param slides - every slide in the deck, in order
503
+ * @param layouts - every slide layout, in order; omitted when no layout part is written
504
+ * @param master - the deck's slide master; omitted when no master part is written
505
+ */
506
+ function requireSlideLinksInDeck(slides, layouts = [], master) {
507
+ const sources = [
508
+ ...slides.map((slide) => ({
509
+ label: `Slide ${slide._slideNum}`,
510
+ part: slide
511
+ })),
512
+ ...layouts.map((layout, idx) => ({
513
+ label: `Slide layout ${idx + 1}`,
514
+ part: layout
515
+ })),
516
+ ...master ? [{
517
+ label: "The slide master",
518
+ part: master
519
+ }] : []
520
+ ];
521
+ for (const { label, part } of sources) for (const rel of part._rels) {
522
+ if (!isHyperlinkRel(rel) || rel.data !== "slide") continue;
523
+ if (!(Number(rel.Target) > slides.length)) continue;
524
+ throw new InvalidOptionError("slide/link-past-last-slide", `${label} links to slide ${String(rel.Target)}, but the deck has ${slides.length} slide${slides.length === 1 ? "" : "s"}.`);
525
+ }
526
+ }
527
+ /**
528
+ * Step 2: load and base64-encode every media rel on the given targets, populating `rel.data`.
529
+ * @param {ReadonlyArray<PresSlideInternal | SlideLayoutInternal | SlideMasterInternal>} targets - media-bearing targets (slides, layouts, master)
530
+ * @param {RuntimeAdapter} runtime - runtime adapter used to load media
531
+ * @param {'throw' | 'placeholder'} onMediaError - failure policy for unloadable media
532
+ */
533
+ async function encodeMediaForTargets(targets, runtime, onMediaError) {
534
+ const promises = [];
535
+ for (const target of targets) promises.push(...encodeSlideMediaRels(target, runtime, onMediaError));
536
+ await Promise.all(promises);
537
+ }
538
+ /**
539
+ * Step 1: seed each slide with the layout placeholders it leaves empty.
540
+ * @param {PresSlideInternal[]} slides - the slides to prepare
541
+ */
542
+ function backfillPlaceholders(slides) {
543
+ for (const slide of slides) if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
544
+ }
545
+ /**
546
+ * Step 3: bake measured text fit.
547
+ * @param {PresSlideInternal[]} slides - the slides to prepare
548
+ * @param {FontMetricsRegistry} fontMetrics - registered font metrics; an empty registry skips the fit bake
549
+ * @returns the step that restores the authored values; call it once the slide XML is built
550
+ */
551
+ function bakeMeasuredFit(slides, fontMetrics) {
552
+ return applyMeasuredFit(slides, fontMetrics);
553
+ }
554
+ //#endregion
555
+ //#region src/gen/anim/animation.ts
556
+ /**
557
+ * Resolved animation target's shape id (`spid`), or `null` when it cannot be resolved — in which
558
+ * case the effect is dropped, so every `null` warns rather than leaving the animation silently
559
+ * missing from the deck.
560
+ *
561
+ * `objectName` resolves through `shapeIds`, which covers group children: they are `<p:cNvPr>`-named
562
+ * on the slide and animate like any other shape, but are not in `_slideObjects`, so the old lookup
563
+ * there dropped every animation targeting one. The name goes in raw, and `resolveObjectNameToId`
564
+ * compares it with the raw name the slide object stores, so the warning below quotes the same
565
+ * spelling the caller passed.
566
+ *
567
+ * `shapeIndex` is a 0-based index into the top-level objects that RENDER, which is the same
568
+ * sequence `collectSlideShapeIds` allocates along; group children take ids past that range. It
569
+ * counted `_slideObjects` itself, and four of that array's members draw nothing — so `addNotes`
570
+ * before the first shape shifted every index by one and `shapeIndex: 0` addressed the notes.
571
+ * An index outside `[0, renderedCount)` would emit a `<p:spTgt spid>` naming no shape on the
572
+ * slide — a dangling spid PowerPoint reports as a repair (0x80070570) — so it warns and drops,
573
+ * exactly like an unresolvable `objectName` does.
574
+ * @param shapeIds - the slide's shape ids, from `collectSlideShapeIds`
575
+ * @param slideObjects - the slide's top-level objects, which a `shapeIndex` addresses
576
+ * @param anim - the animation to resolve
577
+ * @returns the target's `<p:cNvPr>` id, or `null`
578
+ */
579
+ function resolveAnimationSpid(shapeIds, slideObjects, anim) {
580
+ if (typeof anim.shapeIndex === "number") {
581
+ const targets = renderedSlideObjects(slideObjects);
582
+ const target = targets[anim.shapeIndex];
583
+ if (target) return shapeIds.get(target) ?? null;
584
+ warn("animation/target-index-out-of-range", `addAnimation: shapeIndex ${anim.shapeIndex} is out of range (slide has ${targets.length} top-level shape(s)), so its "${anim.preset}" effect was dropped.`);
585
+ return null;
586
+ }
587
+ if (anim.objectName) {
588
+ const id = resolveObjectNameToId(shapeIds, anim.objectName);
589
+ if (id !== null) return id;
590
+ warn("animation/target-not-found", `addAnimation: no object named "${anim.objectName}" on the slide, so its "${anim.preset}" effect was dropped.`);
591
+ return null;
592
+ }
593
+ warn("animation/target-missing", `addAnimation: the "${anim.preset}" effect names no target (pass shapeIndex or objectName), so it was dropped.`);
594
+ return null;
595
+ }
596
+ /**
597
+ * Whether the effect names a preset this module has a template for. The `preset` type admits only
598
+ * those, but a JavaScript caller can pass any string, so an unknown one warns and the effect is
599
+ * dropped before its target is resolved, like an effect whose target cannot be found.
600
+ * @param anim - the animation to check
601
+ * @returns `true` when the preset can be written
602
+ */
603
+ function hasKnownAnimationPreset(anim) {
604
+ if (Object.hasOwn(ANIM_PRESETS, anim.preset)) return true;
605
+ warn("animation/unknown-preset", `addAnimation: "${String(anim.preset)}" is not a preset this library writes (${Object.keys(ANIM_PRESETS).join(", ")}), so the effect was dropped.`);
606
+ return false;
607
+ }
608
+ const ANIM_SET_VISIBLE = (spid, next) => el("p:set", null, [raw(el("p:cBhvr", null, [
609
+ raw(el("p:cTn", {
610
+ id: next(),
611
+ dur: 1,
612
+ fill: "hold"
613
+ }, raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: 0 })))))),
614
+ raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid })))),
615
+ raw(el("p:attrNameLst", null, raw(el("p:attrName", null, "style.visibility"))))
616
+ ])), raw(el("p:to", null, raw(voidEl("p:strVal", { val: "visible" }))))]);
617
+ const ANIM_SET_HIDDEN = (spid, dur, next) => el("p:set", null, [raw(el("p:cBhvr", null, [
618
+ raw(el("p:cTn", {
619
+ id: next(),
620
+ dur: 1,
621
+ fill: "hold"
622
+ }, raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: Math.max(0, dur - 1) })))))),
623
+ raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid })))),
624
+ raw(el("p:attrNameLst", null, raw(el("p:attrName", null, "style.visibility"))))
625
+ ])), raw(el("p:to", null, raw(voidEl("p:strVal", { val: "hidden" }))))]);
626
+ /** `p:animEffect` filter transition (fade/wipe(down)/…), shared by entrance and exit presets. */
627
+ const ANIM_EFFECT = (transition, filter, spid, dur, next) => el("p:animEffect", {
628
+ transition,
629
+ filter
630
+ }, raw(el("p:cBhvr", null, [raw(voidEl("p:cTn", {
631
+ id: next(),
632
+ dur
633
+ })), raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid }))))])));
634
+ const ANIM_FADE = (transition, spid, dur, next) => ANIM_EFFECT(transition, "fade", spid, dur, next);
635
+ /**
636
+ * One `ppt_x`/`ppt_y` motion `<p:anim>` for Fly In (entrance) / Fly Out (exit).
637
+ * PowerPoint authors the two directions differently (captured in
638
+ * `slide-animation-presets.oracle.json`): the entrance form references the
639
+ * hashed run-time variables (`#ppt_x`) and holds (`fill="hold"`) starting
640
+ * off-screen and ending in place; the exit form uses the bare variables
641
+ * (`ppt_x`, no `#`), omits `fill="hold"`, and ends off-screen.
642
+ */
643
+ const ANIM_FLY_AXIS = (direction, axis, spid, dur, next) => {
644
+ const h = direction === "in" ? "#" : "";
645
+ let from;
646
+ let to;
647
+ if (axis === "x") {
648
+ from = `${h}ppt_x`;
649
+ to = `${h}ppt_x`;
650
+ } else if (direction === "in") {
651
+ from = `1+${h}ppt_h/2`;
652
+ to = `${h}ppt_y`;
653
+ } else {
654
+ from = `${h}ppt_y`;
655
+ to = `1+${h}ppt_h/2`;
656
+ }
657
+ const fill = direction === "in" ? "hold" : null;
658
+ return el("p:anim", {
659
+ calcmode: "lin",
660
+ valueType: "num"
661
+ }, [raw(el("p:cBhvr", { additive: "base" }, [
662
+ raw(voidEl("p:cTn", {
663
+ id: next(),
664
+ dur,
665
+ fill
666
+ })),
667
+ raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid })))),
668
+ raw(el("p:attrNameLst", null, raw(el("p:attrName", null, `ppt_${axis}`))))
669
+ ])), raw(el("p:tavLst", null, [raw(el("p:tav", { tm: 0 }, raw(el("p:val", null, raw(voidEl("p:strVal", { val: from })))))), raw(el("p:tav", { tm: 1e5 }, raw(el("p:val", null, raw(voidEl("p:strVal", { val: to }))))))]))]);
670
+ };
671
+ const ANIM_PRESETS = {
672
+ fadeIn: {
673
+ presetID: 10,
674
+ presetClass: "entr",
675
+ presetSubtype: 0,
676
+ defaultDurationMs: 500,
677
+ behaviors: (spid, dur, next) => ANIM_SET_VISIBLE(spid, next) + ANIM_FADE("in", spid, dur, next)
678
+ },
679
+ flyIn: {
680
+ presetID: 2,
681
+ presetClass: "entr",
682
+ presetSubtype: 4,
683
+ defaultDurationMs: 500,
684
+ behaviors: (spid, dur, next) => ANIM_SET_VISIBLE(spid, next) + ANIM_FLY_AXIS("in", "x", spid, dur, next) + ANIM_FLY_AXIS("in", "y", spid, dur, next)
685
+ },
686
+ appear: {
687
+ presetID: 1,
688
+ presetClass: "entr",
689
+ presetSubtype: 0,
690
+ defaultDurationMs: 500,
691
+ behaviors: (spid, _dur, next) => ANIM_SET_VISIBLE(spid, next)
692
+ },
693
+ wipe: {
694
+ presetID: 22,
695
+ presetClass: "entr",
696
+ presetSubtype: 4,
697
+ defaultDurationMs: 500,
698
+ behaviors: (spid, dur, next) => ANIM_SET_VISIBLE(spid, next) + ANIM_EFFECT("in", "wipe(down)", spid, dur, next)
699
+ },
700
+ grow: {
701
+ presetID: 6,
702
+ presetClass: "emph",
703
+ presetSubtype: 0,
704
+ defaultDurationMs: 2e3,
705
+ behaviors: (spid, dur, next) => el("p:animScale", null, [raw(el("p:cBhvr", null, [raw(voidEl("p:cTn", {
706
+ id: next(),
707
+ dur,
708
+ fill: "hold"
709
+ })), raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid }))))])), raw(voidEl("p:by", {
710
+ x: 15e4,
711
+ y: 15e4
712
+ }))])
713
+ },
714
+ spin: {
715
+ presetID: 8,
716
+ presetClass: "emph",
717
+ presetSubtype: 0,
718
+ defaultDurationMs: 2e3,
719
+ behaviors: (spid, dur, next) => el("p:animRot", { by: 216e5 }, [raw(el("p:cBhvr", null, [
720
+ raw(voidEl("p:cTn", {
721
+ id: next(),
722
+ dur,
723
+ fill: "hold"
724
+ })),
725
+ raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid })))),
726
+ raw(el("p:attrNameLst", null, raw(el("p:attrName", null, "r"))))
727
+ ]))])
728
+ },
729
+ fadeOut: {
730
+ presetID: 10,
731
+ presetClass: "exit",
732
+ presetSubtype: 0,
733
+ defaultDurationMs: 500,
734
+ behaviors: (spid, dur, next) => ANIM_FADE("out", spid, dur, next) + ANIM_SET_HIDDEN(spid, dur, next)
735
+ },
736
+ flyOut: {
737
+ presetID: 2,
738
+ presetClass: "exit",
739
+ presetSubtype: 4,
740
+ defaultDurationMs: 500,
741
+ behaviors: (spid, dur, next) => ANIM_FLY_AXIS("out", "x", spid, dur, next) + ANIM_FLY_AXIS("out", "y", spid, dur, next) + ANIM_SET_HIDDEN(spid, dur, next)
742
+ }
743
+ };
744
+ const ANIM_NODE_TYPE = {
745
+ onClick: "clickEffect",
746
+ withPrevious: "withEffect",
747
+ afterPrevious: "afterEffect"
748
+ };
749
+ /**
750
+ * Assemble the `mainSeq` from preset effects. Effects are grouped into click
751
+ * steps: an `onClick` effect opens a new click group; `afterPrevious` opens a new
752
+ * sub-step within it, starting when the sub-step before it ends; `withPrevious`
753
+ * joins the current sub-step. This reproduces PowerPoint's interactive build tree.
754
+ *
755
+ * A sub-step's delay counts from the click, not from the sub-step before it, and that sub-step
756
+ * ends with its longest effect: PowerPoint writes 0, 500, 1250 and 3250 ms for effects of 500,
757
+ * 750 and 1000 ms, a 2000 ms `withPrevious` beside the third, then a fourth.
758
+ */
759
+ function buildAnimationSeq(animations, next) {
760
+ const groups = [];
761
+ for (const entry of animations) {
762
+ const meta = ANIM_PRESETS[entry.anim.preset];
763
+ if (!meta) continue;
764
+ const trigger = entry.anim.trigger ?? "onClick";
765
+ const duration = typeof entry.anim.durationMs === "number" ? entry.anim.durationMs : meta.defaultDurationMs;
766
+ const subs = groups[groups.length - 1]?.subs;
767
+ const lastSub = subs?.[subs.length - 1];
768
+ if (trigger === "onClick" || !subs || !lastSub) groups.push({ subs: [{
769
+ delay: 0,
770
+ longest: duration,
771
+ effects: [entry]
772
+ }] });
773
+ else if (trigger === "afterPrevious") subs.push({
774
+ delay: lastSub.delay + lastSub.longest,
775
+ longest: duration,
776
+ effects: [entry]
777
+ });
778
+ else {
779
+ lastSub.effects.push(entry);
780
+ lastSub.longest = Math.max(lastSub.longest, duration);
781
+ }
782
+ }
783
+ const emitEffect = (entry) => {
784
+ const meta = ANIM_PRESETS[entry.anim.preset];
785
+ if (!meta) return "";
786
+ const nodeType = ANIM_NODE_TYPE[entry.anim.trigger ?? "onClick"];
787
+ const duration = typeof entry.anim.durationMs === "number" ? entry.anim.durationMs : meta.defaultDurationMs;
788
+ const effectId = next();
789
+ const behaviors = meta.behaviors(entry.spid, duration, next);
790
+ return el("p:par", null, raw(el("p:cTn", {
791
+ id: effectId,
792
+ presetID: meta.presetID,
793
+ presetClass: meta.presetClass,
794
+ presetSubtype: meta.presetSubtype,
795
+ fill: "hold",
796
+ grpId: 0,
797
+ nodeType
798
+ }, [raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: 0 })))), raw(el("p:childTnLst", null, raw(behaviors)))])));
799
+ };
800
+ const emitSub = (sub) => {
801
+ const subId = next();
802
+ const effects = sub.effects.map(emitEffect).join("");
803
+ return el("p:par", null, raw(el("p:cTn", {
804
+ id: subId,
805
+ fill: "hold"
806
+ }, [raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: sub.delay })))), raw(el("p:childTnLst", null, raw(effects)))])));
807
+ };
808
+ const emitGroup = (group) => {
809
+ const groupId = next();
810
+ const subs = group.subs.map(emitSub).join("");
811
+ return el("p:par", null, raw(el("p:cTn", {
812
+ id: groupId,
813
+ fill: "hold"
814
+ }, [raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: "indefinite" })))), raw(el("p:childTnLst", null, raw(subs)))])));
815
+ };
816
+ const clickGroups = groups.map(emitGroup).join("");
817
+ return el("p:seq", {
818
+ concurrent: 1,
819
+ nextAc: "seek"
820
+ }, [
821
+ raw(el("p:cTn", {
822
+ id: 2,
823
+ dur: "indefinite",
824
+ nodeType: "mainSeq"
825
+ }, raw(el("p:childTnLst", null, raw(clickGroups))))),
826
+ raw(el("p:prevCondLst", null, raw(el("p:cond", {
827
+ evt: "onPrev",
828
+ delay: 0
829
+ }, raw(el("p:tgtEl", null, raw(voidEl("p:sldTgt")))))))),
830
+ raw(el("p:nextCondLst", null, raw(el("p:cond", {
831
+ evt: "onNext",
832
+ delay: 0
833
+ }, raw(el("p:tgtEl", null, raw(voidEl("p:sldTgt"))))))))
834
+ ]);
835
+ }
836
+ /** One `<p:bldP>` per animated shape, in order of first appearance. */
837
+ function buildBldList(animations) {
838
+ const seen = /* @__PURE__ */ new Set();
839
+ const bldPs = [];
840
+ for (const { spid } of animations) {
841
+ if (seen.has(spid)) continue;
842
+ seen.add(spid);
843
+ bldPs.push(voidEl("p:bldP", {
844
+ spid,
845
+ grpId: 0
846
+ }));
847
+ }
848
+ return el("p:bldLst", null, raw(bldPs.join("")));
849
+ }
850
+ //#endregion
851
+ //#region src/gen/anim/timing.ts
852
+ /**
853
+ * Build the slide-level `<p:timing>` tree that makes embedded media loop.
854
+ * - PowerPoint stores playback looping as `repeatCount` on the media node's `<p:cTn>`
855
+ * (`indefinite` for "Loop until Stopped", or `N*1000` for a finite N plays), inside
856
+ * the slide timing tree rather than on the `<p:pic>` itself.
857
+ * - A slide has at most one `<p:timing>`; all looping media share its `tmRoot` node.
858
+ * - Audio loops via `<p:audio>`, video via `<p:video>` (both `CT_TLCommonMediaNodeData`).
859
+ * - The media node targets the picture by `spid` (its `<p:cNvPr>` id = slide-object index + 2).
860
+ * @param {PresSlideInternal} slide - the slide to inspect for looping media
861
+ * @returns {string} the `<p:timing>` XML, or `''` when no media loops
862
+ */
863
+ function slideTimingToXml(slide) {
864
+ const loopMedia = slide._slideObjects.filter((obj) => obj._type === "media" && obj.mtype !== "online" && typeof obj.mediaRid === "number" && (obj.loop === true || typeof obj.loopCount === "number" && obj.loopCount > 0));
865
+ const shapeIds = collectSlideShapeIds(slide._slideObjects);
866
+ const animations = (slide._animations ?? []).filter((anim) => hasKnownAnimationPreset(anim)).map((anim) => ({
867
+ anim,
868
+ spid: resolveAnimationSpid(shapeIds, slide._slideObjects, anim)
869
+ })).filter((entry) => entry.spid !== null);
870
+ const mediaNode = (obj, nodeId) => {
871
+ const spid = shapeIds.get(obj) ?? 2;
872
+ const repeatCount = obj.loop === true ? "indefinite" : String(Math.round(obj.loopCount * 1e3));
873
+ const mediaTag = obj.mtype === "audio" ? "p:audio" : "p:video";
874
+ return el(mediaTag, null, raw(el("p:cMediaNode", null, [raw(el("p:cTn", {
875
+ id: nodeId,
876
+ repeatCount,
877
+ fill: "hold",
878
+ display: 0
879
+ }, raw(el("p:stCondLst", null, raw(voidEl("p:cond", { delay: "indefinite" })))))), raw(el("p:tgtEl", null, raw(voidEl("p:spTgt", { spid }))))])));
880
+ };
881
+ if (animations.length === 0) {
882
+ if (loopMedia.length === 0) return "";
883
+ let nodeId = 1;
884
+ const mediaNodes = loopMedia.map((obj) => mediaNode(obj, nodeId += 1)).join("");
885
+ return el("p:timing", null, raw(el("p:tnLst", null, raw(el("p:par", null, raw(el("p:cTn", {
886
+ id: 1,
887
+ dur: "indefinite",
888
+ restart: "never",
889
+ nodeType: "tmRoot"
890
+ }, raw(el("p:childTnLst", null, raw(mediaNodes))))))))));
891
+ }
892
+ let id = 2;
893
+ const next = () => id += 1;
894
+ const seq = buildAnimationSeq(animations, next);
895
+ const mediaNodes = loopMedia.map((obj) => mediaNode(obj, next())).join("");
896
+ const bldLst = buildBldList(animations);
897
+ return el("p:timing", null, [raw(el("p:tnLst", null, raw(el("p:par", null, raw(el("p:cTn", {
898
+ id: 1,
899
+ dur: "indefinite",
900
+ restart: "never",
901
+ nodeType: "tmRoot"
902
+ }, raw(el("p:childTnLst", null, raw(seq + mediaNodes))))))))), raw(bldLst)]);
903
+ }
904
+ //#endregion
905
+ //#region src/gen/anim/transition.ts
906
+ /**
907
+ * The attributes of a transition's type element, from the caller's `variant`.
908
+ *
909
+ * Each `variant` key is written as an attribute name, so a key the element does not declare would
910
+ * be an attribute the schema does not allow there, and a key that is not a name at all would be
911
+ * markup. Only the attributes the element declares are written, and the rest warn.
912
+ */
913
+ function transitionVariantAttrs(type, variant) {
914
+ if (!variant) return null;
915
+ const allowed = TRANSITION_VARIANT_ATTRIBUTES[type];
916
+ const attrs = {};
917
+ for (const [name, value] of Object.entries(variant)) {
918
+ if (allowed.includes(name)) {
919
+ attrs[name] = value;
920
+ continue;
921
+ }
922
+ warnOnce("transition/unknown-variant", `slide transition \`${type}\` variant \`${name}\` is not an attribute of <p:${type}> and is ignored — ${allowed.length > 0 ? `use ${allowed.join(", ")}` : "it takes none"}.`, {
923
+ received: name,
924
+ valid: allowed
925
+ });
926
+ }
927
+ return attrs;
928
+ }
929
+ /**
930
+ * Build the `p:sndAc` sound-action child of `p:transition` (positioned after the
931
+ * transition-type element). A start sound is `<p:stSnd [loop="1"]><p:snd r:embed
932
+ * name/></p:stSnd>` referencing the embedded audio by `soundRId`; the stop-previous form is
933
+ * `<p:endSnd/>` (no rel). Returns `''` when the transition has no sound, or a start sound was
934
+ * registered under no relationship on this slide.
935
+ */
936
+ function transitionSoundToXml(transition, soundRId) {
937
+ const sound = transition.sound;
938
+ if (!sound) return "";
939
+ if (sound.stopPrevious) return el("p:sndAc", null, raw(voidEl("p:endSnd")));
940
+ if (soundRId === void 0) return "";
941
+ return el("p:sndAc", null, raw(el("p:stSnd", { loop: xsdBoolIfTrue(sound.loop) }, raw(voidEl("p:snd", {
942
+ "r:embed": `rId${soundRId}`,
943
+ name: sound.name || null
944
+ })))));
945
+ }
946
+ /**
947
+ * Build the slide-show transition tree (`p:transition`), positioned in `CT_Slide`
948
+ * between `p:clrMapOvr` and `p:timing`. Emits PowerPoint's `mc:AlternateContent`
949
+ * form (a `p14` Choice carrying the exact `p14:dur`, plus a base `mc:Fallback`)
950
+ * when `durationMs` is set, and the bare `<p:transition>` otherwise. See
951
+ * `docs/contributing/design/animations.md`.
952
+ * @param slide - the slide whose transition is emitted
953
+ * @param soundRId - the relationship id this slide's start sound was registered under, if one was.
954
+ * It is the slide's, not the transition's: one transition object can sit on several slides, each
955
+ * declaring the sound under an id of its own.
956
+ * @returns {string} the transition XML, or `''` when the slide has no transition the writer can emit
957
+ */
958
+ function slideTransitionToXml(slide, soundRId) {
959
+ const transition = slide.transition;
960
+ if (!transition?.type) return "";
961
+ const type = checkEnumOrWarn(transition.type, TRANSITION_TYPES, "transition/unknown-type", "slide transition `type`");
962
+ if (!type) return "";
963
+ const typeEl = voidEl(`p:${type}`, transitionVariantAttrs(type, transition.variant));
964
+ const sndAc = transitionSoundToXml(transition, soundRId);
965
+ const hasDuration = typeof transition.durationMs === "number" && Number.isFinite(transition.durationMs);
966
+ const baseAttrs = {
967
+ spd: (transition.speed ?? (hasDuration ? transitionSpeedForDuration(transition.durationMs) : null)) || null,
968
+ advClick: transition.advanceOnClick === false ? "0" : null,
969
+ advTm: typeof transition.advanceAfterMs === "number" ? Math.round(transition.advanceAfterMs) : null
970
+ };
971
+ if (!hasDuration) return el("p:transition", baseAttrs, [raw(typeEl), raw(sndAc)]);
972
+ const dur = Math.round(transition.durationMs);
973
+ return alternateContentEl({
974
+ requires: {
975
+ prefix: "p14",
976
+ uri: OOXML_NS.p14
977
+ },
978
+ choice: el("p:transition", {
979
+ ...baseAttrs,
980
+ "p14:dur": dur
981
+ }, [raw(typeEl), raw(sndAc)]),
982
+ fallback: el("p:transition", baseAttrs, [raw(typeEl), raw(sndAc)])
983
+ });
984
+ }
985
+ //#endregion
986
+ //#region src/gen/slide/object.ts
987
+ /** The four axes that make up an explicit group frame. All or nothing — see `givenGroupFrameAxes`. */
988
+ const GROUP_FRAME_AXES = [
989
+ "x",
990
+ "y",
991
+ "w",
992
+ "h"
993
+ ];
994
+ /**
995
+ * Which of `x`/`y`/`w`/`h` a group's caller actually supplied.
996
+ *
997
+ * A group frame is explicit only when **all four** are given; anything less falls back to
998
+ * auto-bounds on every axis. Partial frames used to be honoured per-axis, which let the unset ones
999
+ * take the shared per-object defaults (`x=0`, `y=0`, `cx=75%` of the layout width, `cy=0`) — so
1000
+ * `addGroup([rect], { x: 5, y: 2 })` emitted a zero-height group whose width was a silent
1001
+ * slide-width fraction, and every child of it then re-read as `null` through the read path's
1002
+ * degenerate-`chExt` guard.
1003
+ *
1004
+ * Per-axis fallback was the other candidate, but it cannot mean what it reads like: the writer keeps
1005
+ * an identity child space (`chOff/chExt == off/ext`), so a group's frame never moves or scales its
1006
+ * children — it only places the selection handle and the rotate pivot. `{ x: 5 }` would leave the
1007
+ * children where they were and put the group's box somewhere they are not. Falling back whole, with
1008
+ * a warning, keeps the group box around its content and says so out loud.
1009
+ * @param options - the group object's options
1010
+ * @returns the supplied axis names, in `x`/`y`/`w`/`h` order
1011
+ */
1012
+ const givenGroupFrameAxes = (options) => GROUP_FRAME_AXES.filter((axis) => typeof options[axis] !== "undefined");
1013
+ /**
1014
+ * Whether a group's frame is fully explicit (all four of `x`/`y`/`w`/`h`), and so should be used
1015
+ * verbatim instead of the children's bounding box. See `givenGroupFrameAxes`.
1016
+ * @param options - the group object's options
1017
+ * @returns true when every axis is supplied
1018
+ */
1019
+ const hasCompleteGroupFrame = (options) => givenGroupFrameAxes(options).length === GROUP_FRAME_AXES.length;
1020
+ /**
1021
+ * Normalize one axis of a placement box so its extent is never negative.
1022
+ *
1023
+ * `<a:ext cx>`/`<a:ext cy>` are `ST_PositiveCoordinate` (ECMA-376 Part 1, `CT_PositiveSize2D`), so a
1024
+ * negative extent is schema-invalid. PowerPoint rejects the *whole* presentation with "The file or
1025
+ * directory is corrupted and unreadable" (0x80070570) and names no shape, part, or slide, while
1026
+ * LibreOffice renders the same package happily — so the defect is invisible until the deck reaches
1027
+ * PowerPoint.
1028
+ *
1029
+ * A signed delta is the natural way to write "draw from A to B" (`{ x: x0, y: y0, w: x1 - x0, h: y1 - y0 }`),
1030
+ * and it goes negative the moment the line runs leftward or upward. The correct encoding is the
1031
+ * normalized bounding box plus a flip, which is what `addConnector` already derives from its own
1032
+ * endpoints (`src/gen/define/connector.ts`); this gives every other shape kind the same treatment at
1033
+ * the one point where every `Coord` form ('50%', '2in', a number) has been resolved to EMU.
1034
+ *
1035
+ * The caller composes `flip` onto any `flipH`/`flipV` the author set, so an explicit flip plus a
1036
+ * negative extent cancel out rather than double-applying.
1037
+ * @param off - the axis origin in EMU (`a:off` `x` or `y`)
1038
+ * @param ext - the axis extent in EMU (`a:ext` `cx` or `cy`), possibly negative
1039
+ * @returns the min-corner origin, the absolute extent, and whether the axis was mirrored
1040
+ */
1041
+ const normalizeAxisExtent = (off, ext) => ext < 0 ? {
1042
+ off: off + ext,
1043
+ ext: -ext,
1044
+ flip: true
1045
+ } : {
1046
+ off,
1047
+ ext,
1048
+ flip: false
1049
+ };
1050
+ /**
1051
+ * Resolve the frame a slide object is drawn in.
1052
+ *
1053
+ * The render pass and a group's bounding box both need it, and they used to resolve it apart. The
1054
+ * bounds pass defaulted a missing `w` or `h` to 0 and skipped placeholder inheritance, while the
1055
+ * render pass defaulted `cx` to 75% of the slide width and inherited the placeholder's frame, so a
1056
+ * group holding a text box with no `w` came out narrower than the text box.
1057
+ *
1058
+ * Each axis, most specific source first: what the caller stated on this object, else what the
1059
+ * layout placeholder it names states, else the default (`x`, `y` and `cy` 0, `cx` 75% of the slide
1060
+ * width). An explicit option beats an inherited one everywhere else in this library, and a
1061
+ * placeholder is an inherited one: applying it after the object's own values used to throw away a
1062
+ * stated frame with no diagnostic.
1063
+ *
1064
+ * Inheritance happens before normalization. A negative `w`/`h` becomes a min-corner origin, an
1065
+ * absolute extent and a flip, never a negative `<a:ext>`, which is out of range for
1066
+ * `ST_PositiveCoordinate` and costs the whole package (see `normalizeAxisExtent`). Doing it after
1067
+ * every `Coord` form has resolved to EMU means `'-2in'` and `'-25%'` normalize like a plain
1068
+ * negative number, and a placeholder's extents normalize with the object's own.
1069
+ * @param obj - the slide object
1070
+ * @param slide - the slide or layout holding it
1071
+ * @returns the object's frame
1072
+ */
1073
+ function resolveObjectFrame(obj, slide) {
1074
+ const itemOpts = obj.options ?? {};
1075
+ const layout = slide._presLayout;
1076
+ const placeholder = findLayoutPlaceholder(slide._slideLayout, itemOpts.placeholder);
1077
+ const phOpts = placeholder?.options ?? {};
1078
+ const inherited = (own, ph) => own !== void 0 ? own : ph ?? void 0;
1079
+ const ownX = inherited(itemOpts.x, phOpts.x);
1080
+ const ownY = inherited(itemOpts.y, phOpts.y);
1081
+ const ownW = inherited(itemOpts.w, phOpts.w);
1082
+ const ownH = inherited(itemOpts.h, phOpts.h);
1083
+ const normX = normalizeAxisExtent(ownX !== void 0 ? getSmartParseNumber(ownX, "X", layout) : 0, ownW !== void 0 ? getSmartParseNumber(ownW, "X", layout) : getSmartParseNumber("75%", "X", layout));
1084
+ const normY = normalizeAxisExtent(ownY !== void 0 ? getSmartParseNumber(ownY, "Y", layout) : 0, ownH !== void 0 ? getSmartParseNumber(ownH, "Y", layout) : 0);
1085
+ return {
1086
+ x: normX.off,
1087
+ y: normY.off,
1088
+ cx: normX.ext,
1089
+ cy: normY.ext,
1090
+ flipX: normX.flip,
1091
+ flipY: normY.flip,
1092
+ placeholder
1093
+ };
1094
+ }
1095
+ /**
1096
+ * Whether this part has a real slide number to cache in a `slidenum` field.
1097
+ *
1098
+ * Only a slide does. A master leaves `_slideNum` null and a layout carries the internal 1000+
1099
+ * counter that keeps layout media keys from colliding with slide media — neither is a page
1100
+ * number, and neither belongs in the field's cached text.
1101
+ */
1102
+ function hasRealSlideNumber(slide) {
1103
+ return slide._slideNum != null && slide._slideNum < 1e3;
1104
+ }
1105
+ /**
1106
+ * Warn about duplicate Selection Pane identities on this slide.
1107
+ *
1108
+ * Unique `objectName` values are what consumers (e.g. semantic manifests) rely on, so
1109
+ * collisions are flagged loudly. Deliberately NOT `warnOnce`: this runs per emit, and two
1110
+ * `write()` calls are two separate exports — a caller capturing diagnostics around the second
1111
+ * would otherwise be told its deck is clean because the first one had already said otherwise. Groups are recursed into: a group's children are
1112
+ * `<p:cNvPr>`-named on this same slide, so a child colliding with a top-level object (or with a
1113
+ * child of another group) is a collision the Selection Pane shows, and checking only the top
1114
+ * level cannot see it.
1115
+ */
1116
+ function warnDuplicateObjectNames(slide) {
1117
+ const collectObjectNames = (objects) => objects.flatMap((obj) => [...typeof obj.options?.objectName === "string" ? [obj.options.objectName] : [], ...collectObjectNames(obj._groupObjects || [])]);
1118
+ const duplicateObjectNames = getDuplicateObjectNames(collectObjectNames(slide._slideObjects));
1119
+ if (duplicateObjectNames.length > 0) warn("object-name/duplicate", `duplicate objectName value(s) emitted on a single slide: ${duplicateObjectNames.join(", ")}. Selection Pane identities should be unique.`);
1120
+ }
1121
+ /**
1122
+ * The slide's `<p:bg>`, or `''` when it has none.
1123
+ *
1124
+ * Exactly one of the three forms is emitted: an image background (a rel the media pass resolved),
1125
+ * a colour or gradient, or the white default that only the built-in layout gets. They are
1126
+ * mutually exclusive because a master carrying both a `color` and a `path` must still produce a
1127
+ * single `<p:bg>`.
1128
+ */
1129
+ function slideBackgroundXml(slide) {
1130
+ let strSlideXml = "";
1131
+ if (slide._bkgdImgRid) strSlideXml += el("p:bg", null, raw(el("p:bgPr", null, [raw(el("a:blipFill", {
1132
+ dpi: "0",
1133
+ rotWithShape: "1"
1134
+ }, [
1135
+ raw(el("a:blip", { "r:embed": `rId${slide._bkgdImgRid}` }, raw(voidEl("a:lum")))),
1136
+ raw(voidEl("a:srcRect")),
1137
+ raw(STRETCH_FILL_RECT)
1138
+ ])), raw(voidEl("a:effectLst"))])));
1139
+ else if (fillNamesPaint(slide.background)) strSlideXml += el("p:bg", null, raw(el("p:bgPr", null, [raw(genXmlColorSelection(slide.background)), raw(voidEl("a:effectLst"))])));
1140
+ else if (!slide.background && slide._name && slide._name === "DEFAULT") strSlideXml += el("p:bg", null, raw(el("p:bgRef", { idx: "1001" }, raw(voidEl("a:schemeClr", { val: "bg1" })))));
1141
+ return strSlideXml;
1142
+ }
1143
+ /**
1144
+ * The slide-number placeholder shape.
1145
+ *
1146
+ * Emitted after every authored object, so its `<p:cNvPr>` id can come from the same monotonic
1147
+ * counter the walk has been advancing — the caller allocates it and passes it in.
1148
+ *
1149
+ * @param slide - the slide, layout or master being emitted
1150
+ * @param snProps - the part's slide-number properties, read but never written: a serializer
1151
+ * does not normalize the authored model (see the contract on `RenderContext.itemOpts`)
1152
+ * @param slideNumberId - the `<p:cNvPr>` id to use, already allocated by the caller
1153
+ */
1154
+ function slideNumberPlaceholderXml(slide, snProps, slideNumberId) {
1155
+ let strSlideXml = "";
1156
+ strSlideXml += "<p:sp>";
1157
+ strSlideXml += " <p:nvSpPr>";
1158
+ strSlideXml += voidEl("p:cNvPr", {
1159
+ id: slideNumberId,
1160
+ name: "Slide Number Placeholder 0"
1161
+ }, { openPrefix: " " }) + el("p:cNvSpPr", null, raw(voidEl("a:spLocks", { noGrp: "1" })));
1162
+ strSlideXml += el("p:nvPr", null, raw(voidEl("p:ph", {
1163
+ type: "sldNum",
1164
+ sz: "quarter",
1165
+ idx: "4294967295"
1166
+ })), { openPrefix: " " });
1167
+ strSlideXml += " </p:nvSpPr>";
1168
+ strSlideXml += " <p:spPr>";
1169
+ strSlideXml += el("a:xfrm", null, [raw(voidEl("a:off", {
1170
+ x: getSmartParseNumber(snProps.x, "X", slide._presLayout),
1171
+ y: getSmartParseNumber(snProps.y, "Y", slide._presLayout)
1172
+ })), raw(voidEl("a:ext", {
1173
+ cx: snProps.w ? getSmartParseNumber(snProps.w, "X", slide._presLayout) : "800000",
1174
+ cy: snProps.h ? getSmartParseNumber(snProps.h, "Y", slide._presLayout) : "300000"
1175
+ }))]) + prstGeomRect({ openPrefix: " " }) + el("a:extLst", null, raw(el("a:ext", { uri: "{C572A759-6A51-4108-AA02-DFA0A04FC94B}" }, raw(voidEl("ma14:wrappingTextBoxFlag", {
1176
+ val: "0",
1177
+ "xmlns:ma14": "http://schemas.microsoft.com/office/mac/drawingml/2011/main"
1178
+ })))), { openPrefix: " " }) + "</p:spPr>";
1179
+ strSlideXml += "<p:txBody>";
1180
+ const snInsets = resolveInsetsEmu(snProps.margin);
1181
+ strSlideXml += voidEl("a:bodyPr", {
1182
+ lIns: snInsets?.l ?? null,
1183
+ tIns: snInsets?.t ?? null,
1184
+ rIns: snInsets?.r ?? null,
1185
+ bIns: snInsets?.b ?? null,
1186
+ anchor: resolveTextAnchor(snProps.valign)
1187
+ });
1188
+ let defRPr = "";
1189
+ rejectEmptyColor(snProps.color, "slideNumber color");
1190
+ if (snProps.fontFace || snProps.fontSize || snProps.color) {
1191
+ const face = snProps.fontFace;
1192
+ defRPr = el("a:defRPr", { sz: clampFontSizeSz(snProps.fontSize || 12) }, [
1193
+ snProps.color ? raw(genXmlColorSelection(snProps.color)) : null,
1194
+ face ? raw(voidEl("a:latin", { typeface: face })) : null,
1195
+ face ? raw(voidEl("a:ea", { typeface: face })) : null,
1196
+ face ? raw(voidEl("a:cs", { typeface: face })) : null
1197
+ ]);
1198
+ }
1199
+ strSlideXml += el("a:lstStyle", null, raw(el("a:lvl1pPr", null, raw(defRPr))), { openPrefix: " " });
1200
+ const snAlignRaw = snProps.align ?? "left";
1201
+ const snAlign = snAlignRaw.startsWith("c") ? "ctr" : snAlignRaw.startsWith("r") ? "r" : "l";
1202
+ strSlideXml += el("a:p", null, [
1203
+ raw(voidEl("a:pPr", { algn: snAlign })),
1204
+ raw(el("a:fld", {
1205
+ id: SLDNUMFLDID,
1206
+ type: "slidenum"
1207
+ }, [raw(voidEl("a:rPr", {
1208
+ b: xsdBool(snProps.bold),
1209
+ lang: "en-US"
1210
+ })), raw(el("a:t", null, hasRealSlideNumber(slide) ? String(slide._slideNum) : SLDNUM_PLACEHOLDER_TEXT))])),
1211
+ raw(voidEl("a:endParaRPr", { lang: "en-US" }))
1212
+ ]);
1213
+ strSlideXml += "</p:txBody></p:sp>";
1214
+ return strSlideXml;
1215
+ }
1216
+ /**
1217
+ * Transforms a slide or slideLayout to resulting XML string - Creates `ppt/slide*.xml`
1218
+ * @param slide - slide object created within createSlideObject
1219
+ * @param renderers - which renderer emits each shape family; see {@link RendererTable} for why
1220
+ * this arrives as an argument rather than being imported here
1221
+ * @return XML string with `<p:cSld>` as the root
1222
+ */
1223
+ function slideObjectToXml(slide, renderers) {
1224
+ let strSlideXml = slide._name ? "<p:cSld name=\"" + encodeXmlAttrValue(slide._name) + "\">" : "<p:cSld>";
1225
+ warnDuplicateObjectNames(slide);
1226
+ strSlideXml += slideBackgroundXml(slide);
1227
+ const spTreeChildren = [];
1228
+ const shapeIds = collectSlideShapeIds(slide._slideObjects);
1229
+ const resolveObjBounds = (obj) => {
1230
+ const o = obj.options || {};
1231
+ if (obj._type === "group" && !hasCompleteGroupFrame(o)) {
1232
+ const kids = (obj._groupObjects || []).map(resolveObjBounds);
1233
+ if (kids.length === 0) return {
1234
+ x: 0,
1235
+ y: 0,
1236
+ cx: 0,
1237
+ cy: 0
1238
+ };
1239
+ const minX = Math.min(...kids.map((b) => b.x));
1240
+ const minY = Math.min(...kids.map((b) => b.y));
1241
+ const maxX = Math.max(...kids.map((b) => b.x + b.cx));
1242
+ const maxY = Math.max(...kids.map((b) => b.y + b.cy));
1243
+ return {
1244
+ x: minX,
1245
+ y: minY,
1246
+ cx: maxX - minX,
1247
+ cy: maxY - minY
1248
+ };
1249
+ }
1250
+ const frame = resolveObjectFrame(obj, slide);
1251
+ if (obj._type === "image") {
1252
+ const { drawnW, drawnH } = resolveImageExtent(obj, slide, frame);
1253
+ return {
1254
+ x: frame.x,
1255
+ y: frame.y,
1256
+ cx: drawnW,
1257
+ cy: drawnH
1258
+ };
1259
+ }
1260
+ return {
1261
+ x: frame.x,
1262
+ y: frame.y,
1263
+ cx: frame.cx,
1264
+ cy: frame.cy
1265
+ };
1266
+ };
1267
+ const renderSlideObjectXml = (slideItemObj) => {
1268
+ const shapeId = shapeIds.get(slideItemObj) ?? 2;
1269
+ let strSlideXml = "";
1270
+ const itemOpts = slideItemObj.options ?? {};
1271
+ const { x, y, cx, cy, flipX, flipY, placeholder: placeholderObj } = resolveObjectFrame(slideItemObj, slide);
1272
+ const locationAttrs = {
1273
+ flipH: xsdBoolIfTrue(Boolean(itemOpts.flipH) !== flipX),
1274
+ flipV: xsdBoolIfTrue(Boolean(itemOpts.flipV) !== flipY),
1275
+ rot: mapStated(itemOpts.rotate, convertRotationDegrees) ?? null
1276
+ };
1277
+ const ctx = {
1278
+ obj: slideItemObj,
1279
+ shapeId,
1280
+ shapeIds,
1281
+ slide,
1282
+ frame: {
1283
+ x,
1284
+ y,
1285
+ cx,
1286
+ cy
1287
+ },
1288
+ placeholder: placeholderObj,
1289
+ locationAttrs,
1290
+ itemOpts
1291
+ };
1292
+ switch (slideItemObj._type) {
1293
+ case "chart":
1294
+ case "connector":
1295
+ case "image":
1296
+ case "media":
1297
+ case "model3d":
1298
+ case "oleObject":
1299
+ case "placeholder":
1300
+ case "table":
1301
+ case "text":
1302
+ case "zoom": {
1303
+ const render = renderers[slideItemObj._type];
1304
+ if (!render) throw new InternalError("slide/object-type-not-routed", `slideObjectToXml: no renderer registered for slide object type "${slideItemObj._type}"`);
1305
+ strSlideXml += render(ctx);
1306
+ break;
1307
+ }
1308
+ case "group": {
1309
+ const groupChildren = slideItemObj._groupObjects || [];
1310
+ let innerXml = "";
1311
+ groupChildren.forEach((child) => {
1312
+ innerXml += renderSlideObjectXml(child);
1313
+ });
1314
+ const givenAxes = givenGroupFrameAxes(itemOpts);
1315
+ if (givenAxes.length > 0 && givenAxes.length < GROUP_FRAME_AXES.length) {
1316
+ const missingAxes = GROUP_FRAME_AXES.filter((axis) => !givenAxes.includes(axis));
1317
+ warn("group/partial-frame", `addGroup: group "${itemOpts.objectName ?? ""}" has a partial frame (${givenAxes.join("/")} given, ${missingAxes.join("/")} missing); using auto-bounds (the bounding box of its children) instead. Pass all of x/y/w/h, or none.`);
1318
+ }
1319
+ const gb = hasCompleteGroupFrame(itemOpts) ? {
1320
+ x,
1321
+ y,
1322
+ cx,
1323
+ cy
1324
+ } : resolveObjBounds(slideItemObj);
1325
+ const gx = gb.x;
1326
+ const gy = gb.y;
1327
+ const gcx = gb.cx;
1328
+ const gcy = gb.cy;
1329
+ const grpLockXml = genXmlObjectLock("a:grpSpLocks", GROUP_SHAPE_LOCK_ATTRS, itemOpts.objectLock, itemOpts.objectName);
1330
+ strSlideXml += "<p:grpSp>";
1331
+ strSlideXml += el("p:nvGrpSpPr", null, [
1332
+ raw(cNvPrEl(shapeId, itemOpts.objectName, itemOpts.altText || "")),
1333
+ raw(elOrVoid("p:cNvGrpSpPr", null, grpLockXml)),
1334
+ raw(voidEl("p:nvPr"))
1335
+ ]);
1336
+ strSlideXml += el("p:grpSpPr", null, raw(grpXfrmEl({
1337
+ x: gx,
1338
+ y: gy,
1339
+ cx: gcx,
1340
+ cy: gcy
1341
+ }, locationAttrs)));
1342
+ strSlideXml += innerXml;
1343
+ strSlideXml += "</p:grpSp>";
1344
+ break;
1345
+ }
1346
+ case "notes": break;
1347
+ case "tablecell": break;
1348
+ case "hyperlink": break;
1349
+ case "online": break;
1350
+ default: slideItemObj._type;
1351
+ }
1352
+ return strSlideXml;
1353
+ };
1354
+ slide._slideObjects.forEach((slideItemObj) => {
1355
+ spTreeChildren.push(renderSlideObjectXml(slideItemObj));
1356
+ });
1357
+ if (slide._slideNumberProps) {
1358
+ let maxShapeId = 1;
1359
+ for (const [obj, id] of shapeIds) {
1360
+ const lastId = id + shapeIdCount(obj) - 1;
1361
+ if (lastId > maxShapeId) maxShapeId = lastId;
1362
+ }
1363
+ spTreeChildren.push(slideNumberPlaceholderXml(slide, slide._slideNumberProps, maxShapeId + 1));
1364
+ }
1365
+ strSlideXml += spTreeEl(spTreeChildren);
1366
+ strSlideXml += "</p:cSld>";
1367
+ return strSlideXml;
1368
+ }
1369
+ /**
1370
+ * The relationship id a part's default relationships are numbered from: one past the highest id
1371
+ * among its hyperlink, chart and media relationships.
1372
+ *
1373
+ * Exported because the master's `<p:sldLayoutId r:id>` values have to name the ids its `.rels` part
1374
+ * gives the layouts, and `makeXmlMaster` derived them as `_rels.length + idx + 1`. The two agreed
1375
+ * only while nothing registered a chart or media relationship on the master, since that count
1376
+ * ignores both.
1377
+ * @param slide - the slide, layout or master whose relationships are numbered
1378
+ */
1379
+ function defaultRelIdStart(slide) {
1380
+ let lastRid = 0;
1381
+ for (const rel of slide._rels) lastRid = Math.max(lastRid, rel.rId);
1382
+ for (const rel of slide._relsChart || []) lastRid = Math.max(lastRid, rel.rId);
1383
+ for (const rel of slide._relsMedia || []) lastRid = Math.max(lastRid, rel.rId);
1384
+ return lastRid + 1;
1385
+ }
1386
+ /**
1387
+ * Transforms slide relations to XML string.
1388
+ * Extra relations that are not dynamic can be passed using the 2nd arg (e.g. theme relation in master file).
1389
+ * These relations are numbered from {@link defaultRelIdStart}.
1390
+ * `selfPath` is the emitting part's own path, and it is required because a relationship target
1391
+ * resolves against the part that states it. An internal slide link used to be written
1392
+ * `slide${n}.xml` whatever the part, which is right from `ppt/slides/` and names nothing from
1393
+ * `ppt/slideLayouts/` or `ppt/slideMasters/` — so a `hyperlink: { slide }` on an object given to
1394
+ * `defineSlideMaster({ objects })` wrote a dangling relationship.
1395
+ * @param {PresSlideInternal | SlideLayoutInternal | SlideMasterInternal} slide - slide, layout or master whose relations are being transformed
1396
+ * @param {{ target: string; type: string }[]} defaultRels - array of default relations
1397
+ * @param {string} selfPath - the emitting part's own path, e.g. `ppt/slideLayouts/slideLayout2.xml`
1398
+ * @return {string} XML
1399
+ */
1400
+ function slideObjectRelationsToXml(slide, defaultRels, selfPath) {
1401
+ const rels = [];
1402
+ /**
1403
+ * The Targets emitted so far *from the media loop below*, which is the only place the
1404
+ * question is asked: a media item produces TWO rels sharing one Target, and the second is
1405
+ * told from the first by the first already being there.
1406
+ *
1407
+ * Two things about the shape. It holds raw Targets rather than escaped ones, because this
1408
+ * asks about the model and not about markup — the probe used to substring-scan the emitted
1409
+ * XML for ` Target="…"`, which forced the caller to escape by hand with the same escaper the
1410
+ * builder uses or the two drifted apart, and cost O(rels squared) in string scanning.
1411
+ *
1412
+ * And it is scoped to the media loop rather than to every rel. Scanning all of them meant a
1413
+ * hyperlink could answer a question about media, which is not a hypothetical: an online video
1414
+ * and a hyperlink to the same URL is an ordinary thing to author, and the hyperlink came
1415
+ * first, so the video pair came out as two MS-media rels with no ECMA `video` rel at all and
1416
+ * `<a:videoFile r:link>` pointing at the wrong type.
1417
+ */
1418
+ const mediaTargets = /* @__PURE__ */ new Set();
1419
+ slide._rels.forEach((rel) => {
1420
+ if (isHyperlinkRel(rel)) {
1421
+ if (rel.data === "slide") rels.push(relationshipEl(rel.rId, SLIDE_REL, targetBetweenPptSubparts(selfPath, slidePath(Number(rel.Target)))));
1422
+ else rels.push(externalHyperlinkRel(rel.rId, rel.Target));
1423
+ }
1424
+ });
1425
+ (slide._relsChart || []).forEach((rel) => {
1426
+ rels.push(relationshipEl(rel.rId, rel.isChartEx ? CHARTEX_REL : CHART_REL, rel.Target));
1427
+ });
1428
+ (slide._relsMedia || []).forEach((rel) => {
1429
+ const relType = rel.type.toLowerCase();
1430
+ const seen = mediaTargets.has(rel.Target);
1431
+ mediaTargets.add(rel.Target);
1432
+ const media = (type, targetMode) => relationshipEl(rel.rId, type, rel.Target, { targetMode });
1433
+ if (rel.oleRelType) rels.push(media(rel.oleRelType));
1434
+ else if (rel.model3dRelType) rels.push(media(rel.model3dRelType));
1435
+ else if (relType.includes("image")) rels.push(media(IMAGE_REL));
1436
+ else if (relType.includes("audio")) rels.push(seen ? media(MS_MEDIA_REL) : media(AUDIO_REL));
1437
+ else if (relType.includes("video")) rels.push(seen ? media(MS_MEDIA_REL) : media(VIDEO_REL));
1438
+ else if (relType.includes("online")) rels.push(seen ? media(MS_MEDIA_REL, "External") : media(VIDEO_REL, "External"));
1439
+ });
1440
+ const start = defaultRelIdStart(slide);
1441
+ defaultRels.forEach((rel, idx) => {
1442
+ rels.push(relationshipEl(start + idx, rel.type, rel.target));
1443
+ });
1444
+ return relationshipsPart(rels);
1445
+ }
1446
+ //#endregion
1447
+ //#region src/gen/slide/slide.ts
1448
+ /**
1449
+ * ts-pptx: slide part + slide/layout rels
1450
+ *
1451
+ * Emit a slide (`ppt/slides/slideN.xml`) and the relationship files for slides
1452
+ * and layouts (`slideN.xml.rels`, `slideLayoutN.xml.rels`).
1453
+ */
1454
+ /**
1455
+ * Generates XML for the slide file (`ppt/slides/slide1.xml`)
1456
+ * @param slide - the slide object to transform into XML
1457
+ * @param renderers - which renderer emits each shape family, passed through to the shape walk
1458
+ * @param transitionSoundRId - the relationship id package assembly registered this slide's transition
1459
+ * start sound under, if it registered one
1460
+ * @return XML
1461
+ */
1462
+ function makeXmlSlide(slide, renderers, transitionSoundRId) {
1463
+ return XML_DECL + "\r\n" + el("p:sld", {
1464
+ ...PML_ROOT_NS,
1465
+ show: slide?.hidden ? "0" : null
1466
+ }, [
1467
+ raw(slideObjectToXml(slide, renderers)),
1468
+ raw(el("p:clrMapOvr", null, raw(voidEl("a:masterClrMapping")))),
1469
+ raw(slideTransitionToXml(slide, transitionSoundRId)),
1470
+ raw(slideTimingToXml(slide))
1471
+ ]);
1472
+ }
1473
+ /**
1474
+ * Generates XML string for a slide layout relation file
1475
+ * @param {number} layoutNumber - 1-indexed number of a layout that relations are generated for
1476
+ * @param {SlideLayoutInternal[]} slideLayouts - Slide Layouts
1477
+ * @return {string} XML
1478
+ */
1479
+ function makeXmlSlideLayoutRel(layoutNumber, slideLayouts) {
1480
+ const slideLayout = slideLayouts[layoutNumber - 1];
1481
+ if (!slideLayout) throw new InternalError("slide/rel-index-out-of-range", `makeXmlSlideLayoutRel: no slide layout at index ${layoutNumber - 1}`);
1482
+ return slideObjectRelationsToXml(slideLayout, [{
1483
+ target: "../slideMasters/slideMaster1.xml",
1484
+ type: SLIDE_MASTER_REL
1485
+ }], slideLayoutPath(layoutNumber));
1486
+ }
1487
+ /**
1488
+ * Creates `ppt/_rels/slide*.xml.rels`
1489
+ * @param {PresSlideInternal[]} slides
1490
+ * @param {SlideLayoutInternal[]} slideLayouts - Slide Layout(s)
1491
+ * @param {number} `slideNumber` 1-indexed number of a layout that relations are generated for
1492
+ * @return {string} XML
1493
+ */
1494
+ function makeXmlSlideRel(slides, slideLayouts, slideNumber) {
1495
+ const slide = slides[slideNumber - 1];
1496
+ if (!slide) throw new InternalError("slide/rel-index-out-of-range", `makeXmlSlideRel: no slide at index ${slideNumber - 1}`);
1497
+ const defaultRels = [{
1498
+ target: targetFromPptSubpart(slideLayoutPath(getLayoutIdxForSlide(slides, slideLayouts, slideNumber))),
1499
+ type: SLIDE_LAYOUT_REL
1500
+ }, {
1501
+ target: targetFromPptSubpart(notesSlidePath(slideNumber)),
1502
+ type: NOTES_SLIDE_REL
1503
+ }];
1504
+ if ((slide._comments || []).length > 0) defaultRels.push({
1505
+ target: targetFromPptSubpart(commentPath(slideNumber)),
1506
+ type: COMMENTS_REL
1507
+ });
1508
+ return slideObjectRelationsToXml(slide, defaultRels, slidePath(slideNumber));
1509
+ }
1510
+ /**
1511
+ * For the passed slide number, resolves name of a layout that is used for.
1512
+ * @param {PresSlideInternal[]} slides - srray of slides
1513
+ * @param {SlideLayoutInternal[]} slideLayouts - array of slideLayouts
1514
+ * @param {number} slideNumber
1515
+ * @return {number} slide number
1516
+ */
1517
+ function getLayoutIdxForSlide(slides, slideLayouts, slideNumber) {
1518
+ for (let i = 0; i < slideLayouts.length; i++) if (slideLayouts[i]?._name === slides[slideNumber - 1]?._slideLayout?._name) return i + 1;
1519
+ return 1;
1520
+ }
1521
+ //#endregion
1522
+ //#region src/gen/pres/theme.ts
1523
+ /**
1524
+ * ts-pptx: `ppt/theme/theme1.xml`
1525
+ *
1526
+ * Emit the Office theme: the `<a:clrScheme>` (with any caller color overrides
1527
+ * over the default Office scheme) plus the fixed font/format schemes.
1528
+ */
1529
+ /**
1530
+ * Theme `<a:clrScheme>` slots in OOXML document order, with their default Office color child.
1531
+ * `dk1`/`lt1` default to `sysClr` (windowText/window); the rest are `srgbClr`. A user override
1532
+ * for any slot is emitted as `<a:srgbClr>` (see `buildThemeClrScheme`).
1533
+ */
1534
+ const THEME_CLR_SCHEME_DEFAULTS = [
1535
+ ["dk1", raw(voidEl("a:sysClr", {
1536
+ val: "windowText",
1537
+ lastClr: "000000"
1538
+ }))],
1539
+ ["lt1", raw(voidEl("a:sysClr", {
1540
+ val: "window",
1541
+ lastClr: "FFFFFF"
1542
+ }))],
1543
+ ["dk2", raw(voidEl("a:srgbClr", { val: "44546A" }))],
1544
+ ["lt2", raw(voidEl("a:srgbClr", { val: "E7E6E6" }))],
1545
+ ["accent1", raw(voidEl("a:srgbClr", { val: "4472C4" }))],
1546
+ ["accent2", raw(voidEl("a:srgbClr", { val: "ED7D31" }))],
1547
+ ["accent3", raw(voidEl("a:srgbClr", { val: "A5A5A5" }))],
1548
+ ["accent4", raw(voidEl("a:srgbClr", { val: "FFC000" }))],
1549
+ ["accent5", raw(voidEl("a:srgbClr", { val: "5B9BD5" }))],
1550
+ ["accent6", raw(voidEl("a:srgbClr", { val: "70AD47" }))],
1551
+ ["hlink", raw(voidEl("a:srgbClr", { val: "0563C1" }))],
1552
+ ["folHlink", raw(voidEl("a:srgbClr", { val: "954F72" }))]
1553
+ ];
1554
+ /**
1555
+ * Build the theme `<a:clrScheme>` block, applying any caller-supplied color overrides over the
1556
+ * default Office scheme. Invalid (non 6-digit-hex) overrides warn and keep the default rather
1557
+ * than emitting a degenerate color.
1558
+ * @param {ThemeColorScheme} [scheme] - per-slot hex overrides
1559
+ * @return {string} the `<a:clrScheme>...</a:clrScheme>` XML
1560
+ */
1561
+ function buildThemeClrScheme(scheme) {
1562
+ return el("a:clrScheme", { name: "Office" }, THEME_CLR_SCHEME_DEFAULTS.map(([slot, defaultChild]) => {
1563
+ const override = scheme?.[slot];
1564
+ let child = defaultChild;
1565
+ if (typeof override === "string" && override.length > 0) {
1566
+ const hex = stripHash(override);
1567
+ if (isHexColor(hex)) child = raw(voidEl("a:srgbClr", { val: hex.toUpperCase() }));
1568
+ else warn("theme/invalid-color-override", `makeXmlTheme: colorScheme.${slot} "${override}" is not a 6-digit hex color; keeping the Office default.`);
1569
+ }
1570
+ return raw(el(`a:${slot}`, null, child));
1571
+ }));
1572
+ }
1573
+ /**
1574
+ * The per-script theme font fallback table (`a:fontScheme`'s CT_TextFont lists), in OOXML
1575
+ * document order. Static Office defaults — no caller data, no escaping risk — unlike the
1576
+ * `<a:latin>`/`<a:ea>`/`<a:cs>` slots above.
1577
+ *
1578
+ * Forty-one of the forty-seven scripts take the same face in the major and minor lists, so the
1579
+ * shared table below is the whole thing and the two override maps hold the six-odd rows that
1580
+ * actually differ. Written out twice, ninety-four lines said less than these do, and one row
1581
+ * mistyped in one copy produces a theme that opens fine and paints a script in the wrong face —
1582
+ * a difference nobody reviewing a diff of two 47-row tables would see.
1583
+ */
1584
+ const THEME_FONT_LIST = [
1585
+ ["Jpan", "游ゴシック"],
1586
+ ["Hang", "맑은 고딕"],
1587
+ ["Hans", "等线"],
1588
+ ["Hant", "新細明體"],
1589
+ ["Arab", "Arial"],
1590
+ ["Hebr", "Arial"],
1591
+ ["Thai", "Cordia New"],
1592
+ ["Ethi", "Nyala"],
1593
+ ["Beng", "Vrinda"],
1594
+ ["Gujr", "Shruti"],
1595
+ ["Khmr", "DaunPenh"],
1596
+ ["Knda", "Tunga"],
1597
+ ["Guru", "Raavi"],
1598
+ ["Cans", "Euphemia"],
1599
+ ["Cher", "Plantagenet Cherokee"],
1600
+ ["Yiii", "Microsoft Yi Baiti"],
1601
+ ["Tibt", "Microsoft Himalaya"],
1602
+ ["Thaa", "MV Boli"],
1603
+ ["Deva", "Mangal"],
1604
+ ["Telu", "Gautami"],
1605
+ ["Taml", "Latha"],
1606
+ ["Syrc", "Estrangelo Edessa"],
1607
+ ["Orya", "Kalinga"],
1608
+ ["Mlym", "Kartika"],
1609
+ ["Laoo", "DokChampa"],
1610
+ ["Sinh", "Iskoola Pota"],
1611
+ ["Mong", "Mongolian Baiti"],
1612
+ ["Viet", "Arial"],
1613
+ ["Uigh", "Microsoft Uighur"],
1614
+ ["Geor", "Sylfaen"],
1615
+ ["Armn", "Arial"],
1616
+ ["Bugi", "Leelawadee UI"],
1617
+ ["Bopo", "Microsoft JhengHei"],
1618
+ ["Java", "Javanese Text"],
1619
+ ["Lisu", "Segoe UI"],
1620
+ ["Mymr", "Myanmar Text"],
1621
+ ["Nkoo", "Ebrima"],
1622
+ ["Olck", "Nirmala UI"],
1623
+ ["Osma", "Ebrima"],
1624
+ ["Phag", "Phagspa"],
1625
+ ["Syrn", "Estrangelo Edessa"],
1626
+ ["Syrj", "Estrangelo Edessa"],
1627
+ ["Syre", "Estrangelo Edessa"],
1628
+ ["Sora", "Nirmala UI"],
1629
+ ["Tale", "Microsoft Tai Le"],
1630
+ ["Talu", "Microsoft New Tai Lue"],
1631
+ ["Tfng", "Ebrima"]
1632
+ ];
1633
+ /**
1634
+ * The seven scripts whose *heading* face differs from the body one. Latin and CJK headings take
1635
+ * the Light weight; the scripts with no Light cut take a serif (or, for Khmer, a different face
1636
+ * entirely) where the body takes a sans. Every other script in {@link THEME_FONT_LIST} uses one
1637
+ * face for both.
1638
+ */
1639
+ const MAJOR_FONT_OVERRIDES = {
1640
+ Jpan: "游ゴシック Light",
1641
+ Hans: "等线 Light",
1642
+ Arab: "Times New Roman",
1643
+ Hebr: "Times New Roman",
1644
+ Thai: "Angsana New",
1645
+ Khmr: "MoolBoran",
1646
+ Viet: "Times New Roman"
1647
+ };
1648
+ /** The major (heading) and minor (body) lists, both derived from the one table. */
1649
+ const MAJOR_FONT_LIST = THEME_FONT_LIST.map(([script, face]) => [script, MAJOR_FONT_OVERRIDES[script] ?? face]);
1650
+ const MINOR_FONT_LIST = THEME_FONT_LIST;
1651
+ function fontListChildren(scriptFaces) {
1652
+ return scriptFaces.map(([script, typeface]) => raw(voidEl("a:font", {
1653
+ script,
1654
+ typeface
1655
+ })));
1656
+ }
1657
+ /**
1658
+ * Creates `ppt/theme/theme1.xml`
1659
+ * @return {string} XML
1660
+ */
1661
+ function makeXmlTheme(pres) {
1662
+ const majorLatin = pres.theme?.headFontFace ? raw(voidEl("a:latin", { typeface: pres.theme.headFontFace })) : raw(voidEl("a:latin", {
1663
+ typeface: "Calibri Light",
1664
+ panose: "020F0302020204030204"
1665
+ }));
1666
+ const minorLatin = pres.theme?.bodyFontFace ? raw(voidEl("a:latin", { typeface: pres.theme.bodyFontFace })) : raw(voidEl("a:latin", {
1667
+ typeface: "Calibri",
1668
+ panose: "020F0502020204030204"
1669
+ }));
1670
+ const majorEa = raw(voidEl("a:ea", { typeface: pres.theme?.headFontFaceEA ?? "" }));
1671
+ const minorEa = raw(voidEl("a:ea", { typeface: pres.theme?.bodyFontFaceEA ?? "" }));
1672
+ const majorCs = raw(voidEl("a:cs", { typeface: pres.theme?.headFontFaceCS ?? "" }));
1673
+ const minorCs = raw(voidEl("a:cs", { typeface: pres.theme?.bodyFontFaceCS ?? "" }));
1674
+ const fontScheme = el("a:fontScheme", { name: "Office" }, [raw(el("a:majorFont", null, [
1675
+ majorLatin,
1676
+ majorEa,
1677
+ majorCs,
1678
+ ...fontListChildren(MAJOR_FONT_LIST)
1679
+ ])), raw(el("a:minorFont", null, [
1680
+ minorLatin,
1681
+ minorEa,
1682
+ minorCs,
1683
+ ...fontListChildren(MINOR_FONT_LIST)
1684
+ ]))]);
1685
+ const themeElements = el("a:themeElements", null, [
1686
+ raw(buildThemeClrScheme(pres.theme?.colorScheme)),
1687
+ raw(fontScheme),
1688
+ raw(FMT_SCHEME_XML)
1689
+ ]);
1690
+ const extLst = el("a:extLst", null, raw(el("a:ext", { uri: "{05A4C25C-085E-4340-85A3-A5531E510DB2}" }, raw(voidEl("thm15:themeFamily", {
1691
+ "xmlns:thm15": "http://schemas.microsoft.com/office/thememl/2012/main",
1692
+ name: "Office Theme",
1693
+ id: "{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}",
1694
+ vid: "{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"
1695
+ })))));
1696
+ return XML_DECL + el("a:theme", {
1697
+ "xmlns:a": OOXML_NS.a,
1698
+ name: "Office Theme"
1699
+ }, [
1700
+ raw(themeElements),
1701
+ raw(voidEl("a:objectDefaults")),
1702
+ raw(voidEl("a:extraClrSchemeLst")),
1703
+ raw(extLst)
1704
+ ]);
1705
+ }
1706
+ //#endregion
1707
+ //#region src/gen/extract-slides.ts
1708
+ /**
1709
+ * Embedded audio/video. addMedia (`gen/define/media.ts`) pushes three consecutive
1710
+ * _relsMedia entries per item off `mediaRid`: the ECMA audio/video rel
1711
+ * (rId=mediaRid), the MS-2007 `media` rel (mediaRid+1, sharing one Target),
1712
+ * and the preview image rel (mediaRid+2). The body references all three; we
1713
+ * surface the descriptor so appendSlides can reproduce the rel graph. Online
1714
+ * media (external link) is excluded — it has a different rel shape and no part.
1715
+ */
1716
+ function avMediaOf(slide, relByRid) {
1717
+ return slide._slideObjects.filter((obj) => obj._type === "media" && obj.mtype !== "online" && typeof obj.mediaRid === "number").map((obj) => {
1718
+ const mtype = obj.mtype === "audio" ? "audio" : "video";
1719
+ const mediaRid = obj.mediaRid;
1720
+ const mediaRel = relByRid.get(mediaRid);
1721
+ const previewRel = relByRid.get(previewRid(mediaRid));
1722
+ if (!mediaRel || !previewRel) return null;
1723
+ const mediaBytes = decodeBase64ToBytes(typeof mediaRel.data === "string" ? mediaRel.data : "");
1724
+ const previewBytes = decodeBase64ToBytes(typeof previewRel.data === "string" ? previewRel.data : "");
1725
+ if (!mediaBytes || !previewBytes) return null;
1726
+ const mediaExtn = relExtension(mediaRel, "mp4");
1727
+ const previewExtn = relExtension(previewRel, "png");
1728
+ return {
1729
+ mtype,
1730
+ mediaRid,
1731
+ msMediaRid: msMediaRid(mediaRid),
1732
+ previewRid: previewRid(mediaRid),
1733
+ mediaBytes,
1734
+ mediaExtn,
1735
+ mediaContentType: avContentType(mediaExtn, mtype),
1736
+ previewBytes,
1737
+ previewExtn,
1738
+ previewContentType: imageContentType(previewExtn)
1739
+ };
1740
+ }).filter((m) => m !== null);
1741
+ }
1742
+ /**
1743
+ * A media rel's lowercased file extension: its own `extn` when it has one, else the suffix of
1744
+ * its `Target`, else `fallback`.
1745
+ * @param rel - the media relationship
1746
+ * @param fallback - the extension to assume when neither source names one
1747
+ */
1748
+ function relExtension(rel, fallback) {
1749
+ return (rel.extn || rel.Target.split(".").pop() || fallback).toLowerCase();
1750
+ }
1751
+ /**
1752
+ * Plain image media, as decoded bytes keyed by the `rId` the body uses.
1753
+ *
1754
+ * `avPreviewRids` are the poster frames of {@link avMediaOf}'s items: they live in
1755
+ * `_relsMedia` as image rels too, and their descriptor already carries them, so they are
1756
+ * excluded here rather than surfaced twice.
1757
+ */
1758
+ function imageMediaOf(slide, avPreviewRids) {
1759
+ return slide._relsMedia.filter((rel) => rel.type.toLowerCase().includes("image") && !avPreviewRids.has(rel.rId)).map((rel) => {
1760
+ const data = rel.data && typeof rel.data === "string" ? rel.data : "";
1761
+ const bytes = decodeBase64ToBytes(data);
1762
+ const extn = relExtension(rel, "png");
1763
+ return bytes ? {
1764
+ rId: rel.rId,
1765
+ bytes,
1766
+ extn,
1767
+ contentType: imageContentType(extn)
1768
+ } : null;
1769
+ }).filter((m) => m !== null);
1770
+ }
1771
+ /**
1772
+ * Online (external-link) video. addMedia type:'online' pushes three rels off
1773
+ * mediaRid: the ECMA video rel (mediaRid, External), the MS-2007 media rel
1774
+ * (mediaRid+1, External, sharing the link Target), and the poster image rel
1775
+ * (mediaRid+2, carried by {@link imageMediaOf} as a normal image). No media binary
1776
+ * part exists; appendSlides reproduces only the two external rels + poster.
1777
+ */
1778
+ function onlineMediaOf(slide, relByRid) {
1779
+ return slide._slideObjects.filter((obj) => obj._type === "media" && obj.mtype === "online" && typeof obj.mediaRid === "number").map((obj) => {
1780
+ const mediaRid = obj.mediaRid;
1781
+ const videoRel = relByRid.get(mediaRid);
1782
+ if (!videoRel || typeof videoRel.Target !== "string" || !videoRel.Target) return null;
1783
+ return {
1784
+ mediaRid,
1785
+ msMediaRid: msMediaRid(mediaRid),
1786
+ link: videoRel.Target
1787
+ };
1788
+ }).filter((m) => m !== null);
1789
+ }
1790
+ /** External hyperlink rels, keyed by the `rId` the body uses. */
1791
+ function hyperlinksOf(slide) {
1792
+ return slide._rels.filter((rel) => isHyperlinkRel(rel) && rel.data !== "slide").map((rel) => ({
1793
+ rId: rel.rId,
1794
+ target: rel.Target
1795
+ }));
1796
+ }
1797
+ /**
1798
+ * Internal slide-to-slide links: `rel.data === 'slide'`, and `rel.Target` is the 1-based
1799
+ * source slide number (see addText's hyperlink.slide handling).
1800
+ *
1801
+ * A Target that does not read as a slide number throws rather than passing `NaN` on to the
1802
+ * append path's rel-graph rebuild, where it surfaces as "links to source slide NaN" a whole
1803
+ * subsystem later. `addText` writes this Target itself, so the condition is a defect on this
1804
+ * side rather than bad input — an {@link InternalError}, like the chart router's own.
1805
+ */
1806
+ function slideLinksOf(slide) {
1807
+ return slide._rels.filter((rel) => isHyperlinkRel(rel) && rel.data === "slide").map((rel) => {
1808
+ const sourceSlideNumber = Number(rel.Target);
1809
+ if (!Number.isInteger(sourceSlideNumber) || sourceSlideNumber < 1) throw new InternalError("slide/link-target-not-a-number", `Internal slide link rId${rel.rId} targets "${String(rel.Target)}", which is not a slide number`);
1810
+ return {
1811
+ rId: rel.rId,
1812
+ sourceSlideNumber
1813
+ };
1814
+ });
1815
+ }
1816
+ /**
1817
+ * Speaker notes, or `undefined` when the slide has none. The body and the hyperlinks come from one
1818
+ * resolution of the notes, because the two must agree on every hyperlink rId. Notes rels reserve
1819
+ * rId1=notesMaster and rId2=slide, both of which appendSlides wires itself.
1820
+ */
1821
+ function notesOf(slide) {
1822
+ if (!slide._slideObjects.some((obj) => obj._type === "notes")) return void 0;
1823
+ const notes = buildNotesSlideRels(slide);
1824
+ return {
1825
+ xml: makeXmlNotesSlide(slide, notes),
1826
+ hyperlinks: notes.rels.filter((rel) => typeof rel.Target === "string" && rel.Target).map((rel) => ({
1827
+ rId: rel.rId,
1828
+ target: rel.Target
1829
+ }))
1830
+ };
1831
+ }
1832
+ /**
1833
+ * Serialize a deck's authored slides as injectable descriptors, without producing a package.
1834
+ * @param source - the deck state to read; see {@link ExtractSource}
1835
+ * @param opts.onMediaError - what a media source that cannot be read does, as on `write()`
1836
+ */
1837
+ async function extractSlides(source, opts = {}) {
1838
+ const onMediaError = opts.onMediaError ?? "throw";
1839
+ const { presentation } = source;
1840
+ const deckSlides = presentation.slides;
1841
+ requireSlideLinksInDeck(deckSlides);
1842
+ backfillPlaceholders(deckSlides);
1843
+ await encodeMediaForTargets(deckSlides, source.runtime, onMediaError);
1844
+ const restoreAuthoredFit = bakeMeasuredFit(deckSlides, source.fontMetrics);
1845
+ let slides;
1846
+ try {
1847
+ slides = deckSlides.map((slide) => {
1848
+ const relByRid = new Map(slide._relsMedia.map((rel) => [rel.rId, rel]));
1849
+ const avMedia = avMediaOf(slide, relByRid);
1850
+ const notes = notesOf(slide);
1851
+ return {
1852
+ xml: makeXmlSlide(slide, source.renderers),
1853
+ media: imageMediaOf(slide, new Set(avMedia.map((item) => item.previewRid))),
1854
+ hyperlinks: hyperlinksOf(slide),
1855
+ charts: source.extract.charts?.(slide) ?? [],
1856
+ slideLinks: slideLinksOf(slide),
1857
+ avMedia,
1858
+ onlineMedia: onlineMediaOf(slide, relByRid),
1859
+ ...notes ? { notes } : {}
1860
+ };
1861
+ });
1862
+ } finally {
1863
+ restoreAuthoredFit();
1864
+ }
1865
+ const notesMaster = slides.some((s) => s.notes) ? {
1866
+ xml: makeXmlNotesMaster(),
1867
+ themeXml: makeXmlTheme(presentation)
1868
+ } : void 0;
1869
+ return {
1870
+ widthEmu: presentation.presLayout.width,
1871
+ heightEmu: presentation.presLayout.height,
1872
+ slides,
1873
+ embeddedFonts: presentation.embeddedFonts,
1874
+ ...notesMaster ? { notesMaster } : {}
1875
+ };
1876
+ }
1877
+ //#endregion
1878
+ //#region src/gen/opc/app.ts
1879
+ /**
1880
+ * ts-pptx: `docProps/app.xml`
1881
+ *
1882
+ * Emit the extended-properties part (application, slide/notes counts, heading
1883
+ * pairs, titles of parts, company).
1884
+ */
1885
+ /** This part is pretty-printed, one element per line, at three nesting depths. */
1886
+ const INDENT_1 = "\n ";
1887
+ const INDENT_2 = "\n ";
1888
+ const INDENT_3 = "\n ";
1889
+ const PROP = { openPrefix: INDENT_1 };
1890
+ const VECTOR = {
1891
+ openPrefix: INDENT_2,
1892
+ closePrefix: INDENT_2
1893
+ };
1894
+ const ITEM = { openPrefix: INDENT_3 };
1895
+ const NS = {
1896
+ xmlns: OOXML_NS.ep,
1897
+ "xmlns:vt": OOXML_NS.vt
1898
+ };
1899
+ /** One `<vt:variant>` of a heading pair: the section's name, then its count. */
1900
+ function headingPair(name, count) {
1901
+ return [el("vt:variant", null, raw(el("vt:lpstr", null, name)), ITEM), el("vt:variant", null, raw(el("vt:i4", null, count)), ITEM)];
1902
+ }
1903
+ /**
1904
+ * Creates `docProps/app.xml`
1905
+ *
1906
+ * `PresentationFormat` and `HiddenSlides` were constants -- `On-screen Show (16:9)` and `0` --
1907
+ * whatever the deck. PowerPoint rewrites both on its first save, so nothing broke, but until then
1908
+ * a 4:3 deck and a deck with hidden slides each stated something false to anything reading the
1909
+ * package without opening it. Both are known here, so both are reported.
1910
+ * @param {PresSlideInternal[]} slides - Presenation Slides
1911
+ * @param {string} company - "Company" metadata
1912
+ * @param {PresLayout} presLayout - the deck's slide size, which names its presentation format
1913
+ * @returns XML
1914
+ */
1915
+ function makeXmlApp(slides, company, presLayout) {
1916
+ const headingPairs = el("vt:vector", {
1917
+ size: 6,
1918
+ baseType: "variant"
1919
+ }, [
1920
+ ...headingPair("Fonts Used", 2),
1921
+ ...headingPair("Theme", 1),
1922
+ ...headingPair("Slide Titles", slides.length)
1923
+ ].map(raw), VECTOR);
1924
+ const titlesOfParts = el("vt:vector", {
1925
+ size: slides.length + 1 + 2,
1926
+ baseType: "lpstr"
1927
+ }, [
1928
+ raw(el("vt:lpstr", null, "Arial", ITEM)),
1929
+ raw(el("vt:lpstr", null, "Calibri", ITEM)),
1930
+ raw(el("vt:lpstr", null, "Office Theme", ITEM)),
1931
+ raw(INDENT_3),
1932
+ ...slides.map((_slideObj, idx) => raw(el("vt:lpstr", null, `Slide ${idx + 1}`)))
1933
+ ], VECTOR);
1934
+ return XML_DECL + "\r\n" + el("Properties", NS, [
1935
+ raw(el("TotalTime", null, 0, PROP)),
1936
+ raw(el("Words", null, 0, PROP)),
1937
+ raw(el("Application", null, "Microsoft Office PowerPoint", PROP)),
1938
+ raw(el("PresentationFormat", null, presentationFormatLabel(presLayout.width, presLayout.height), PROP)),
1939
+ raw(el("Paragraphs", null, 0, PROP)),
1940
+ raw(el("Slides", null, slides.length, PROP)),
1941
+ raw(el("Notes", null, slides.length, PROP)),
1942
+ raw(el("HiddenSlides", null, slides.filter((slide) => slide.hidden).length, PROP)),
1943
+ raw(el("MMClips", null, 0, PROP)),
1944
+ raw(el("ScaleCrop", null, "false", PROP)),
1945
+ raw(el("HeadingPairs", null, raw(headingPairs), {
1946
+ ...PROP,
1947
+ closePrefix: INDENT_1
1948
+ })),
1949
+ raw(el("TitlesOfParts", null, raw(titlesOfParts), {
1950
+ ...PROP,
1951
+ closePrefix: INDENT_1
1952
+ })),
1953
+ raw(el("Company", null, company, PROP)),
1954
+ raw(el("LinksUpToDate", null, "false", PROP)),
1955
+ raw(el("SharedDoc", null, "false", PROP)),
1956
+ raw(el("HyperlinksChanged", null, "false", PROP)),
1957
+ raw(el("AppVersion", null, "16.0000", PROP))
1958
+ ], { closePrefix: INDENT_1 });
1959
+ }
1960
+ //#endregion
1961
+ //#region src/gen/opc/content-types.ts
1962
+ /**
1963
+ * ts-pptx: `[Content_Types].xml`
1964
+ *
1965
+ * Emit the package content-types part: Default entries for the media extensions
1966
+ * actually used by the deck (plus the font default when faces are embedded) and
1967
+ * Override entries for every part the package skeleton writes.
1968
+ *
1969
+ * Entries that exist only because a construct family put a part in the package arrive as
1970
+ * {@link ContentTypeContributions} — already resolved to data and grouped by where they land.
1971
+ * The order below is a property of the format, not of whoever assembled that list, so a deck
1972
+ * written by a program that links three families and one that links ten emit the same bytes.
1973
+ */
1974
+ /**
1975
+ * Some Override entries have always been emitted with a leading space. It is insignificant
1976
+ * whitespace, but it is in the bytes this library has shipped for years, so it is reproduced
1977
+ * verbatim rather than normalized (see AGENTS.md "Byte identity": whitespace diffs are a STOP).
1978
+ */
1979
+ const LEADING_SPACE = { openPrefix: " " };
1980
+ /** Nothing beyond the skeleton — the shape a deck written with no construct families contributes. */
1981
+ const NO_CONTENT_TYPE_CONTRIBUTIONS = Object.freeze({
1982
+ defaults: [],
1983
+ presentation: [],
1984
+ perSlide: [],
1985
+ perLayout: [],
1986
+ trailing: []
1987
+ });
1988
+ function contentDefault(extension, contentType) {
1989
+ return voidEl("Default", {
1990
+ Extension: extension,
1991
+ ContentType: contentType
1992
+ });
1993
+ }
1994
+ function override(partName, contentType, fmt) {
1995
+ return voidEl("Override", {
1996
+ PartName: partName,
1997
+ ContentType: contentType
1998
+ }, fmt);
1999
+ }
2000
+ /** Render a contributed Override, honouring its leading-space flag. */
2001
+ function contributedOverride(entry) {
2002
+ return override(entry.partName, entry.contentType, entry.leadingSpace ? LEADING_SPACE : void 0);
2003
+ }
2004
+ /**
2005
+ * Generate XML ContentType
2006
+ * @param opts - the deck's parts, plus whatever the construct families it was written with add
2007
+ * @returns XML
2008
+ */
2009
+ function makeXmlContTypes(opts) {
2010
+ const { slides, slideLayouts, masterSlide, hasCustomProps, embeddedFonts } = opts;
2011
+ const contributions = opts.contributions ?? NO_CONTENT_TYPE_CONTRIBUTIONS;
2012
+ const parts = [contentDefault("xml", "application/xml"), contentDefault("rels", RELATIONSHIPS_CONTENT_TYPE)];
2013
+ const extnTypeMap = /* @__PURE__ */ new Map();
2014
+ const ctTargets = [];
2015
+ (slides || []).forEach((s) => ctTargets.push(s));
2016
+ (slideLayouts || []).forEach((l) => ctTargets.push(l));
2017
+ if (masterSlide) ctTargets.push(masterSlide);
2018
+ ctTargets.forEach((target) => {
2019
+ (target._relsMedia || []).forEach((rel) => {
2020
+ if (rel.type === "online" || !rel.extn || !rel.type) return;
2021
+ const contentType = rel.type.startsWith("audio/") ? avContentType(rel.extn, "audio") : rel.type.startsWith("video/") ? avContentType(rel.extn, "video") : rel.type;
2022
+ if (!extnTypeMap.has(rel.extn)) extnTypeMap.set(rel.extn, contentType);
2023
+ });
2024
+ });
2025
+ extnTypeMap.forEach((type, extn) => {
2026
+ parts.push(contentDefault(extn, type));
2027
+ });
2028
+ contributions.defaults.forEach((entry) => {
2029
+ if (extnTypeMap.has(entry.extension)) return;
2030
+ extnTypeMap.set(entry.extension, entry.contentType);
2031
+ parts.push(contentDefault(entry.extension, entry.contentType));
2032
+ });
2033
+ if ((embeddedFonts || []).some((font) => font.faces.some((face) => face.bytes))) parts.push(contentDefault(FONT_DATA_EXTENSION, FONT_DATA_CONTENT_TYPE));
2034
+ parts.push(override(overrideName(PRESENTATION_PATH), PRESENTATION_MAIN_CONTENT_TYPE));
2035
+ contributions.presentation.forEach((entry) => parts.push(contributedOverride(entry)));
2036
+ parts.push(override(overrideName(SLIDE_MASTER_PATH), SLIDE_MASTER_CONTENT_TYPE));
2037
+ slides.forEach((_slide, idx) => {
2038
+ parts.push(override(overrideName(slidePath(idx + 1)), SLIDE_CONTENT_TYPE));
2039
+ (contributions.perSlide[idx] || []).forEach((entry) => parts.push(contributedOverride(entry)));
2040
+ });
2041
+ parts.push(override("/ppt/presProps.xml", PRES_PROPS_CONTENT_TYPE));
2042
+ parts.push(override("/ppt/viewProps.xml", VIEW_PROPS_CONTENT_TYPE));
2043
+ parts.push(override("/ppt/theme/theme1.xml", THEME_CONTENT_TYPE));
2044
+ parts.push(override("/ppt/theme/theme2.xml", THEME_CONTENT_TYPE));
2045
+ parts.push(override("/ppt/tableStyles.xml", TABLE_STYLES_CONTENT_TYPE));
2046
+ slideLayouts.forEach((_layout, idx) => {
2047
+ parts.push(override(overrideName(slideLayoutPath(idx + 1)), SLIDE_LAYOUT_CONTENT_TYPE));
2048
+ (contributions.perLayout[idx] || []).forEach((entry) => parts.push(contributedOverride(entry)));
2049
+ });
2050
+ contributions.trailing.forEach((entry) => parts.push(contributedOverride(entry)));
2051
+ parts.push(override("/docProps/core.xml", CORE_PROPS_CONTENT_TYPE, LEADING_SPACE));
2052
+ parts.push(override("/docProps/app.xml", EXTENDED_PROPS_CONTENT_TYPE, LEADING_SPACE));
2053
+ if (hasCustomProps) parts.push(override("/docProps/custom.xml", CUSTOM_PROPS_CONTENT_TYPE, LEADING_SPACE));
2054
+ return XML_DECL + "\r\n" + el("Types", { xmlns: OOXML_NS.ct }, parts.map(raw));
2055
+ }
2056
+ //#endregion
2057
+ //#region src/gen/opc/custom-props.ts
2058
+ /**
2059
+ * ts-pptx: `docProps/custom.xml`
2060
+ *
2061
+ * Emit the custom-properties part from caller-supplied name/value pairs (bool,
2062
+ * date, number and string value types).
2063
+ */
2064
+ const CUSTOM_PROPS_FMTID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
2065
+ const PROPS_NS = {
2066
+ xmlns: "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
2067
+ "xmlns:vt": OOXML_NS.vt
2068
+ };
2069
+ /** Serialize one custom-property value to its typed `vt:` element. */
2070
+ function valueEl(value) {
2071
+ if (typeof value === "boolean") return el("vt:bool", null, String(value));
2072
+ if (value instanceof Date) return el("vt:filetime", null, value.toISOString().replace(/\.\d{3}Z$/, "Z"));
2073
+ if (typeof value === "number") return el(Number.isInteger(value) ? "vt:i4" : "vt:r8", null, value);
2074
+ return el("vt:lpwstr", null, String(value));
2075
+ }
2076
+ /**
2077
+ * Creates `docProps/custom.xml`
2078
+ * @param props - custom property name/value pairs
2079
+ * @returns XML
2080
+ */
2081
+ function makeXmlCustomProperties(props) {
2082
+ const properties = props.map(({ name, value }, idx) => raw(el("property", {
2083
+ fmtid: CUSTOM_PROPS_FMTID,
2084
+ pid: idx + 2,
2085
+ name
2086
+ }, raw(valueEl(value)))));
2087
+ return XML_DECL + "\r\n" + el("Properties", PROPS_NS, properties);
2088
+ }
2089
+ //#endregion
2090
+ //#region src/gen/opc/root-rels.ts
2091
+ /**
2092
+ * ts-pptx: `_rels/.rels`
2093
+ *
2094
+ * Emit the package root relationships (app/core/presentation, plus custom-props
2095
+ * when present).
2096
+ */
2097
+ /** Each entry carries its own indent; the `<Relationships>` wrapper adds none of its own. */
2098
+ const INDENTED = { fmt: { openPrefix: "\n " } };
2099
+ /**
2100
+ * Creates `_rels/.rels`
2101
+ * @returns XML
2102
+ */
2103
+ function makeXmlRootRels(hasCustomProps) {
2104
+ const rels = [
2105
+ relationshipEl("rId1", EXTENDED_PROPS_REL, "docProps/app.xml", INDENTED),
2106
+ relationshipEl("rId2", CORE_PROPS_REL, "docProps/core.xml", INDENTED),
2107
+ relationshipEl("rId3", OFFICE_DOCUMENT_REL, "ppt/presentation.xml", INDENTED)
2108
+ ];
2109
+ if (hasCustomProps) rels.push(relationshipEl("rId4", CUSTOM_PROPS_REL, "docProps/custom.xml", INDENTED));
2110
+ return relationshipsPart(rels, { closePrefix: "\n " });
2111
+ }
2112
+ //#endregion
2113
+ //#region src/gen/pres/presentation-rels.ts
2114
+ /**
2115
+ * Creates `ppt/_rels/presentation.xml.rels`
2116
+ * @param {PresSlideInternal[]} slides - Presenation Slides
2117
+ * @returns XML
2118
+ */
2119
+ /**
2120
+ * The relationship ids `presentation.xml.rels` gives its fixed parts, and the first id free
2121
+ * after them.
2122
+ *
2123
+ * Layout: rId1 = slideMaster, rId2..(N+1) = N slides, then notesMaster/presProps/viewProps/
2124
+ * theme1/tableStyles, then commentAuthors when the deck has any comment, then the embedded
2125
+ * fonts.
2126
+ *
2127
+ * The rels writer and `makeXmlPresentation` both need these numbers — the presentation part
2128
+ * names the notesMaster and each embedded face by `r:id` — and each derived them separately,
2129
+ * one as `slides.length + 2` under a comment saying it copied the other's logic. Inserting a
2130
+ * relationship before `notesMaster` then broke the presentation's own reference with no
2131
+ * signal.
2132
+ * @param slides - the deck's slides, whose count and comments decide the layout
2133
+ */
2134
+ function presentationFixedRelIds(slides) {
2135
+ const first = (slides?.length ?? 0) + 2;
2136
+ const hasComments = (slides || []).some((slide) => (slide._comments || []).length > 0);
2137
+ return {
2138
+ notesMaster: first,
2139
+ presProps: first + 1,
2140
+ viewProps: first + 2,
2141
+ theme: first + 3,
2142
+ tableStyles: first + 4,
2143
+ commentAuthors: hasComments ? first + 5 : null,
2144
+ fontStart: first + (hasComments ? 6 : 5)
2145
+ };
2146
+ }
2147
+ /**
2148
+ * The first relationship id free for embedded-font rels — {@link presentationFixedRelIds}'
2149
+ * `fontStart`, kept as its own name because that is what the two font walks ask for.
2150
+ */
2151
+ function presentationFontRelStart(slides) {
2152
+ return presentationFixedRelIds(slides).fontStart;
2153
+ }
2154
+ function makeXmlPresentationRels(slides, embeddedFonts) {
2155
+ const fixed = presentationFixedRelIds(slides);
2156
+ const rels = [relationshipEl(1, SLIDE_MASTER_REL, targetFromPresentation(SLIDE_MASTER_PATH))];
2157
+ for (let idx = 1; idx <= slides.length; idx++) rels.push(relationshipEl(idx + 1, SLIDE_REL, targetFromPresentation(slidePath(idx))));
2158
+ rels.push(relationshipEl(fixed.notesMaster, NOTES_MASTER_REL, targetFromPresentation(NOTES_MASTER_PATH)), relationshipEl(fixed.presProps, PRES_PROPS_REL, "presProps.xml"), relationshipEl(fixed.viewProps, VIEW_PROPS_REL, "viewProps.xml"), relationshipEl(fixed.theme, THEME_REL, "theme/theme1.xml"), relationshipEl(fixed.tableStyles, TABLE_STYLES_REL, "tableStyles.xml"));
2159
+ if (fixed.commentAuthors !== null) rels.push(relationshipEl(fixed.commentAuthors, COMMENT_AUTHORS_REL, "commentAuthors.xml"));
2160
+ for (const face of flattenEmbeddedFaces(embeddedFonts || [], presentationFontRelStart(slides))) rels.push(relationshipEl(face.rId, FONT_REL_TYPE, targetFromPresentation(fontPath(face.partIndex))));
2161
+ return relationshipsPart(rels);
2162
+ }
2163
+ //#endregion
2164
+ //#region src/gen/pres/presentation.ts
2165
+ /**
2166
+ * ts-pptx: `ppt/presentation.xml` + presProps/viewProps
2167
+ *
2168
+ * Emit the presentation part (masters/notes/slide id lists, sizes, embedded-font
2169
+ * list, default text styles, sections) and the small presProps/viewProps parts.
2170
+ */
2171
+ function defaultTextStyleLevel(idy, marL) {
2172
+ return lvlPPr(idy, {
2173
+ marL,
2174
+ algn: "l"
2175
+ }, [raw(themeFontDefRPr("mn", {
2176
+ sz: 1800,
2177
+ kern: 1200
2178
+ }))]);
2179
+ }
2180
+ function sectionsExtLst(sections) {
2181
+ return el("p:extLst", null, [raw(el("p:ext", { uri: "{521415D9-36F7-43E2-AB2F-B90AF26B5E84}" }, raw(el("p14:sectionLst", { "xmlns:p14": OOXML_NS.p14 }, sections.map((sect) => raw(el("p14:section", {
2182
+ name: sect.title,
2183
+ id: sect._id
2184
+ }, raw(el("p14:sldIdLst", null, sect._slides.map((slide) => raw(voidEl("p14:sldId", { id: slide._slideId })))))))))))), raw(el("p:ext", { uri: "{EFAFB233-063F-42B5-8137-9DF3F51BA10A}" }, raw(voidEl("p15:sldGuideLst", { "xmlns:p15": OOXML_NS.p15 }))))]);
2185
+ }
2186
+ /**
2187
+ * Create presentation file (`ppt/presentation.xml`)
2188
+ * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
2189
+ * @see http://www.datypic.com/sc/ooxml/t-p_CT_Presentation.html
2190
+ * @param {PresentationPropsInternal} pres - presentation
2191
+ * @return {string} XML
2192
+ */
2193
+ function makeXmlPresentation(pres) {
2194
+ const openTag = `<p:presentation xmlns:a="${OOXML_NS.a}" xmlns:r="${OOXML_NS.r}" xmlns:p="${OOXML_NS.p}" ${pres.rtlMode ? "rtl=\"1\"" : ""} ${(pres.embeddedFonts || []).some((font) => font.faces.some((face) => face.bytes)) ? "embedTrueTypeFonts=\"1\" saveSubsetFonts=\"0\"" : "saveSubsetFonts=\"1\""} autoCompressPictures="0"${pres.firstSlideNum !== 1 ? ` firstSlideNum="${pres.firstSlideNum}"` : ""}>`;
2195
+ const sldMasterIdLst = el("p:sldMasterIdLst", null, raw(voidEl("p:sldMasterId", {
2196
+ id: MIN_SLIDE_MASTER_ID,
2197
+ "r:id": "rId1"
2198
+ })));
2199
+ const notesMasterIdLst = el("p:notesMasterIdLst", null, raw(voidEl("p:notesMasterId", { "r:id": `rId${presentationFixedRelIds(pres.slides).notesMaster}` })));
2200
+ const sldIdLst = el("p:sldIdLst", null, pres.slides.map((slide) => raw(voidEl("p:sldId", {
2201
+ id: slide._slideId,
2202
+ "r:id": `rId${slide._rId}`
2203
+ }))));
2204
+ const { width, height } = pres.presLayout;
2205
+ const sldSz = voidEl("p:sldSz", {
2206
+ cx: width,
2207
+ cy: height,
2208
+ type: slideSizeType(width, height)
2209
+ });
2210
+ const notesSz = voidEl("p:notesSz", {
2211
+ cx: pres.presLayout.height,
2212
+ cy: pres.presLayout.width
2213
+ });
2214
+ const fonts = pres.embeddedFonts || [];
2215
+ const flat = flattenEmbeddedFaces(fonts, presentationFontRelStart(pres.slides));
2216
+ const rIdOf = new Map(flat.map((face) => [`${face.fontIndex}:${face.slot}`, face.rId]));
2217
+ const embeddedFontLst = serializeEmbeddedFontLst(fonts, (fontIndex, slot) => rIdOf.get(`${fontIndex}:${slot}`));
2218
+ const defaultTextStyle = el("p:defaultTextStyle", null, LEVEL_MARGINS_EMU.map((marL, i) => raw(defaultTextStyleLevel(i + 1, marL))));
2219
+ const extLst = pres.sections && pres.sections.length > 0 ? sectionsExtLst(pres.sections) : "";
2220
+ return XML_DECL + "\r\n" + openTag + sldMasterIdLst + notesMasterIdLst + sldIdLst + sldSz + notesSz + embeddedFontLst + defaultTextStyle + extLst + "</p:presentation>";
2221
+ }
2222
+ /**
2223
+ * Create `ppt/presProps.xml`
2224
+ * @return {string} XML
2225
+ */
2226
+ function makeXmlPresProps() {
2227
+ return XML_DECL + "\r\n" + voidEl("p:presentationPr", { ...PML_ROOT_NS });
2228
+ }
2229
+ /**
2230
+ * Creates `ppt/viewProps.xml`
2231
+ * @return {string} XML
2232
+ */
2233
+ function makeXmlViewProps() {
2234
+ return XML_DECL + "\r\n" + el("p:viewPr", { ...PML_ROOT_NS }, [
2235
+ raw(el("p:normalViewPr", { horzBarState: "maximized" }, [raw(voidEl("p:restoredLeft", { sz: 15611 })), raw(voidEl("p:restoredTop", { sz: 94610 }))])),
2236
+ raw(el("p:slideViewPr", null, raw(el("p:cSldViewPr", {
2237
+ snapToGrid: 0,
2238
+ snapToObjects: 1
2239
+ }, [raw(el("p:cViewPr", { varScale: 1 }, [raw(el("p:scale", null, [raw(voidEl("a:sx", {
2240
+ n: 136,
2241
+ d: 100
2242
+ })), raw(voidEl("a:sy", {
2243
+ n: 136,
2244
+ d: 100
2245
+ }))])), raw(voidEl("p:origin", {
2246
+ x: 216,
2247
+ y: 312
2248
+ }))])), raw(voidEl("p:guideLst"))])))),
2249
+ raw(el("p:notesTextViewPr", null, raw(el("p:cViewPr", null, [raw(el("p:scale", null, [raw(voidEl("a:sx", {
2250
+ n: 1,
2251
+ d: 1
2252
+ })), raw(voidEl("a:sy", {
2253
+ n: 1,
2254
+ d: 1
2255
+ }))])), raw(voidEl("p:origin", {
2256
+ x: 0,
2257
+ y: 0
2258
+ }))])))),
2259
+ raw(voidEl("p:gridSpacing", {
2260
+ cx: 76200,
2261
+ cy: 76200
2262
+ }))
2263
+ ]);
2264
+ }
2265
+ //#endregion
2266
+ //#region src/gen/pres/table-styles.ts
2267
+ /**
2268
+ * Create `ppt/tableStyles.xml`
2269
+ * @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
2270
+ * @return {string} XML
2271
+ */
2272
+ function makeXmlTableStyles() {
2273
+ return XML_DECL + "\r\n" + voidEl("a:tblStyleLst", {
2274
+ "xmlns:a": OOXML_NS.a,
2275
+ def: "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"
2276
+ });
2277
+ }
2278
+ //#endregion
2279
+ //#region src/gen/slide/layout.ts
2280
+ /**
2281
+ * ts-pptx: slide-layout part
2282
+ *
2283
+ * Emit a slide layout (`ppt/slideLayouts/slideLayoutN.xml`) from a layout object.
2284
+ */
2285
+ /**
2286
+ * Generates the XML layout resource from a layout object
2287
+ * @param layout - slide layout (master)
2288
+ * @param renderers - which renderer emits each shape family, passed through to the shape walk
2289
+ * @return XML
2290
+ */
2291
+ function makeXmlLayout(layout, renderers) {
2292
+ return XML_DECL + el("p:sldLayout", {
2293
+ ...PML_ROOT_NS,
2294
+ preserve: "1"
2295
+ }, [raw(slideObjectToXml(layout, renderers)), raw(el("p:clrMapOvr", null, raw(voidEl("a:masterClrMapping"))))], {
2296
+ openPrefix: "\n ",
2297
+ childPrefix: "\n "
2298
+ });
2299
+ }
2300
+ //#endregion
2301
+ //#region src/gen/slide/master.ts
2302
+ /**
2303
+ * ts-pptx: slide-master parts
2304
+ *
2305
+ * The slide master (`slideMaster1.xml`) and its `.rels`, plus the master text
2306
+ * styles (`<p:txStyles>`): per-level default values mirroring the built-in
2307
+ * Office master, with any `MasterTextStyleProps` overrides layered on top.
2308
+ */
2309
+ const MASTER_TITLE_DEFAULT = {
2310
+ marL: 0,
2311
+ algn: "ctr",
2312
+ spcBefPct: 0,
2313
+ bu: "none",
2314
+ sz: 4400,
2315
+ font: "mj"
2316
+ };
2317
+ const MASTER_BODY_DEFAULTS = [
2318
+ {
2319
+ marL: 342900,
2320
+ indent: -342900,
2321
+ algn: "l",
2322
+ spcBefPct: 2e4,
2323
+ bu: {
2324
+ char: "•",
2325
+ font: "Arial"
2326
+ },
2327
+ sz: 3200,
2328
+ font: "mn"
2329
+ },
2330
+ {
2331
+ marL: 742950,
2332
+ indent: -285750,
2333
+ algn: "l",
2334
+ spcBefPct: 2e4,
2335
+ bu: {
2336
+ char: "–",
2337
+ font: "Arial"
2338
+ },
2339
+ sz: 2800,
2340
+ font: "mn"
2341
+ },
2342
+ {
2343
+ marL: 1143e3,
2344
+ indent: -228600,
2345
+ algn: "l",
2346
+ spcBefPct: 2e4,
2347
+ bu: {
2348
+ char: "•",
2349
+ font: "Arial"
2350
+ },
2351
+ sz: 2400,
2352
+ font: "mn"
2353
+ },
2354
+ {
2355
+ marL: 1600200,
2356
+ indent: -228600,
2357
+ algn: "l",
2358
+ spcBefPct: 2e4,
2359
+ bu: {
2360
+ char: "–",
2361
+ font: "Arial"
2362
+ },
2363
+ sz: 2e3,
2364
+ font: "mn"
2365
+ },
2366
+ {
2367
+ marL: 2057400,
2368
+ indent: -228600,
2369
+ algn: "l",
2370
+ spcBefPct: 2e4,
2371
+ bu: {
2372
+ char: "»",
2373
+ font: "Arial"
2374
+ },
2375
+ sz: 2e3,
2376
+ font: "mn"
2377
+ },
2378
+ {
2379
+ marL: 2514600,
2380
+ indent: -228600,
2381
+ algn: "l",
2382
+ spcBefPct: 2e4,
2383
+ bu: {
2384
+ char: "•",
2385
+ font: "Arial"
2386
+ },
2387
+ sz: 2e3,
2388
+ font: "mn"
2389
+ },
2390
+ {
2391
+ marL: 2971800,
2392
+ indent: -228600,
2393
+ algn: "l",
2394
+ spcBefPct: 2e4,
2395
+ bu: {
2396
+ char: "•",
2397
+ font: "Arial"
2398
+ },
2399
+ sz: 2e3,
2400
+ font: "mn"
2401
+ },
2402
+ {
2403
+ marL: 3429e3,
2404
+ indent: -228600,
2405
+ algn: "l",
2406
+ spcBefPct: 2e4,
2407
+ bu: {
2408
+ char: "•",
2409
+ font: "Arial"
2410
+ },
2411
+ sz: 2e3,
2412
+ font: "mn"
2413
+ },
2414
+ {
2415
+ marL: 3886200,
2416
+ indent: -228600,
2417
+ algn: "l",
2418
+ spcBefPct: 2e4,
2419
+ bu: {
2420
+ char: "•",
2421
+ font: "Arial"
2422
+ },
2423
+ sz: 2e3,
2424
+ font: "mn"
2425
+ }
2426
+ ];
2427
+ const MASTER_OTHER_DEFAULTS = LEVEL_MARGINS_EMU.map((marL) => ({
2428
+ marL,
2429
+ algn: "l",
2430
+ sz: 1800,
2431
+ font: "mn"
2432
+ }));
2433
+ function masterAlignAttr(align) {
2434
+ return align === void 0 ? "" : textAlignToken(align) ?? "";
2435
+ }
2436
+ /** Build the bullet element for a master level: caller override wins over the level default. */
2437
+ function masterBulletXml(bulletOverride, base) {
2438
+ if (bulletOverride === false) return voidEl("a:buNone");
2439
+ if (bulletOverride && typeof bulletOverride === "object") {
2440
+ const font = bulletOverride.fontFace ? voidEl("a:buFont", { typeface: bulletOverride.fontFace }) : "";
2441
+ if (bulletOverride.type === "number") {
2442
+ const startAt = typeof bulletOverride.numberStartAt === "number" ? bulletOverride.numberStartAt : void 0;
2443
+ return font + buAutoNumEl(bulletOverride.numberType || "arabicPeriod", startAt, "master textStyles bullet");
2444
+ }
2445
+ return (bulletOverride.fontFace ? font : voidEl("a:buFont", {
2446
+ typeface: "Arial",
2447
+ pitchFamily: 34,
2448
+ charset: 0
2449
+ })) + buCharEl(bulletOverride.characterCode, "•", "master textStyles bullet");
2450
+ }
2451
+ if (base === "none") return voidEl("a:buNone");
2452
+ if (base && typeof base === "object") return voidEl("a:buFont", {
2453
+ typeface: base.font,
2454
+ pitchFamily: 34,
2455
+ charset: 0
2456
+ }) + buCharEl(void 0, base.char, "master textStyles bullet");
2457
+ return "";
2458
+ }
2459
+ /**
2460
+ * One length-valued override on a master text-style level, in EMU, or the level's default when the
2461
+ * caller stated something we cannot use.
2462
+ *
2463
+ * `marginLeft` and `indent` used to reach their clamps only when they were finite numbers and fall
2464
+ * back in silence otherwise, so `NaN`, `Infinity` and a string such as `'0.5in'` all became the
2465
+ * Office default with nothing said -- while `fontSize` on the very same level warned for exactly
2466
+ * those inputs. Silent coercion of invalid input is a footgun: the deck comes out wrong and the
2467
+ * caller has nothing to go on.
2468
+ *
2469
+ * `undefined` is the caller stating nothing, which is not a problem and is not reported.
2470
+ * @param stated - the caller's value, in inches
2471
+ * @param option - the option's name, as `MasterTextStyleLevel` spells it
2472
+ * @param fallback - the level's default, already in EMU
2473
+ * @param resolve - the clamp that converts a usable value to EMU
2474
+ */
2475
+ function masterLevelLength(stated, option, fallback, resolve) {
2476
+ if (stated === void 0) return fallback;
2477
+ if (typeof stated === "number" && Number.isFinite(stated)) return resolve(stated);
2478
+ const kept = fallback === void 0 ? "the level default" : `${fallback / EMU_PER_INCH}in`;
2479
+ warn("master/invalid-text-style-length", `master textStyles ${option} "${String(stated)}" is invalid; keeping ${kept}.`);
2480
+ return fallback;
2481
+ }
2482
+ /** Serialize one `<a:lvlNpPr>` from its default, layering an optional caller override. */
2483
+ function masterLevelXml(levelNum, base, levelOverride = {}) {
2484
+ const marL = masterLevelLength(levelOverride.marginLeft, "marginLeft", base.marL, (inches) => clampParaMarginInchesEmu(inches, "master textStyles marginLeft"));
2485
+ const indentEmu = masterLevelLength(levelOverride.indent, "indent", base.indent, (inches) => clampParaIndentInchesEmu(inches, "master textStyles indent"));
2486
+ const algn = levelOverride.align && masterAlignAttr(levelOverride.align) || base.algn;
2487
+ let sz = base.sz;
2488
+ if (levelOverride.fontSize !== void 0) {
2489
+ if (typeof levelOverride.fontSize !== "number" || !Number.isFinite(levelOverride.fontSize) || levelOverride.fontSize <= 0) warn("master/invalid-text-style-font-size", `master textStyles fontSize "${String(levelOverride.fontSize)}" is invalid; keeping default ${base.sz / 100}pt.`);
2490
+ else sz = clampFontSizeSz(levelOverride.fontSize, "master textStyles fontSize");
2491
+ }
2492
+ rejectEmptyColor(levelOverride.color, "textStyles color");
2493
+ const colorXml = levelOverride.color ? createColorElement(levelOverride.color) : voidEl("a:schemeClr", { val: "tx1" });
2494
+ const latinXml = levelOverride.fontFace ? voidEl("a:latin", { typeface: levelOverride.fontFace }) : voidEl("a:latin", { typeface: `+${base.font}-lt` });
2495
+ return lvlPPr(levelNum, {
2496
+ marL,
2497
+ indent: indentEmu,
2498
+ algn
2499
+ }, [
2500
+ typeof base.spcBefPct === "number" ? raw(el("a:spcBef", null, raw(voidEl("a:spcPct", { val: base.spcBefPct })))) : null,
2501
+ raw(masterBulletXml(levelOverride.bullet, base.bu)),
2502
+ raw(themeFontDefRPr(base.font, {
2503
+ sz,
2504
+ b: xsdBoolIfTrue(levelOverride.bold),
2505
+ i: xsdBoolIfTrue(levelOverride.italic),
2506
+ kern: 1200
2507
+ }, colorXml, latinXml))
2508
+ ]);
2509
+ }
2510
+ /** Clamp a caller-provided per-level override array to the 9 valid list levels, warning on overflow. */
2511
+ function masterLevelOverrides(levels, group) {
2512
+ if (!Array.isArray(levels)) return [];
2513
+ if (levels.length > 9) warn("master/too-many-text-style-levels", `master textStyles.${group} has ${levels.length} levels; only the first 9 are used.`);
2514
+ return levels.slice(0, 9);
2515
+ }
2516
+ /**
2517
+ * Build the `<p:txStyles>` block from caller overrides layered onto the Office master defaults.
2518
+ * Only invoked when `defineSlideMaster({ textStyles })` was set; the unconfigured deck keeps the
2519
+ * verbatim default literal in `makeXmlMaster` for byte-identical output.
2520
+ */
2521
+ function makeXmlMasterTxStyles(textStyles) {
2522
+ const title = masterLevelXml(1, MASTER_TITLE_DEFAULT, textStyles.title);
2523
+ const bodyOverrides = masterLevelOverrides(textStyles.body, "body");
2524
+ const body = MASTER_BODY_DEFAULTS.map((base, i) => masterLevelXml(i + 1, base, bodyOverrides[i])).join("");
2525
+ const otherOverrides = masterLevelOverrides(textStyles.other, "other");
2526
+ const other = MASTER_OTHER_DEFAULTS.map((base, i) => masterLevelXml(i + 1, base, otherOverrides[i])).join("");
2527
+ return el("p:txStyles", null, [
2528
+ raw(el("p:titleStyle", null, raw(title))),
2529
+ raw(el("p:bodyStyle", null, raw(body))),
2530
+ raw(el("p:otherStyle", null, [raw(el("a:defPPr", null, raw(voidEl("a:defRPr", { lang: "en-US" })))), raw(other)]))
2531
+ ]);
2532
+ }
2533
+ /**
2534
+ * The built-in Office master's `<p:txStyles>`, used verbatim when `defineSlideMaster({ textStyles })`
2535
+ * was never called. Deliberately NOT built via `masterLevelXml`: a configured title level always
2536
+ * carries `marL="0"` (from `MASTER_TITLE_DEFAULT.marL`), but the true unconfigured default titleStyle
2537
+ * has no `marL`/`indent` attribute at all — a pre-existing asymmetry between the two paths, preserved
2538
+ * exactly rather than unified.
2539
+ */
2540
+ function makeXmlMasterDefaultTxStyles() {
2541
+ const defaultLevel = (n, attrs, bullet, sz, font, spcBefPct) => lvlPPr(n, attrs, [
2542
+ typeof spcBefPct === "number" ? raw(el("a:spcBef", null, raw(voidEl("a:spcPct", { val: spcBefPct })))) : null,
2543
+ raw(bullet),
2544
+ raw(themeFontDefRPr(font, {
2545
+ sz,
2546
+ kern: 1200
2547
+ }))
2548
+ ]);
2549
+ const title = defaultLevel(1, { algn: "ctr" }, voidEl("a:buNone"), 4400, "mj", 0);
2550
+ const bodyLevels = MASTER_BODY_DEFAULTS.map((base, i) => {
2551
+ const bu = base.bu;
2552
+ return defaultLevel(i + 1, {
2553
+ marL: base.marL,
2554
+ indent: base.indent,
2555
+ algn: base.algn
2556
+ }, voidEl("a:buFont", {
2557
+ typeface: bu.font,
2558
+ pitchFamily: 34,
2559
+ charset: 0
2560
+ }) + voidEl("a:buChar", { char: bu.char }), base.sz, "mn", base.spcBefPct);
2561
+ });
2562
+ const otherLevels = MASTER_OTHER_DEFAULTS.map((base, i) => defaultLevel(i + 1, {
2563
+ marL: base.marL,
2564
+ algn: base.algn
2565
+ }, "", base.sz, "mn"));
2566
+ return el("p:txStyles", null, [
2567
+ raw(el("p:titleStyle", null, raw(title), {
2568
+ childPrefix: " ",
2569
+ closePrefix: " "
2570
+ })),
2571
+ raw(el("p:bodyStyle", null, bodyLevels.map(raw), {
2572
+ childPrefix: " ",
2573
+ closePrefix: " "
2574
+ })),
2575
+ raw(el("p:otherStyle", null, [raw(el("a:defPPr", null, raw(voidEl("a:defRPr", { lang: "en-US" })))), ...otherLevels.map(raw)], {
2576
+ childPrefix: " ",
2577
+ closePrefix: " "
2578
+ }))
2579
+ ], { childPrefix: " " });
2580
+ }
2581
+ /**
2582
+ * Creates Slide Master 1 (`ppt/slideMasters/slideMaster1.xml`)
2583
+ * @param slide - slide object that represents master slide layout
2584
+ * @param layouts - slide layouts
2585
+ * @param renderers - which renderer emits each shape family, passed through to the shape walk
2586
+ * @return XML
2587
+ */
2588
+ function makeXmlMaster(slide, layouts, renderers) {
2589
+ const layoutRidStart = defaultRelIdStart(slide);
2590
+ const layoutDefs = layouts.map((_layoutDef, idx) => voidEl("p:sldLayoutId", {
2591
+ id: LAYOUT_IDX_SERIES_BASE + idx,
2592
+ "r:id": `rId${layoutRidStart + idx}`
2593
+ })).join("");
2594
+ const clrMap = voidEl("p:clrMap", DEFAULT_COLOR_MAP);
2595
+ const hf = voidEl("p:hf", {
2596
+ sldNum: slide._slideNumberProps ? null : 0,
2597
+ hdr: 0,
2598
+ ftr: 0,
2599
+ dt: 0
2600
+ });
2601
+ const txStyles = slide._txStyles ? makeXmlMasterTxStyles(slide._txStyles) : makeXmlMasterDefaultTxStyles();
2602
+ return XML_DECL + "\r\n" + el("p:sldMaster", { ...PML_ROOT_NS }, [
2603
+ raw(slideObjectToXml(slide, renderers)),
2604
+ raw(clrMap),
2605
+ raw(el("p:sldLayoutIdLst", null, raw(layoutDefs))),
2606
+ raw(hf),
2607
+ raw(txStyles)
2608
+ ]);
2609
+ }
2610
+ /**
2611
+ * Creates `ppt/slideMasters/_rels/slideMaster1.xml.rels`
2612
+ * @param {SlideMasterInternal} masterSlide - the deck's slide master
2613
+ * @param {SlideLayoutInternal[]} slideLayouts - Slide Layouts
2614
+ * @return {string} XML
2615
+ */
2616
+ function makeXmlMasterRel(masterSlide, slideLayouts) {
2617
+ const defaultRels = slideLayouts.map((_layoutDef, idx) => ({
2618
+ target: targetFromPptSubpart(slideLayoutPath(idx + 1)),
2619
+ type: SLIDE_LAYOUT_REL
2620
+ }));
2621
+ defaultRels.push({
2622
+ target: "../theme/theme1.xml",
2623
+ type: THEME_REL
2624
+ });
2625
+ return slideObjectRelationsToXml(masterSlide, defaultRels, SLIDE_MASTER_PATH);
2626
+ }
2627
+ //#endregion
2628
+ //#region src/package/parts/shared.ts
2629
+ /**
2630
+ * The contributors in emission order. Sorting here rather than trusting the caller's array is the
2631
+ * whole point of {@link PartContributor.order}; the sort is stable, so two families that share a
2632
+ * rank keep their listed order rather than swapping unpredictably.
2633
+ */
2634
+ function orderedContributors(contributors) {
2635
+ return [...contributors].sort((a, b) => a.order - b.order);
2636
+ }
2637
+ /**
2638
+ * Ask every contributor for its content-type entries and flatten them into the slot-keyed data
2639
+ * `makeXmlContTypes` emits. Takes the contributors already ordered — within a slot the entries
2640
+ * come out in the order they are collected here.
2641
+ */
2642
+ function collectContentTypes(contributors, pres) {
2643
+ const defaults = [];
2644
+ const presentation = [];
2645
+ const trailing = [];
2646
+ const perSlide = pres.slides.map(() => []);
2647
+ const perLayout = pres.slideLayouts.map(() => []);
2648
+ for (const contributor of contributors) {
2649
+ const hooks = contributor.contentTypes;
2650
+ if (!hooks) continue;
2651
+ if (hooks.defaults) defaults.push(...hooks.defaults(pres));
2652
+ if (hooks.presentation) presentation.push(...hooks.presentation(pres));
2653
+ if (hooks.perSlide) {
2654
+ const perSlideHook = hooks.perSlide;
2655
+ pres.slides.forEach((slide, idx) => perSlide[idx]?.push(...perSlideHook(slide, idx + 1)));
2656
+ }
2657
+ if (hooks.perLayout) {
2658
+ const perLayoutHook = hooks.perLayout;
2659
+ pres.slideLayouts.forEach((layout, idx) => perLayout[idx]?.push(...perLayoutHook(layout, idx + 1)));
2660
+ }
2661
+ if (hooks.trailing) trailing.push(...hooks.trailing(pres));
2662
+ }
2663
+ return {
2664
+ defaults,
2665
+ presentation,
2666
+ perSlide,
2667
+ perLayout,
2668
+ trailing
2669
+ };
2670
+ }
2671
+ //#endregion
2672
+ //#region src/package/assemble.ts
2673
+ /**
2674
+ * Package assembly: turn an authored presentation's internal state into the full set of
2675
+ * OOXML package parts and hand the bytes to the ZIP writer. This is the write-side
2676
+ * "packaging" layer — `[Content_Types].xml`, the `_rels` graph, docProps, theme, the
2677
+ * per-slide/layout/master parts, and media rels — split out of the authoring class
2678
+ * (`TsPptx`) so that class stays a façade over slide authoring. The parts a construct
2679
+ * family adds on top of that skeleton — charts, comments, notes — come from the part
2680
+ * contributors it is handed, so nothing here names a family.
2681
+ *
2682
+ * The entry point `writePackage` takes a structural {@link PackageSource} that the
2683
+ * authoring class satisfies; it does not depend on the class itself, so the same pipeline
2684
+ * can be driven from any assembled deck state.
2685
+ */
2686
+ /**
2687
+ * Media extensions whose bytes are already entropy-coded, so running the ZIP's
2688
+ * DEFLATE pass over them costs CPU for a negligible size gain. For these we set
2689
+ * the per-entry ZIP compression to STORE while leaving XML parts on DEFLATE.
2690
+ * In image/video-heavy decks media dominates the byte count, so this is the
2691
+ * dominant cost when writing large presentations.
2692
+ * Formats that genuinely benefit from DEFLATE (bmp, wav, tiff, emf, wmf, svg)
2693
+ * are deliberately excluded so they keep inheriting the global compression.
2694
+ */
2695
+ const ALREADY_COMPRESSED_MEDIA_EXTN = /* @__PURE__ */ new Set([
2696
+ "jpg",
2697
+ "jpeg",
2698
+ "png",
2699
+ "gif",
2700
+ "webp",
2701
+ "heic",
2702
+ "heif",
2703
+ "avif",
2704
+ "mp4",
2705
+ "m4v",
2706
+ "mov",
2707
+ "avi",
2708
+ "mpg",
2709
+ "mpeg",
2710
+ "wmv",
2711
+ "webm",
2712
+ "mkv",
2713
+ "mp3",
2714
+ "m4a",
2715
+ "aac",
2716
+ "ogg",
2717
+ "oga"
2718
+ ]);
2719
+ /**
2720
+ * Extensions whose payload is itself a ZIP archive — the embedded OPC packages an OLE object can
2721
+ * carry (`addOleObject`). Deflating a zip inside a zip buys nothing, so these are STOREd like the
2722
+ * already-compressed media above. No media/image rel ever uses one of these extensions, so decks
2723
+ * without an OLE object are unaffected.
2724
+ */
2725
+ const ZIP_CONTAINER_EXTN = /* @__PURE__ */ new Set([
2726
+ "xlsx",
2727
+ "xlsm",
2728
+ "docx",
2729
+ "docm",
2730
+ "pptx",
2731
+ "pptm"
2732
+ ]);
2733
+ /**
2734
+ * Each slide's registered transition sound.
2735
+ *
2736
+ * Kept per slide, never on the transition: one transition object can be assigned to several slides,
2737
+ * and a relationship id means something only inside the slide part that declares it. Kept across
2738
+ * writes as well, so a deck written twice registers each sound once. An entry is reused only while
2739
+ * the slide still holds its relationship and still has the same sound assigned.
2740
+ */
2741
+ const registeredTransitionSounds = /* @__PURE__ */ new WeakMap();
2742
+ /**
2743
+ * Register an audio media part + relationship for each slide-transition start sound
2744
+ * (`transition.sound` with `data`/`path`), and return the relationship id each slide's
2745
+ * `p:sndAc/p:snd r:embed` refers to. Runs before media encoding so the bytes are loaded. Re-export
2746
+ * is safe: a slide whose sound is already registered reuses that relationship. The stop-previous
2747
+ * form (`sound.stopPrevious`) needs no part and is skipped.
2748
+ *
2749
+ * Registered here, at write time, rather than when `slide.transition` is assigned. A slide's rel
2750
+ * ids and media part names are handed out in registration order, and a transition is often set
2751
+ * before the slide's pictures and media are added; registering the sound then would move every
2752
+ * later rel id and part name on the slide.
2753
+ */
2754
+ function registerTransitionSounds(slides) {
2755
+ const soundRIds = /* @__PURE__ */ new Map();
2756
+ slides.forEach((slide) => {
2757
+ const transition = slide.transition;
2758
+ if (!transition || !TRANSITION_TYPES.includes(transition.type)) return;
2759
+ const sound = transition.sound;
2760
+ if (!sound || sound.stopPrevious) return;
2761
+ if (!sound.data && !sound.path) return;
2762
+ const registered = registeredTransitionSounds.get(slide);
2763
+ if (registered && slide._relsMedia.includes(registered.rel) && registered.data === sound.data && registered.path === sound.path) {
2764
+ soundRIds.set(slide, registered.rel.rId);
2765
+ return;
2766
+ }
2767
+ const dataMime = dataUriMediaType(sound.data ?? "");
2768
+ const pathFile = sound.path ? (sound.path.split("/").pop() ?? "").split("?")[0] ?? "" : "";
2769
+ const extn = dataMime?.startsWith("audio/") ? audioExtensionForSubtype(dataMime.slice(6)) : (pathFile.split(".").pop() ?? "wav").toLowerCase();
2770
+ const rId = getNewRelId(slide);
2771
+ const rel = pushMediaRel(slide, {
2772
+ kind: "audio",
2773
+ extn,
2774
+ type: `audio/${extn}`,
2775
+ path: sound.path,
2776
+ data: sound.data,
2777
+ rId
2778
+ });
2779
+ registeredTransitionSounds.set(slide, {
2780
+ rel,
2781
+ data: sound.data,
2782
+ path: sound.path
2783
+ });
2784
+ soundRIds.set(slide, rId);
2785
+ });
2786
+ return soundRIds;
2787
+ }
2788
+ /**
2789
+ * Write the media parts one target's rels point at. Images are how every deck carries a picture,
2790
+ * so this stays on the core path; the chart half of what used to be one function is a part
2791
+ * contributor (`parts/chart.ts`) and runs just before it, per target, so part order is unchanged.
2792
+ *
2793
+ * After media de-duplication many relationships share one `Target`, across targets as well as
2794
+ * within one: a logo on every slide is one part. Each used to be decoded and added once per
2795
+ * relationship. A path is now written the first time it decodes, and skipped after that. The zip
2796
+ * keys entries by path and a re-added key keeps its first position, and relationships sharing a
2797
+ * `Target` share its data and extension, so skipping cannot change a byte.
2798
+ * @param {PartTarget} slide - slide, layout or master carrying the rels
2799
+ * @param {ZipWriter} zip - zip writer
2800
+ * @param {Set<string>} written - the media paths already added, shared across every target of one package
2801
+ */
2802
+ function createMediaParts(slide, zip, written) {
2803
+ slide._relsMedia.forEach((rel) => {
2804
+ if (rel.type !== "online" && rel.type !== "hyperlink") {
2805
+ const path = rel.Target.replace("..", "ppt");
2806
+ if (written.has(path)) return;
2807
+ const data = rel.data && typeof rel.data === "string" ? rel.data : "";
2808
+ const bytes = decodeBase64ToBytes(data);
2809
+ if (!bytes) return;
2810
+ const extn = (rel.extn || rel.Target.split(".").pop() || "").toLowerCase();
2811
+ zip.add(path, bytes, { store: ALREADY_COMPRESSED_MEDIA_EXTN.has(extn) || ZIP_CONTAINER_EXTN.has(extn) });
2812
+ written.add(path);
2813
+ }
2814
+ });
2815
+ }
2816
+ /**
2817
+ * Assemble every package part for `source` and return them in emission order, without zipping.
2818
+ * This runs the transition-sound registration, placeholder backfill, media encode, cross-deck
2819
+ * media de-dup, chart-part-id assignment, and measured-fit passes before the synchronous
2820
+ * XML pass reads slide state. The bytes are the same the ZIP writer would compress; splitting the
2821
+ * assembly from the zip lets the byte-identity harness (and a future parts API) read parts
2822
+ * directly. Only `onMediaError` is meaningful here — compression/output shape are zip concerns
2823
+ * handled by {@link zipPackageParts}.
2824
+ */
2825
+ async function buildPackageParts(source, props) {
2826
+ const pres = source.presentation;
2827
+ const partPromises = [];
2828
+ const contributors = orderedContributors(source.partContributors);
2829
+ const zip = new ZipWriter();
2830
+ requireSlideLinksInDeck(pres.slides, pres.slideLayouts, pres.masterSlide);
2831
+ const transitionSoundRIds = registerTransitionSounds(pres.slides);
2832
+ backfillPlaceholders(pres.slides);
2833
+ const onMediaError = props.onMediaError ?? "throw";
2834
+ const mediaTargets = [
2835
+ ...pres.slides,
2836
+ ...pres.slideLayouts,
2837
+ pres.masterSlide
2838
+ ];
2839
+ return await encodeMediaForTargets(mediaTargets, source.runtime, onMediaError).then(async () => {
2840
+ const canonicalMediaTargets = /* @__PURE__ */ new Map();
2841
+ for (const target of mediaTargets) for (const rel of target._relsMedia || []) {
2842
+ if (rel.type === "online" || rel.type === "hyperlink" || typeof rel.data !== "string" || !rel.data) continue;
2843
+ if (rel.oleRelType) continue;
2844
+ const key = (rel.extn || "") + "\0" + rel.data;
2845
+ const canonical = canonicalMediaTargets.get(key);
2846
+ if (canonical) rel.Target = canonical;
2847
+ else canonicalMediaTargets.set(key, rel.Target);
2848
+ }
2849
+ let chartPartIdx = 0;
2850
+ for (const target of mediaTargets) for (const rel of target._relsChart || []) {
2851
+ const chartId = ++chartPartIdx;
2852
+ rel.globalId = chartId;
2853
+ const { fileName, target } = chartPartNames(chartId, rel.isChartEx);
2854
+ rel.fileName = fileName;
2855
+ rel.Target = target;
2856
+ }
2857
+ const restoreAuthoredFit = bakeMeasuredFit(pres.slides, source.fontMetrics);
2858
+ try {
2859
+ const hasCustomProps = source.customProperties.length > 0;
2860
+ zip.add("[Content_Types].xml", makeXmlContTypes({
2861
+ slides: pres.slides,
2862
+ slideLayouts: pres.slideLayouts,
2863
+ masterSlide: pres.masterSlide,
2864
+ hasCustomProps,
2865
+ embeddedFonts: pres.embeddedFonts,
2866
+ contributions: collectContentTypes(contributors, pres)
2867
+ }));
2868
+ zip.add("_rels/.rels", makeXmlRootRels(hasCustomProps));
2869
+ zip.add("docProps/app.xml", makeXmlApp(pres.slides, pres.company, pres.presLayout));
2870
+ zip.add("docProps/core.xml", makeXmlCore(pres.title, pres.subject, pres.author, pres.revision));
2871
+ if (hasCustomProps) zip.add("docProps/custom.xml", makeXmlCustomProperties(source.customProperties));
2872
+ zip.add(relsPath(PRESENTATION_PATH), makeXmlPresentationRels(pres.slides, pres.embeddedFonts));
2873
+ for (const face of flattenEmbeddedFaces(pres.embeddedFonts, 1)) zip.add(fontPath(face.partIndex), face.bytes, { store: true });
2874
+ const themeXml = makeXmlTheme(pres);
2875
+ zip.add("ppt/theme/theme1.xml", themeXml);
2876
+ zip.add("ppt/theme/theme2.xml", themeXml);
2877
+ zip.add(PRESENTATION_PATH, makeXmlPresentation(pres));
2878
+ zip.add("ppt/presProps.xml", makeXmlPresProps());
2879
+ zip.add("ppt/tableStyles.xml", makeXmlTableStyles());
2880
+ zip.add("ppt/viewProps.xml", makeXmlViewProps());
2881
+ pres.slideLayouts.forEach((layout, idx) => {
2882
+ zip.add(slideLayoutPath(idx + 1), makeXmlLayout(layout, source.renderers));
2883
+ zip.add(relsPath(slideLayoutPath(idx + 1)), makeXmlSlideLayoutRel(idx + 1, pres.slideLayouts));
2884
+ });
2885
+ pres.slides.forEach((slide, idx) => {
2886
+ zip.add(slidePath(idx + 1), makeXmlSlide(slide, source.renderers, transitionSoundRIds.get(slide)));
2887
+ zip.add(relsPath(slidePath(idx + 1)), makeXmlSlideRel(pres.slides, pres.slideLayouts, idx + 1));
2888
+ contributors.forEach((contributor) => contributor.parts?.withEachSlide?.(slide, idx + 1, zip));
2889
+ });
2890
+ zip.add(SLIDE_MASTER_PATH, makeXmlMaster(pres.masterSlide, pres.slideLayouts, source.renderers));
2891
+ zip.add(relsPath(SLIDE_MASTER_PATH), makeXmlMasterRel(pres.masterSlide, pres.slideLayouts));
2892
+ contributors.forEach((contributor) => contributor.parts?.afterMaster?.(pres, zip));
2893
+ const writtenMediaPaths = /* @__PURE__ */ new Set();
2894
+ const addTargetRelParts = (target) => {
2895
+ contributors.forEach((contributor) => {
2896
+ const pending = contributor.parts?.fromTargetRels?.(target, zip);
2897
+ if (pending) partPromises.push(...pending);
2898
+ });
2899
+ createMediaParts(target, zip, writtenMediaPaths);
2900
+ };
2901
+ pres.slideLayouts.forEach(addTargetRelParts);
2902
+ pres.slides.forEach(addTargetRelParts);
2903
+ addTargetRelParts(pres.masterSlide);
2904
+ } finally {
2905
+ restoreAuthoredFit();
2906
+ }
2907
+ return await Promise.all(partPromises).then(() => zip.entries());
2908
+ });
2909
+ }
2910
+ /**
2911
+ * Zip an ordered list of package parts into the output shape `props.outputType` selects. Re-adds
2912
+ * each part to a fresh {@link ZipWriter} in order — preserving each part's `store` hint — so the
2913
+ * archive is byte-identical to assembling straight into one writer. Compression and output type
2914
+ * are the only zip-level knobs; part assembly happened in {@link buildPackageParts}.
2915
+ */
2916
+ async function zipPackageParts(parts, props) {
2917
+ const zip = new ZipWriter();
2918
+ for (const part of parts) zip.add(part.path, part.data, { store: part.store });
2919
+ const compression = props.compression !== false;
2920
+ if (props.outputType) return await zip.generate(props.outputType, { compression });
2921
+ else return await zip.generate("blob", { compression });
2922
+ }
2923
+ /**
2924
+ * Assemble every package part for `source` and zip it into the output shape `props.outputType`
2925
+ * selects. Thin composition of {@link buildPackageParts} (the assembly pipeline) and
2926
+ * {@link zipPackageParts} (the zip pass), kept as the stable entry point the authoring class calls.
2927
+ */
2928
+ async function writePackage(source, props) {
2929
+ return await zipPackageParts(await buildPackageParts(source, props), props);
2930
+ }
2931
+ //#endregion
2932
+ //#region src/presentation.ts
2933
+ /**
2934
+ * ts-pptx: the presentation core
2935
+ *
2936
+ * Home of {@link PresentationCore}, the class every entry point subclasses with a runtime
2937
+ * adapter (`index.ts`, `node.ts`, `browser.ts`). See the class doc below for the layout of
2938
+ * its body and where package assembly actually happens.
2939
+ *
2940
+ * Units: this library takes user coordinates in inches and converts to EMU on output — see
2941
+ * `units.ts` for the conversions and `STANDARD_LAYOUTS` for the built-in slide sizes.
2942
+ *
2943
+ * Derived from PptxGenJS (C) 2015-present Brent Ely (MIT), which in turn took some code from
2944
+ * the OfficeGen project (Copyright 2013 Ziv Barber). This project is likewise MIT; the full
2945
+ * license text is in `LICENSE` at the repository root.
2946
+ */
2947
+ const VERSION = "4.0.1";
2948
+ function standardLayoutToPresLayout(layout) {
2949
+ return {
2950
+ name: layout.name,
2951
+ width: layout.widthEmu,
2952
+ height: layout.heightEmu
2953
+ };
2954
+ }
2955
+ /**
2956
+ * One side of a `defineLayout` definition, in inches, clamped to what `ST_SlideSizeCoordinate`
2957
+ * allows.
2958
+ *
2959
+ * `p:sldSz/@cx` and `@cy` are bounded 914400 to 51206400 EMU -- 1 to 56 inches -- and nothing
2960
+ * checked. Every arm of `defineLayout`'s guard chain is a `warn`, so
2961
+ * `{ name: 'Badge', width: 0.5, height: 0.5 }` produced no diagnostic at all (both values are
2962
+ * truthy and finite) and emitted a `sldSz` PowerPoint offers to repair; `width: -5` is truthy
2963
+ * too, so it reached the file as `cx="-4572000"`.
2964
+ *
2965
+ * `Number()` first, because the guard chain above deliberately accepts a numeric string as
2966
+ * advice rather than as an error, and `clampRangedInput` takes a number. A value with nothing
2967
+ * to coerce from is `NaN`, which it throws on -- as `coord/non-finite`, the code the unit
2968
+ * conversion used to raise one line later.
2969
+ * @param value - the caller's `width` or `height`
2970
+ * @param side - which one, for the diagnostic
2971
+ */
2972
+ function layoutSideInches(value, side) {
2973
+ return clampRangedInput(Number(value), 1, 56, "layout/size-out-of-range", `defineLayout ${side} (inches)`, "coord/non-finite");
2974
+ }
2975
+ /**
2976
+ * Main presentation class and package export flow — the public entry point consumers
2977
+ * instantiate (`new TsPptx()`). Owns presentation-level state and metadata, collects
2978
+ * slides, and drives `write`/`writeFile`/`stream`. The actual OOXML string building is
2979
+ * delegated to the `gen-*` modules; runtime file/stream output goes through the injected
2980
+ * `RuntimeAdapter`. Enums (`AlignH`, `ChartType`, `ShapeType`, …) are imported from the
2981
+ * package entry, not read off the instance.
2982
+ *
2983
+ * Rough layout of the class body:
2984
+ * - Metadata accessors layout / author / company / title / theme / sections getters+setters
2985
+ * - Private slide helpers addNewSlide, getSlide, setSlideNumber
2986
+ * - Public export methods stream / write / writeFile — delegate packaging to `writePackage`
2987
+ * - Public authoring methods addSlide / defineLayout / defineSlideMaster / defineTableStyle
2988
+ *
2989
+ * Package assembly (`[Content_Types].xml`, the rels graph, per-part XML, the ZIP) lives in
2990
+ * `package/assemble.ts`; this class provides the deck state via {@link PresentationCore.packageSource}.
2991
+ * The live-DOM `tableToSlides()` is NOT here — it is added by the browser entry subclass
2992
+ * (`browser.ts`) so it stays out of the Node build and out of the core chunk.
2993
+ */
2994
+ var PresentationCore = class {
2995
+ /**
2996
+ * Presentation layout name
2997
+ * Standard layouts:
2998
+ * - 'LAYOUT_4x3' (10" x 7.5")
2999
+ * - 'LAYOUT_16x9' (10" x 5.625")
3000
+ * - 'LAYOUT_16x10' (10" x 6.25")
3001
+ * - 'LAYOUT_WIDE' (13.333" x 7.5")
3002
+ * Custom layouts:
3003
+ * Use `pptx.defineLayout()` to create custom layouts (e.g.: 'A4')
3004
+ * @type {string}
3005
+ * @see https://support.office.com/en-us/article/Change-the-size-of-your-slides-040a811c-be43-40b9-8d04-0de5ed79987e
3006
+ */
3007
+ _layout = DEF_PRES_LAYOUT;
3008
+ set layout(value) {
3009
+ const layoutKey = typeof value === "string" ? value : value?.layout;
3010
+ const newLayout = layoutKey ? this.LAYOUTS[layoutKey] : void 0;
3011
+ if (newLayout) {
3012
+ this._layout = layoutKey;
3013
+ this._presLayout = newLayout;
3014
+ } else throw new InvalidOptionError("layout/unknown", `Unknown layout ${JSON.stringify(layoutKey)}; pass a registered layout name (see LAYOUTS) or a STANDARD_LAYOUTS preset`);
3015
+ }
3016
+ get layout() {
3017
+ return this._layout;
3018
+ }
3019
+ /**
3020
+ * ts-pptx Library Version
3021
+ */
3022
+ _version = VERSION;
3023
+ get version() {
3024
+ return this._version;
3025
+ }
3026
+ /**
3027
+ * @type {string}
3028
+ */
3029
+ _author;
3030
+ set author(value) {
3031
+ this._author = value;
3032
+ }
3033
+ get author() {
3034
+ return this._author;
3035
+ }
3036
+ /**
3037
+ * @type {string}
3038
+ */
3039
+ _company;
3040
+ set company(value) {
3041
+ this._company = value;
3042
+ }
3043
+ get company() {
3044
+ return this._company;
3045
+ }
3046
+ /**
3047
+ * @type {string}
3048
+ * @note the `revision` value must be a whole number only (without "." or "," - otherwise, PPT will throw errors upon opening!)
3049
+ */
3050
+ _revision;
3051
+ set revision(value) {
3052
+ this._revision = value;
3053
+ }
3054
+ get revision() {
3055
+ return this._revision;
3056
+ }
3057
+ /**
3058
+ * @type {string}
3059
+ */
3060
+ _subject;
3061
+ set subject(value) {
3062
+ this._subject = value;
3063
+ }
3064
+ get subject() {
3065
+ return this._subject;
3066
+ }
3067
+ /**
3068
+ * @type {ThemeProps}
3069
+ */
3070
+ _theme = {};
3071
+ set theme(value) {
3072
+ this._theme = value;
3073
+ }
3074
+ get theme() {
3075
+ return this._theme;
3076
+ }
3077
+ /**
3078
+ * @type {string}
3079
+ */
3080
+ _title;
3081
+ set title(value) {
3082
+ this._title = value;
3083
+ }
3084
+ get title() {
3085
+ return this._title;
3086
+ }
3087
+ /**
3088
+ * Slide number shown on the first slide (maps to firstSlideNum in presentation.xml)
3089
+ * @throws {InvalidOptionError} when the value is not an integer `xsd:int` can hold
3090
+ */
3091
+ _firstSlideNum;
3092
+ set firstSlideNum(value) {
3093
+ if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) throw new InvalidOptionError("presentation/first-slide-num-not-an-integer", `firstSlideNum must be an integer, got ${String(value)}`);
3094
+ this._firstSlideNum = value;
3095
+ }
3096
+ get firstSlideNum() {
3097
+ return this._firstSlideNum;
3098
+ }
3099
+ /**
3100
+ * Whether Right-to-Left (RTL) mode is enabled
3101
+ * @type {boolean}
3102
+ */
3103
+ _rtlMode;
3104
+ set rtlMode(value) {
3105
+ this._rtlMode = value;
3106
+ }
3107
+ get rtlMode() {
3108
+ return this._rtlMode;
3109
+ }
3110
+ /** the slide master the deck's master part is written from; not a `Slide`, so not handed out */
3111
+ _masterSlide;
3112
+ /** this Presentation's Slide objects */
3113
+ _slides;
3114
+ get slides() {
3115
+ return this._slides;
3116
+ }
3117
+ /** this Presentation's sections */
3118
+ _sections;
3119
+ get sections() {
3120
+ return this._sections;
3121
+ }
3122
+ /** custom document properties stored in docProps/custom.xml */
3123
+ _customProperties;
3124
+ /**
3125
+ * slide layout definition objects, used for generating slide layout files.
3126
+ * `protected` (not `private`) so the browser entry subclass can resolve
3127
+ * `tableToSlides({ masterTitle })` against them — that method lives on the
3128
+ * browser build only (it reads a live DOM), see `browser.ts`.
3129
+ */
3130
+ _slideLayouts;
3131
+ get slideLayouts() {
3132
+ return this._slideLayouts;
3133
+ }
3134
+ get internalPresentation() {
3135
+ return {
3136
+ author: this.author,
3137
+ company: this.company,
3138
+ firstSlideNum: this.firstSlideNum,
3139
+ layout: this.layout,
3140
+ masterSlide: this._masterSlide,
3141
+ presLayout: this.presLayout,
3142
+ revision: this.revision,
3143
+ rtlMode: this.rtlMode,
3144
+ sections: this._sections,
3145
+ slideLayouts: this._slideLayouts,
3146
+ slides: this._slides,
3147
+ subject: this.subject,
3148
+ theme: this.theme,
3149
+ title: this.title,
3150
+ embeddedFonts: this._embeddedFonts
3151
+ };
3152
+ }
3153
+ LAYOUTS;
3154
+ /**
3155
+ * The built-in default layout, or a throw. `LAYOUTS` is a string-keyed map, so the lookup is
3156
+ * typed as possibly-missing at every call site even though the constructor registers it — and
3157
+ * the two callers that need it were spelling the same guard and the same message.
3158
+ */
3159
+ #requireDefaultLayout() {
3160
+ const defLayout = this.LAYOUTS[DEF_PRES_LAYOUT];
3161
+ if (!defLayout) throw new InternalError("layout/default-not-registered", `Default presentation layout "${DEF_PRES_LAYOUT}" is not registered`);
3162
+ return defLayout;
3163
+ }
3164
+ _presLayout;
3165
+ get presLayout() {
3166
+ return this._presLayout;
3167
+ }
3168
+ _runtime;
3169
+ /** The construct families this presentation was composed with, flattened per seam. */
3170
+ #composition;
3171
+ /**
3172
+ * What a family's presentation-level author is handed. Assembled once: it carries the font
3173
+ * metrics registry, which is private to this class and therefore unreachable from a family
3174
+ * through the instance alone.
3175
+ */
3176
+ #authorContext;
3177
+ /** Write-side font metrics for measured text fit (`fit:'shrink'`). @see registerFontMetrics */
3178
+ _fontMetrics = new FontMetricsRegistry();
3179
+ /** Author-side embedded font faces, accumulated by {@link embedFont} and emitted at write time. */
3180
+ _embeddedFonts = [];
3181
+ constructor(runtime, families) {
3182
+ this._runtime = runtime;
3183
+ this.#composition = composeFamilies(families);
3184
+ this.#authorContext = {
3185
+ pres: this,
3186
+ fontMetrics: this._fontMetrics
3187
+ };
3188
+ this.LAYOUTS = {
3189
+ LAYOUT_4x3: standardLayoutToPresLayout(STANDARD_LAYOUTS.LAYOUT_4x3),
3190
+ LAYOUT_16x9: standardLayoutToPresLayout(STANDARD_LAYOUTS.LAYOUT_16x9),
3191
+ LAYOUT_16x10: standardLayoutToPresLayout(STANDARD_LAYOUTS.LAYOUT_16x10),
3192
+ LAYOUT_WIDE: standardLayoutToPresLayout(STANDARD_LAYOUTS.LAYOUT_WIDE)
3193
+ };
3194
+ this._author = "ts-pptx";
3195
+ this._company = "ts-pptx";
3196
+ this._revision = "1";
3197
+ this._subject = "ts-pptx Presentation";
3198
+ this._title = "ts-pptx Presentation";
3199
+ const defLayout = this.#requireDefaultLayout();
3200
+ this._presLayout = {
3201
+ name: defLayout.name,
3202
+ width: defLayout.width,
3203
+ height: defLayout.height
3204
+ };
3205
+ this._firstSlideNum = 1;
3206
+ this._rtlMode = false;
3207
+ this._slideLayouts = [{
3208
+ _margin: DEF_SLIDE_MARGIN_IN,
3209
+ _name: DEF_PRES_LAYOUT_NAME,
3210
+ _presLayout: this._presLayout,
3211
+ _rels: [],
3212
+ _relsChart: [],
3213
+ _relsMedia: [],
3214
+ _slide: null,
3215
+ _slideNum: 1e3,
3216
+ _slideNumberProps: null,
3217
+ _slideObjects: []
3218
+ }];
3219
+ this._slides = [];
3220
+ this._sections = [];
3221
+ this._customProperties = [];
3222
+ this._masterSlide = {
3223
+ _presLayout: this._presLayout,
3224
+ _rels: [],
3225
+ _relsChart: [],
3226
+ _relsMedia: [],
3227
+ _slideNum: null,
3228
+ _slideNumberProps: null,
3229
+ _slideObjects: []
3230
+ };
3231
+ }
3232
+ /**
3233
+ * Provides an API for `addTableDefinition` to create slides as needed for auto-paging
3234
+ * @param layout - the layout of the slide the table is paged from
3235
+ * @return {Slide} new Slide
3236
+ */
3237
+ addNewSlide = (layout) => {
3238
+ const nextOptions = {};
3239
+ if (layout && this._slideLayouts.includes(layout)) setOrClear(nextOptions, "masterTitle", layout._name);
3240
+ const lastSlide = this._slides[this._slides.length - 1];
3241
+ const sourceSection = this._sections.find((sect) => sect._slides.some((s) => s._slideNum === lastSlide?._slideNum));
3242
+ setOrClear(nextOptions, "sectionTitle", sourceSection?.title);
3243
+ return this.addSlide(nextOptions);
3244
+ };
3245
+ /**
3246
+ * Provides an API for `addTableDefinition` to get slide reference by number
3247
+ * @param {number} slideNum - slide number
3248
+ * @return {Slide} Slide
3249
+ */
3250
+ getSlide = (slideNum) => this._slides.find((slide) => slide._slideNum === slideNum);
3251
+ /**
3252
+ * Provides the live section list to the `Slide` class so Section/Summary Zoom can resolve
3253
+ * a section title to its stable GUID and enumerate sections in order.
3254
+ * @return {SectionInternalProps[]} the presentation's sections
3255
+ */
3256
+ getSections = () => this._sections;
3257
+ /**
3258
+ * Enables the `Slide` class to set ts-pptx [Presentation] master/layout slidenumbers
3259
+ * @param {SlideNumberProps} slideNum - slide number config
3260
+ */
3261
+ setSlideNumber = (slideNum) => {
3262
+ this._masterSlide._slideNumberProps = slideNum;
3263
+ const defLayout = this._slideLayouts.find((layout) => layout._name === DEF_PRES_LAYOUT_NAME);
3264
+ if (defLayout) defLayout._slideNumberProps = slideNum;
3265
+ };
3266
+ /**
3267
+ * Assemble the slice of internal state the packaging layer reads. `PresentationCore` owns the
3268
+ * authored deck; `writePackage` (in `package/assemble.ts`) turns it into OOXML parts.
3269
+ */
3270
+ packageSource() {
3271
+ return {
3272
+ runtime: this._runtime,
3273
+ presentation: this.internalPresentation,
3274
+ customProperties: this._customProperties,
3275
+ fontMetrics: this._fontMetrics,
3276
+ renderers: this.#composition.renderers,
3277
+ partContributors: this.#composition.partContributors
3278
+ };
3279
+ }
3280
+ /**
3281
+ * Author + serialize this presentation's slides as injectable descriptors,
3282
+ * WITHOUT producing a `.pptx` package. Runs the same media-encode, placeholder
3283
+ * backfill, and measured-fit passes `write()` uses, then serializes each slide
3284
+ * body and resolves its image media to decoded bytes — so a loaded deck can
3285
+ * splice the slides in via `Presentation.appendSlides()` (see `pptx-ts/read`)
3286
+ * while keeping its own masters/layouts/theme byte-identical.
3287
+ *
3288
+ * Returns the deck's slide size (EMU, for the destination size check) and one
3289
+ * descriptor per authored slide. Charts, audio/video media, online media, and
3290
+ * internal slide-to-slide hyperlinks are surfaced as descriptors alongside the
3291
+ * serialized body — this method does not resolve them into a package; the append
3292
+ * path consumes each descriptor to reserve the parts and rebuild the rel graph.
3293
+ */
3294
+ extractSlides = async (opts = {}) => extractSlides({
3295
+ runtime: this._runtime,
3296
+ presentation: this.internalPresentation,
3297
+ fontMetrics: this._fontMetrics,
3298
+ renderers: this.#composition.renderers,
3299
+ extract: this.#composition.extract
3300
+ }, opts);
3301
+ /**
3302
+ * One of this presentation's family-supplied methods, or the failure that names the family it
3303
+ * would have come from. The methods that call it stay on the class because their signatures are
3304
+ * the public contract; what they reach is what a family owns.
3305
+ *
3306
+ * `protected` for the browser entry, whose `tableToSlides` is one of these and is declared on
3307
+ * the subclass so that a Node build never links the DOM reader.
3308
+ */
3309
+ familyAuthor(method) {
3310
+ const author = requirePresentationAuthor(this.#composition.presentationAuthors, method);
3311
+ return ((...args) => author(this.#authorContext, ...args));
3312
+ }
3313
+ /**
3314
+ * Register a font's metrics so `fit:'shrink'` and `fit:'resize'` text is measured at
3315
+ * export time: `'shrink'` bakes a real `fontScale` and `'resize'` a real box height, so
3316
+ * the text renders fitted in headless renderers and on plain file-open, with no manual
3317
+ * edit/resize.
3318
+ *
3319
+ * Without registered metrics, both write the bare flag (`<a:normAutofit/>` or
3320
+ * `<a:spAutoFit/>`) that only PowerPoint recomputes on edit. Register the same
3321
+ * face once per weight/style you use; bold/italic advances differ.
3322
+ *
3323
+ * **Font collections** (`.ttc`/`.otc`, how MS Gothic, Yu Gothic, SimSun, Microsoft
3324
+ * YaHei and Cambria ship on Windows) hold several fonts in one file, so one has to be
3325
+ * chosen. With no `font` option, `face` is used as the selector, which is usually what
3326
+ * you meant; a `face` that names no font in the collection throws rather than falling
3327
+ * back to the first, since measuring the wrong member is invisible downstream. Pass
3328
+ * `font` when the deck-side name differs from the name inside the file, and use
3329
+ * `listFontFaces` from `pptx-ts/measure` to see what a file holds.
3330
+ * @param {string} face - font family name as used in `fontFace` (e.g. 'Aptos')
3331
+ * @param {string | Uint8Array | ArrayBuffer} source - font file path/URL (Node/web) or raw TTF/OTF/TTC
3332
+ * bytes. A string is always a path or URL here; base64 text is `embedFont`'s `data`, not this.
3333
+ * @param {object} [opts] - variant flags (advances differ per weight/style) and collection selection
3334
+ * @param {boolean} [opts.bold] - these are the bold advances
3335
+ * @param {boolean} [opts.italic] - these are the italic advances
3336
+ * @param {number | string} [opts.font] - which font inside a collection: 0-based index, or a name
3337
+ * @example await pptx.registerFontMetrics('Aptos', '/usr/share/fonts/Aptos.ttf')
3338
+ * @example await pptx.registerFontMetrics('Aptos', aptosBoldBytes, { bold: true })
3339
+ * @example await pptx.registerFontMetrics('MS PGothic', 'C:/Windows/Fonts/msgothic.ttc')
3340
+ * @example await pptx.registerFontMetrics('Cambria Math', 'C:/Windows/Fonts/cambria.ttc', { font: 1 })
3341
+ */
3342
+ async registerFontMetrics(face, source, opts) {
3343
+ const bytes = await resolveFontBytes(source, this._runtime, void 0, "registerFontMetrics: `source`");
3344
+ const font = opts?.font ?? (isFontCollection(bytes) ? face : void 0);
3345
+ const metrics = await parseFontMetrics(bytes, font === void 0 ? void 0 : { font });
3346
+ this._fontMetrics.set(face, metrics, pickDefined(opts ?? {}, ["bold", "italic"]));
3347
+ }
3348
+ /**
3349
+ * Embed a font face so the deck renders with it even on machines that do not
3350
+ * have it installed. The **whole** face is embedded (not glyph-subset) under
3351
+ * `/ppt/fonts/` and wired into `presentation.xml` (`p:embeddedFontLst` +
3352
+ * `embedTrueTypeFonts="1"`), exactly as PowerPoint's "Embed fonts in the file"
3353
+ * does. Call once per face/weight you use.
3354
+ *
3355
+ * The declared `typeface` MUST match the family name used in your run/`fontFace`
3356
+ * typefaces (e.g. `addText('hi', { fontFace: 'Silkscreen' })`) or PowerPoint
3357
+ * will not bind the embedded bytes to the text. Repeated calls with the same
3358
+ * `typeface` and different `style` accumulate into one `p:embeddedFont` entry.
3359
+ *
3360
+ * Font licensing is the caller's responsibility: the bytes are embedded as
3361
+ * handed over; `OS/2.fsType` embedding-permission bits are not enforced.
3362
+ * @param {object} opts - font source + identity
3363
+ * @param {string} [opts.path] - font file path (Node) or URL (web) to a `.ttf`/`.otf`
3364
+ * @param {ArrayBuffer | Uint8Array | string} [opts.data] - raw font bytes, or a base64 string (bare or a
3365
+ * whole `data:` URL), in lieu of `path`. Unlike `registerFontMetrics`, a string here is never a path.
3366
+ * @param {string} opts.typeface - family name as referenced by run/`fontFace` typefaces
3367
+ * @param {EmbeddedFontSlot} [opts.style] - face slot; defaults to `'regular'`
3368
+ * @example await pptx.embedFont({ path: '/fonts/Silkscreen-Regular.ttf', typeface: 'Silkscreen' })
3369
+ * @example await pptx.embedFont({ path: '/fonts/Silkscreen-Bold.ttf', typeface: 'Silkscreen', style: 'bold' })
3370
+ */
3371
+ async embedFont(opts) {
3372
+ if (!opts || typeof opts.typeface !== "string" || opts.typeface.trim() === "") throw new InvalidOptionError("font/missing-typeface", "embedFont: `typeface` is required (the family name your runs reference)");
3373
+ const slot = opts.style ?? "regular";
3374
+ if (!EMBEDDED_FONT_SLOTS.includes(slot)) throw new InvalidOptionError("font/invalid-style-slot", `embedFont: invalid style "${slot}"; expected one of ${EMBEDDED_FONT_SLOTS.join(", ")}`);
3375
+ if (opts.path === void 0 && opts.data === void 0) throw new InvalidOptionError("font/missing-source", "embedFont: provide either `path` or `data`");
3376
+ const bytes = typeof opts.path === "string" ? await resolveFontBytes(opts.path, this._runtime, void 0, "embedFont: `path`") : await resolveFontBytes(opts.data, this._runtime, { base64: true }, "embedFont: `data` string");
3377
+ let font = this._embeddedFonts.find((f) => f.typeface === opts.typeface);
3378
+ if (!font) {
3379
+ font = {
3380
+ typeface: opts.typeface,
3381
+ faces: []
3382
+ };
3383
+ this._embeddedFonts.push(font);
3384
+ }
3385
+ const existing = font.faces.find((f) => f.slot === slot);
3386
+ if (existing) existing.bytes = bytes;
3387
+ else font.faces.push({
3388
+ slot,
3389
+ bytes
3390
+ });
3391
+ }
3392
+ /**
3393
+ * Measure how tall text wraps at a given width, using the **same** calibrated wrap
3394
+ * model the export-time autofit bake uses — so a layout-time prediction matches the
3395
+ * value `fit:'shrink'`/`'resize'` would bake. Synchronous: register the face's metrics
3396
+ * first with {@link TsPptx.registerFontMetrics} (lookup is sync).
3397
+ *
3398
+ * Lets a consumer size its own geometry before export — grow a card to fit its text,
3399
+ * reflow a grid, or detect overflow at layout time. Heights err **tall** (conservative),
3400
+ * so the returned height is ≥ what PowerPoint/LibreOffice render.
3401
+ * @param {string | TextProps[]} text - a string or run array (per-run options override the defaults in `opts`)
3402
+ * @param {MeasureTextOptions} opts - width (inches), font size/face (points), and spacing
3403
+ * @returns {TextMeasurement} laid-out height + line count + fit helpers
3404
+ * @example const m = pptx.measureText('Long heading…', { wIn: 3, fontSize: 18, fontFace: 'Aptos' })
3405
+ * @example if (pptx.measureText(runs, { wIn, fontSize, fontFace }).heightIn > cardHeightIn) growCard()
3406
+ */
3407
+ measureText(text, opts) {
3408
+ return this.familyAuthor("measureText")(text, opts);
3409
+ }
3410
+ /**
3411
+ * Convenience overflow check: `true` if `text` does not fit a box of inner size
3412
+ * `wIn`×`hIn` (inches) at full size. Because the model errs tall, this is a
3413
+ * **conservative** (slightly over-reporting) check — appropriate for a build-time
3414
+ * warning, not a hard gate. An unmeasurable (unnamed theme-default) face reports
3415
+ * `false` (no overflow) so the linter does not false-positive on faces it cannot measure.
3416
+ * @param {string | TextProps[]} text - a string or run array
3417
+ * @param {OverflowBoxOptions} opts - {@link MeasureTextOptions} plus the box inner height `hIn`
3418
+ * @returns {boolean} true if the text overflows the box
3419
+ */
3420
+ overflowsBox(text, opts) {
3421
+ return this.familyAuthor("overflowsBox")(text, opts);
3422
+ }
3423
+ /**
3424
+ * Compute the per-cell geometry of a table laid out at `opts.x`/`y`/`w`, without
3425
+ * adding it to a slide — so a consumer can place images or shapes precisely over
3426
+ * cells. Column widths (cell `x`/`w`) are exact, derived from the same logic the
3427
+ * writer uses. Row heights (`y`/`h`) are exact when pinned by `rowH` (array or
3428
+ * scalar) or table `h`; an auto-height row is estimated with the same conservative
3429
+ * (tall) text model as {@link TsPptx.measureText} and flagged `heightExact:false`
3430
+ * (register the cell font via {@link TsPptx.registerFontMetrics} for an exact
3431
+ * estimate). Geometry is for a single, un-paginated table — `autoPage` paging is
3432
+ * not modeled.
3433
+ * @param {TableRow[]} rows - the same `rows` passed to `slide.addTable`
3434
+ * @param {TableProps} opts - the same table options (`x`, `y`, `w`, `colW`, `rowH`, `h`, …)
3435
+ * @returns {TableLayoutResult} per-cell rectangles (inches) plus overall table bounds
3436
+ * @example
3437
+ * const g = pptx.tableLayout(rows, { x: 1, y: 1, w: 8, colW: [2, 3, 3] })
3438
+ * const c = g.cells.find(c => c.row === 0 && c.col === 2)
3439
+ * slide.addImage({ path: 'logo.png', x: c.xIn, y: c.yIn, w: c.wIn, h: c.hIn })
3440
+ */
3441
+ tableLayout(rows, opts) {
3442
+ return this.familyAuthor("tableLayout")(rows, opts);
3443
+ }
3444
+ /**
3445
+ * Export the current presentation as portable ZIP bytes. Unlike the former
3446
+ * `stream()` method, this does not imply incremental output or convert the
3447
+ * archive to a Node `Buffer`: package assembly is synchronous and the returned
3448
+ * `Uint8Array` works unchanged in Node, browsers, and workers.
3449
+ * @param {WriteBaseProps} props - output properties
3450
+ * @returns {Promise<Uint8Array>} complete `.pptx` archive bytes
3451
+ */
3452
+ async toBytes(props) {
3453
+ return await writePackage(this.packageSource(), {
3454
+ ...pickDefined(props ?? {}, ["compression", "onMediaError"]),
3455
+ outputType: "uint8array"
3456
+ });
3457
+ }
3458
+ /**
3459
+ * Export the current Presentation as ZIP content with the selected type
3460
+ * @param {WriteProps} props output properties
3461
+ * @returns {Promise<string | ArrayBuffer | Blob | Uint8Array>} file content in selected type
3462
+ */
3463
+ async write(props) {
3464
+ return await writePackage(this.packageSource(), pickDefined(props ?? {}, [
3465
+ "compression",
3466
+ "outputType",
3467
+ "onMediaError"
3468
+ ]));
3469
+ }
3470
+ /**
3471
+ * Assemble the current Presentation into its OOXML package parts WITHOUT zipping — the raw
3472
+ * `path → bytes` contents `write()` would compress into the `.pptx`. Use this to stream parts
3473
+ * into a custom container, inspect individual parts, or feed a pipeline that does its own
3474
+ * archiving. To produce a real `.pptx` file, use {@link write} / {@link writeFile} instead.
3475
+ *
3476
+ * The returned parts are byte-identical, per part, to what `write()` emits for the same deck,
3477
+ * and their order is the package's emission order. Both are a stability-guaranteed observable
3478
+ * contract: adding a new part in a later release is backward-compatible as long as existing
3479
+ * part paths and their relative order do not shift; renaming or reordering an existing part is
3480
+ * a breaking change. Each call returns fresh `Uint8Array` views over per-call buffers (a new
3481
+ * assembly runs each time), so callers may retain or transfer them without defensive copying.
3482
+ * @param {PartsProps} props - assembly options (`onMediaError` only; compression/output shape
3483
+ * are zip concerns that do not apply to unzipped parts)
3484
+ * @returns {Promise<PackagePart[]>} the package parts in emission order
3485
+ */
3486
+ async toParts(props) {
3487
+ return (await buildPackageParts(this.packageSource(), { onMediaError: props?.onMediaError })).map(({ path, data }) => ({
3488
+ path,
3489
+ data
3490
+ }));
3491
+ }
3492
+ /**
3493
+ * Export the current Presentation.
3494
+ * Write the generated presentation to disk (Node) or trigger a download (browser).
3495
+ * @param {WriteFileProps} props - output file properties
3496
+ * @returns {Promise<string>} the presentation name
3497
+ */
3498
+ async writeFile(props) {
3499
+ const rawName = props?.fileName ?? "Presentation.pptx";
3500
+ const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
3501
+ const writeProps = pickDefined(props ?? {}, ["compression", "onMediaError"]);
3502
+ setOrClear(writeProps, "outputType", this._runtime.writeFileOutputType ?? void 0);
3503
+ const data = await writePackage(this.packageSource(), writeProps);
3504
+ return await this._runtime.writeFile(fileName, data);
3505
+ }
3506
+ /**
3507
+ * Set a custom document property stored in `docProps/custom.xml`.
3508
+ * Calling with the same name replaces the existing value.
3509
+ * @param name - property name
3510
+ * @param value - string, integer/float number, boolean, or Date
3511
+ */
3512
+ setCustomProperty(name, value) {
3513
+ this._customProperties = this._customProperties.filter((p) => p.name !== name);
3514
+ this._customProperties.push({
3515
+ name,
3516
+ value
3517
+ });
3518
+ }
3519
+ /**
3520
+ * Add a new Section to Presentation
3521
+ * @param {SectionProps} section - section properties
3522
+ * @example pptx.addSection({ title:'Charts' });
3523
+ */
3524
+ addSection(section) {
3525
+ if (!section) {
3526
+ warn("section/missing-argument", "addSection requires an argument");
3527
+ return;
3528
+ } else if (!section.title) {
3529
+ warn("section/missing-title", "addSection requires a title");
3530
+ return;
3531
+ } else if (this._sections.some((sect) => sect.title === section.title)) {
3532
+ warn("section/duplicate-title", `addSection: a section titled "${section.title}" already exists; ignoring duplicate`);
3533
+ return;
3534
+ }
3535
+ const newSection = {
3536
+ _type: "user",
3537
+ _slides: [],
3538
+ _id: bracedGuid(),
3539
+ title: section.title
3540
+ };
3541
+ if (section.order === void 0) {
3542
+ this._sections.push(newSection);
3543
+ return;
3544
+ }
3545
+ if (!Number.isInteger(section.order) || section.order < 1) {
3546
+ warn("section/invalid-order", `addSection: "order" must be a whole number of at least 1 (the first position); got ${String(section.order)}. Appending.`);
3547
+ this._sections.push(newSection);
3548
+ return;
3549
+ }
3550
+ this._sections.splice(section.order - 1, 0, newSection);
3551
+ }
3552
+ /**
3553
+ * Add a new Slide to Presentation
3554
+ * @param {AddSlideProps} options - slide options
3555
+ * @returns {Slide} the new Slide
3556
+ */
3557
+ addSlide(options) {
3558
+ const masterTitle = options?.masterTitle ?? "";
3559
+ let slideLayout = {
3560
+ _name: this.#requireDefaultLayout().name,
3561
+ _presLayout: this.presLayout,
3562
+ _rels: [],
3563
+ _relsChart: [],
3564
+ _relsMedia: [],
3565
+ _slideNum: this._slides.length + 1,
3566
+ _slideObjects: []
3567
+ };
3568
+ if (masterTitle) {
3569
+ const tmpLayout = this._slideLayouts.find((layout) => layout._name === masterTitle);
3570
+ if (tmpLayout) slideLayout = tmpLayout;
3571
+ else warn("slide/master-not-found", `addSlide: unable to find a slide master with title: "${masterTitle}"; using the default layout`);
3572
+ }
3573
+ const newSlide = new SlideBuilder({
3574
+ addSlide: this.addNewSlide,
3575
+ getSlide: this.getSlide,
3576
+ getSections: this.getSections,
3577
+ presLayout: this.presLayout,
3578
+ authors: this.#composition.authors,
3579
+ childAuthors: this.#composition.children,
3580
+ setSlideNum: this.setSlideNumber,
3581
+ slideId: this._slides.length + 256,
3582
+ slideRId: this._slides.length + 2,
3583
+ slideNumber: this._slides.length + 1,
3584
+ slideLayout
3585
+ });
3586
+ this._slides.push(newSlide);
3587
+ const namedSection = options?.sectionTitle ? this._sections.find((section) => section.title === options.sectionTitle) : void 0;
3588
+ if (options?.sectionTitle && !namedSection) warn("slide/section-not-found", `addSlide: unable to find section with title: "${options.sectionTitle}"`);
3589
+ if (namedSection) namedSection._slides.push(newSlide);
3590
+ else if (this._sections.length > 0) {
3591
+ const lastSect = this._sections[this._sections.length - 1];
3592
+ if (lastSect?._type === "default") lastSect._slides.push(newSlide);
3593
+ else this._sections.push({
3594
+ title: `Default-${this._sections.filter((sect) => sect._type === "default").length + 1}`,
3595
+ _type: "default",
3596
+ _id: bracedGuid(),
3597
+ _slides: [newSlide]
3598
+ });
3599
+ }
3600
+ return newSlide;
3601
+ }
3602
+ /**
3603
+ * Create a custom Slide Layout in any size
3604
+ * @param {PresLayout} layout - layout properties
3605
+ * @example pptx.defineLayout({ name:'A3', width:16.5, height:11.7 });
3606
+ */
3607
+ defineLayout(layout) {
3608
+ if (!layout || typeof layout !== "object") throw new InvalidOptionError("layout/invalid-definition", "defineLayout requires an object `{ name, width, height }` with the dimensions in inches");
3609
+ if (!layout.name) throw new InvalidOptionError("layout/invalid-definition", "defineLayout requires a non-empty `name`: it is the key the layout is registered and selected under");
3610
+ if (layout.width === void 0) warn("layout/invalid-definition", "defineLayout requires `width`");
3611
+ else if (typeof layout.width !== "number") warn("layout/invalid-definition", "defineLayout `width` should be a number (inches)");
3612
+ if (layout.height === void 0) warn("layout/invalid-definition", "defineLayout requires `height`");
3613
+ else if (typeof layout.height !== "number") warn("layout/invalid-definition", "defineLayout `height` should be a number (inches)");
3614
+ this.LAYOUTS[layout.name] = {
3615
+ name: layout.name,
3616
+ width: inchesToEmu(layoutSideInches(layout.width, "width")),
3617
+ height: inchesToEmu(layoutSideInches(layout.height, "height"))
3618
+ };
3619
+ }
3620
+ /**
3621
+ * Create a new slide master [layout] for the Presentation
3622
+ * @param {SlideMasterProps} props - layout properties
3623
+ */
3624
+ defineSlideMaster(props) {
3625
+ const propsClone = structuredClone(props);
3626
+ if (!propsClone.title) throw new InvalidOptionError("master/missing-title", "defineSlideMaster() object argument requires a `title` value.");
3627
+ const newLayout = {
3628
+ _margin: propsClone.margin || DEF_SLIDE_MARGIN_IN,
3629
+ _name: propsClone.title,
3630
+ _presLayout: this.presLayout,
3631
+ _rels: [],
3632
+ _relsChart: [],
3633
+ _relsMedia: [],
3634
+ _slide: null,
3635
+ _slideNum: 1e3 + this._slideLayouts.length + 1,
3636
+ _slideNumberProps: propsClone.slideNumber || null,
3637
+ _slideObjects: [],
3638
+ background: propsClone.background
3639
+ };
3640
+ createSlideMaster(propsClone, newLayout, this.#composition.children);
3641
+ if (propsClone.textStyles && typeof propsClone.textStyles === "object") this._masterSlide._txStyles = {
3642
+ ...this._masterSlide._txStyles,
3643
+ ...propsClone.textStyles
3644
+ };
3645
+ this._slideLayouts.push(newLayout);
3646
+ if (propsClone.background) addBackgroundDefinition(propsClone.background, newLayout);
3647
+ if (newLayout._slideNumberProps && !this._masterSlide._slideNumberProps) this._masterSlide._slideNumberProps = newLayout._slideNumberProps;
3648
+ }
3649
+ };
3650
+ //#endregion
3651
+ //#region src/families/core.ts
3652
+ /**
3653
+ * ts-pptx: the construct families every presentation carries
3654
+ *
3655
+ * The core tier. A composed presentation gets these whether it asks for them or not, so
3656
+ * `createPresentation()` with no families still authors text, shapes, images, groups and speaker
3657
+ * notes -- a deck, rather than a slide-shaped hole.
3658
+ *
3659
+ * The line is a judgement rather than a measurement: these five are what "a slide" means to most
3660
+ * callers, they are cheap, and a surface that made someone compose `text` before it could write a
3661
+ * word would be a worse default than one that costs a few kilobytes more. A family is core when
3662
+ * leaving it out would make the result something other than a deck; everything else is asked for.
3663
+ */
3664
+ /** Every family a composed presentation carries without being asked. */
3665
+ const CORE_CONSTRUCT_FAMILIES = [
3666
+ textFamily,
3667
+ shapeFamily,
3668
+ imageFamily,
3669
+ groupFamily,
3670
+ notesFamily
3671
+ ];
3672
+ //#endregion
3673
+ //#region src/entry-compose.ts
3674
+ /**
3675
+ * ts-pptx: composing a presentation from the construct families it actually needs
3676
+ *
3677
+ * The body behind each entry's `createPresentation`, and the types that describe what comes back.
3678
+ * It lives beside `entry-surface.ts` for the same reason that does: `index.ts`, `node.ts` and
3679
+ * `browser.ts` differ only in the runtime adapter they hand down, so the shared half is written
3680
+ * once and cannot drift between them.
3681
+ *
3682
+ * There is no second lean entry point, deliberately. A `pptx-ts/core` would need the same
3683
+ * three-condition treatment `.` gets, so three more source entries, three more `dist` files, three
3684
+ * more budget rows and a second place for the entries to diverge -- and it would buy nothing that
3685
+ * export-level shaking does not already give. `pptx-ts` is an intermediate module in a consumer's
3686
+ * graph: a program that imports only `createPresentation` drops the `TsPptx` class beside it and,
3687
+ * with it, every family that class names.
3688
+ */
3689
+ /**
3690
+ * Build a presentation that carries the core tier plus `opts.use`, on the given runtime adapter.
3691
+ *
3692
+ * The cast is the one place this file is not type-safe, and it is unavoidable: the class is one
3693
+ * class whatever it was composed with, while the type says which of its methods are actually
3694
+ * there. The two are kept honest by the runtime, which raises `family/not-composed` naming the
3695
+ * family for any method the type has already refused.
3696
+ */
3697
+ function composePresentation(runtime, opts) {
3698
+ return new PresentationCore(runtime, [...CORE_CONSTRUCT_FAMILIES, ...opts?.use ?? []]);
3699
+ }
3700
+ //#endregion
3701
+ //#region src/entry-families.ts
3702
+ /**
3703
+ * ts-pptx: the construct families the three main entries are composed with
3704
+ *
3705
+ * The full authoring surface, as a list: `index.ts`, `node.ts` and `browser.ts` each compose every
3706
+ * family, so `TsPptx` authors everything it ever has; the browser entry adds the table family's
3707
+ * live-DOM half on top, because that one needs a `document` to read. The families a *composed*
3708
+ * presentation gets without asking are the shorter list in `families/core.ts`.
3709
+ *
3710
+ * It lives beside `entry-surface.ts`, and for the same reason: the three entries publish one API
3711
+ * and differ only in the runtime adapter they hand down, so what they are composed with is one
3712
+ * list rather than three that can drift.
3713
+ *
3714
+ * Naming a family here is what puts its code in the graph -- a named import inside a reachable
3715
+ * function body is retained unconditionally. That is the whole point of the seam
3716
+ * (`families/shared.ts`): a program composed with fewer families links fewer of them.
3717
+ */
3718
+ /**
3719
+ * Every construct family the library can author with: the core tier, plus everything a composed
3720
+ * presentation would have to ask for. Built from `CORE_CONSTRUCT_FAMILIES` rather than restating
3721
+ * it, so the two lists cannot disagree about whether a family exists.
3722
+ */
3723
+ const ALL_CONSTRUCT_FAMILIES = /* @__PURE__ */ Object.freeze([
3724
+ textFamily,
3725
+ shapeFamily,
3726
+ imageFamily,
3727
+ groupFamily,
3728
+ notesFamily,
3729
+ animationsFamily,
3730
+ chartFamily,
3731
+ commentsFamily,
3732
+ connectorFamily,
3733
+ measureFamily,
3734
+ mediaFamily,
3735
+ model3dFamily,
3736
+ oleFamily,
3737
+ tableFamily,
3738
+ zoomFamily
3739
+ ]);
3740
+ //#endregion
3741
+ //#region src/runtime/fetch-media.ts
3742
+ /**
3743
+ * The parts of a runtime adapter that are not runtime-specific.
3744
+ *
3745
+ * `fetch`, `btoa` and `TextEncoder` exist in Node, in every browser, and in the edge/worker
3746
+ * runtimes the neutral adapter serves, so the remote-load paths were the same code in all three
3747
+ * adapters — byte-identical in two of them and an inline branch in the third. What is genuinely
3748
+ * per-runtime stays in each adapter: `node:fs`, `FileReader`, the canvas rasterizer, the
3749
+ * `<a download>` writer.
3750
+ *
3751
+ * Nothing here touches a host global beyond `fetch`.
3752
+ */
3753
+ /**
3754
+ * Whether `source` names something to `fetch` rather than something to read from a filesystem.
3755
+ *
3756
+ * A real scheme test, not `source.startsWith('http')`. That prefix matched a *relative path* —
3757
+ * `httpdocs/logo.png`, `http-icons/x.svg` — and sent it to `fetch`, which then failed with a
3758
+ * fetch error naming a file that exists on disk. It was also case-sensitive, so
3759
+ * `HTTPS://example.com/f.ttf` missed and went to `fs.readFile`.
3760
+ *
3761
+ * Only `http:` and `https:` count. A `file:` or `data:` URL falls through to the adapter's
3762
+ * local branch, where it fails — the same as before this was tightened, and deliberately left
3763
+ * that way: making either work is a feature with its own decisions (what a `file:` URL means
3764
+ * off Node, whether a `data:` URL should bypass loading entirely), not a side effect of fixing
3765
+ * a prefix test.
3766
+ */
3767
+ function isRemote(source) {
3768
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(source)?.[1]?.toLowerCase();
3769
+ return scheme === "http" || scheme === "https";
3770
+ }
3771
+ /** Fetch a font file into raw bytes for `registerFontMetrics`. */
3772
+ async function fetchFontBytes(source) {
3773
+ const response = await fetch(source);
3774
+ if (!response.ok) throw new MediaError("font/fetch-failed", `Unable to load font (fetch): ${source}`);
3775
+ return new Uint8Array(await response.arrayBuffer());
3776
+ }
3777
+ /**
3778
+ * Fetch a media file as raw base64 (no `data:` prefix) — see {@link RuntimeAdapter.loadMedia}
3779
+ * for why both encodings are accepted.
3780
+ *
3781
+ * Encodes through `bytesToBase64` rather than `Buffer`, so the same function serves a runtime
3782
+ * that has no `Buffer`. The Node adapter still uses `Buffer` on its *filesystem* branch, where
3783
+ * it is both available and faster on a large payload.
3784
+ */
3785
+ async function fetchMediaBase64(path) {
3786
+ const response = await fetch(path);
3787
+ if (!response.ok) throw new MediaError("media/fetch-failed", `Unable to load image (fetch): ${path}`);
3788
+ return bytesToBase64(new Uint8Array(await response.arrayBuffer()));
3789
+ }
3790
+ /**
3791
+ * The SVG-preview fallback for a runtime with no canvas: stamp the placeholder PNG.
3792
+ *
3793
+ * An SVG placed on a slide needs a raster fallback beside it for viewers that will not render
3794
+ * the SVG. Rasterizing needs a canvas, which only the browser adapter has, so everywhere else
3795
+ * the fallback is a fixed placeholder image rather than a missing part. That is this runtime's
3796
+ * output rather than a failure, so it resolves `null`: reporting one would fail every SVG under
3797
+ * the default `onMediaError`.
3798
+ */
3799
+ async function placeholderSvgPreview(rel) {
3800
+ rel.data = IMG_SVG_PLACEHOLDER;
3801
+ return null;
3802
+ }
3803
+ //#endregion
3804
+ //#region src/clip.ts
3805
+ /**
3806
+ * ts-pptx: named clip silhouettes for images
3807
+ *
3808
+ * A `ClipShape` is declarative data — a named silhouette plus its options — and {@link clipPath}
3809
+ * resolves it to the freeform `points` path `addImage` emits as the `<a:custGeom>` clip mask on
3810
+ * the `<p:pic>`. Paired with `sizing: { type: 'cover' }` (an `<a:srcRect>` crop) it reproduces
3811
+ * what a PowerPoint *picture placeholder* does — a layout `custGeom` clipping an inherited
3812
+ * blipFill — but standalone, with no placeholder involved.
3813
+ *
3814
+ * These are silhouettes recurring often enough in real decks to be worth naming; hand-authored
3815
+ * `points` remain available for anything else. Nothing here is theme- or brand-aware: which
3816
+ * silhouette to use, and which edge it sits flush to, belongs to the caller.
3817
+ *
3818
+ * ## The coordinate trap
3819
+ *
3820
+ * Coordinates come back in the image box's OWN inch space (`0..w`, `0..h`), because `custGeom`
3821
+ * points expressed as `%` resolve against the SLIDE, not the box. A box-relative silhouette must
3822
+ * therefore be emitted in inches already scaled to the box, which is why {@link clipPath} takes
3823
+ * the box size: the fractions below are multiplied by `w` / `h` at build time, so one silhouette
3824
+ * scales with whatever region it is handed. Authoring the same shape as percentages would put
3825
+ * the clip in the wrong place on every box that is not the full slide.
3826
+ */
3827
+ const HALF_DISC = {
3828
+ deep: {
3829
+ topEdgeX: .31788,
3830
+ botEdgeX: .31788,
3831
+ tipTopX: .30061,
3832
+ tipTopY: .01078,
3833
+ tipBotX: .30061,
3834
+ tipBotY: .98922,
3835
+ apexY: .5,
3836
+ c1b: {
3837
+ x: .11594,
3838
+ y: .86833
3839
+ },
3840
+ c2b: {
3841
+ x: 0,
3842
+ y: .69392
3843
+ },
3844
+ c1t: {
3845
+ x: 0,
3846
+ y: .30609
3847
+ },
3848
+ c2t: {
3849
+ x: .11594,
3850
+ y: .13167
3851
+ }
3852
+ },
3853
+ shallow: {
3854
+ topEdgeX: .12611,
3855
+ botEdgeX: .11128,
3856
+ tipTopX: .09976,
3857
+ tipTopY: .05448,
3858
+ tipBotX: .09976,
3859
+ tipBotY: .97617,
3860
+ apexY: .51533,
3861
+ c1b: {
3862
+ x: .03522,
3863
+ y: .83247
3864
+ },
3865
+ c2b: {
3866
+ x: 0,
3867
+ y: .67725
3868
+ },
3869
+ c1t: {
3870
+ x: 0,
3871
+ y: .3534
3872
+ },
3873
+ c2t: {
3874
+ x: .03522,
3875
+ y: .19818
3876
+ }
3877
+ }
3878
+ };
3879
+ /** The values each {@link ClipShape} field takes, for refusing one a JavaScript caller got wrong. */
3880
+ const CLIP_KINDS = ["half-disc"];
3881
+ const FLAT_SIDES = ["left", "right"];
3882
+ const HALF_DISC_PRESETS = Object.keys(HALF_DISC);
3883
+ /**
3884
+ * Half-disc clip path for a `w × h` box. Authored flat-right; `flat: 'left'` mirrors `x → 1 - x`.
3885
+ */
3886
+ function halfDisc(w, h, flat, preset) {
3887
+ const s = HALF_DISC[preset];
3888
+ const fx = flat === "left" ? (x) => (1 - x) * w : (x) => x * w;
3889
+ const fy = (y) => y * h;
3890
+ return [
3891
+ {
3892
+ x: fx(s.topEdgeX),
3893
+ y: fy(0),
3894
+ moveTo: true
3895
+ },
3896
+ {
3897
+ x: fx(1),
3898
+ y: fy(0)
3899
+ },
3900
+ {
3901
+ x: fx(1),
3902
+ y: fy(1)
3903
+ },
3904
+ {
3905
+ x: fx(s.botEdgeX),
3906
+ y: fy(1)
3907
+ },
3908
+ {
3909
+ x: fx(s.tipBotX),
3910
+ y: fy(s.tipBotY)
3911
+ },
3912
+ {
3913
+ x: fx(0),
3914
+ y: fy(s.apexY),
3915
+ curve: {
3916
+ type: "cubic",
3917
+ x1: fx(s.c1b.x),
3918
+ y1: fy(s.c1b.y),
3919
+ x2: fx(s.c2b.x),
3920
+ y2: fy(s.c2b.y)
3921
+ }
3922
+ },
3923
+ {
3924
+ x: fx(s.tipTopX),
3925
+ y: fy(s.tipTopY),
3926
+ curve: {
3927
+ type: "cubic",
3928
+ x1: fx(s.c1t.x),
3929
+ y1: fy(s.c1t.y),
3930
+ x2: fx(s.c2t.x),
3931
+ y2: fy(s.c2t.y)
3932
+ }
3933
+ },
3934
+ { close: true }
3935
+ ];
3936
+ }
3937
+ /**
3938
+ * Resolve a {@link ClipShape} to a freeform `points` path filling a `w × h` box, ready to hand
3939
+ * to `addImage({ points })`.
3940
+ *
3941
+ * `w`/`h` are the image box's size **in inches** and must match the `w`/`h` the picture is drawn
3942
+ * at — the path is emitted in the box's own coordinate space, not normalized (see the module
3943
+ * note above).
3944
+ * @param {ClipShape} shape - the named silhouette and its options
3945
+ * @param {number} w - image box width (inches)
3946
+ * @param {number} h - image box height (inches)
3947
+ * @returns {GeometryPoint[]} the freeform path, for `addImage({ points })`
3948
+ * @throws {InvalidOptionError} `clip/invalid-shape` when `kind`, `flat` or `preset` is not one the
3949
+ * silhouette names. From JavaScript an unknown preset used to fail reading a field off
3950
+ * `undefined`, an unknown kind returned nothing and an unknown flat side traced the right-flat path.
3951
+ * @example
3952
+ * const w = 5.22, h = 7.5
3953
+ * slide.addImage({
3954
+ * path: 'cover.jpg', x: 0, y: 0, w, h,
3955
+ * points: clipPath({ kind: 'half-disc', flat: 'right' }, w, h),
3956
+ * sizing: { type: 'cover' }, // center-crop the photo into the "D"
3957
+ * })
3958
+ */
3959
+ function clipPath(shape, w, h) {
3960
+ switch (checkEnumOrThrow(String(shape?.kind), CLIP_KINDS, "clipPath `kind`", "clip/invalid-shape")) {
3961
+ case "half-disc": return halfDisc(w, h, checkEnumOrThrow(shape.flat, FLAT_SIDES, "clipPath `flat`", "clip/invalid-shape"), checkEnumOrThrow(shape.preset ?? "deep", HALF_DISC_PRESETS, "clipPath `preset`", "clip/invalid-shape"));
3962
+ }
3963
+ }
3964
+ //#endregion
3965
+ //#region src/types/text.ts
3966
+ /** Factory for a single inline text run. Prevents `as never` casts when building mixed-style run arrays. */
3967
+ function textRun(text, options) {
3968
+ return options !== void 0 ? {
3969
+ text,
3970
+ options
3971
+ } : { text };
3972
+ }
3973
+ /** Wraps a run array so TypeScript accepts it as `TextProps[]` without a cast. */
3974
+ function textRuns(runs) {
3975
+ return runs;
3976
+ }
3977
+ //#endregion
3978
+ export { fetchMediaBase64 as a, ALL_CONSTRUCT_FAMILIES as c, IMG_SVG_PLACEHOLDER as d, fetchFontBytes as i, composePresentation as l, textRuns as n, isRemote as o, clipPath as r, placeholderSvgPreview as s, textRun as t, PresentationCore as u };
3979
+
3980
+ //# sourceMappingURL=text-sN64Otf_.js.map