@shbernal/ts-pptx 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +55 -20
  2. package/dist/{autopage-QQyfRtoK.js → autopage-D1sqK6j7.js} +16 -15
  3. package/dist/{autopage-QQyfRtoK.js.map → autopage-D1sqK6j7.js.map} +1 -1
  4. package/dist/browser.d.ts +6 -6
  5. package/dist/browser.js +5 -6
  6. package/dist/browser.js.map +1 -1
  7. package/dist/{diagnostics-PMINsVP6.d.ts → diagnostics-B2yWpcjB.d.ts} +31 -14
  8. package/dist/{diagnostics-PMINsVP6.d.ts.map → diagnostics-B2yWpcjB.d.ts.map} +1 -1
  9. package/dist/{diagnostics-DYQC7xLV.js → diagnostics-H1QzgRSw.js} +0 -0
  10. package/dist/{diagnostics-DYQC7xLV.js.map → diagnostics-H1QzgRSw.js.map} +1 -1
  11. package/dist/{enums-CoXnxiCI.d.ts → enums-CvRYniUA.d.ts} +13 -3
  12. package/dist/{enums-CoXnxiCI.d.ts.map → enums-CvRYniUA.d.ts.map} +1 -1
  13. package/dist/{errors-BeUEkNWL.d.ts → errors-fB1KDJKQ.d.ts} +5 -5
  14. package/dist/{errors-BeUEkNWL.d.ts.map → errors-fB1KDJKQ.d.ts.map} +1 -1
  15. package/dist/{fit-DhMbQG5O.js → fit-CYJ1jkQI.js} +0 -0
  16. package/dist/fit-CYJ1jkQI.js.map +1 -0
  17. package/dist/{html-dom-DajBuobe.js → html-dom-CzewEdE1.js} +4 -5
  18. package/dist/{html-dom-DajBuobe.js.map → html-dom-CzewEdE1.js.map} +1 -1
  19. package/dist/html.d.ts +3 -3
  20. package/dist/html.js +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.js +5 -6
  23. package/dist/index.js.map +1 -1
  24. package/dist/inspect.d.ts +1 -1
  25. package/dist/inspect.js +4 -4
  26. package/dist/math.d.ts +1 -1
  27. package/dist/measure.d.ts +96 -6
  28. package/dist/measure.d.ts.map +1 -1
  29. package/dist/measure.js +2 -2
  30. package/dist/node.d.ts +6 -6
  31. package/dist/node.js +4 -5
  32. package/dist/node.js.map +1 -1
  33. package/dist/notes-SCvpJz0l.js +1646 -0
  34. package/dist/notes-SCvpJz0l.js.map +1 -0
  35. package/dist/{pres-BgvxhQ_m.d.ts → pres-Cb-1xDne.d.ts} +4 -4
  36. package/dist/{pres-BgvxhQ_m.d.ts.map → pres-Cb-1xDne.d.ts.map} +1 -1
  37. package/dist/{presentation-BP1B8PCF.js → presentation-DeKTJGHf.js} +1096 -440
  38. package/dist/presentation-DeKTJGHf.js.map +1 -0
  39. package/dist/{presentation-DyIRDKkp.d.ts → presentation-types-B50aOUFI.d.ts} +804 -39
  40. package/dist/presentation-types-B50aOUFI.d.ts.map +1 -0
  41. package/dist/read.d.ts +4 -5
  42. package/dist/read.d.ts.map +1 -1
  43. package/dist/read.js +2 -2
  44. package/dist/script.d.ts +32 -4
  45. package/dist/script.d.ts.map +1 -1
  46. package/dist/script.js +0 -0
  47. package/dist/script.js.map +1 -1
  48. package/dist/{shapes-DMThLsZN.js → shapes-BXVgsk0F.js} +81 -63
  49. package/dist/shapes-BXVgsk0F.js.map +1 -0
  50. package/dist/{slide-BRAtT8Dl.d.ts → slide-DWw9UoI6.d.ts} +130 -11
  51. package/dist/slide-DWw9UoI6.d.ts.map +1 -0
  52. package/dist/{st-enums-evQG2A7Z.js → st-enums-bVnezkMJ.js} +33 -12
  53. package/dist/{st-enums-evQG2A7Z.js.map → st-enums-bVnezkMJ.js.map} +1 -1
  54. package/dist/{text-B6PbMFih.js → text-uM-zGton.js} +242 -1611
  55. package/dist/text-uM-zGton.js.map +1 -0
  56. package/dist/{units-internal-CR8s8YQa.js → units-internal-CGf4VrXy.js} +156 -4
  57. package/dist/units-internal-CGf4VrXy.js.map +1 -0
  58. package/dist/zip.d.ts +2 -2
  59. package/package.json +40 -28
  60. package/skills/ts-pptx-upstream/SKILL.md +107 -14
  61. package/dist/embedded-fonts-MaynAHa9.js +0 -94
  62. package/dist/embedded-fonts-MaynAHa9.js.map +0 -1
  63. package/dist/fit-DhMbQG5O.js.map +0 -1
  64. package/dist/presentation-BP1B8PCF.js.map +0 -1
  65. package/dist/presentation-DyIRDKkp.d.ts.map +0 -1
  66. package/dist/presentation-types-Dj4riuDs.d.ts +0 -538
  67. package/dist/presentation-types-Dj4riuDs.d.ts.map +0 -1
  68. package/dist/shapes-DMThLsZN.js.map +0 -1
  69. package/dist/slide-BRAtT8Dl.d.ts.map +0 -1
  70. package/dist/text-B6PbMFih.js.map +0 -1
  71. package/dist/units-C7QM1KgH.js +0 -144
  72. package/dist/units-C7QM1KgH.js.map +0 -1
  73. package/dist/units-internal-CR8s8YQa.js.map +0 -1
@@ -0,0 +1,1646 @@
1
+ import { n as warn, r as warnOnce } from "./diagnostics-H1QzgRSw.js";
2
+ import { n as InvalidOptionError, o as UnsupportedFeatureError } from "./errors-Bvln0H-7.js";
3
+ import { D as DEF_TEXT_GLOW, I as REGEX_HEX_COLOR, K as SchemeColor, L as SLDNUMFLDID, O as DEF_TEXT_SHADOW, R as XML_DECL, a as lineWidthToEmu, at as EMU_PER_POINT, ct as PERCENT_SCALE, i as inch2Emu, l as transparencyToAlpha, n as convertRotationDegrees, nt as ANGLE_UNITS_PER_DEGREE, ot as FIXED_PCT_PER_PERCENT, s as opacityToAlpha, u as valToPts, x as DEF_FONT_COLOR, yt as ptToHundredths } from "./units-internal-CGf4VrXy.js";
4
+ import { g as PACKAGE_REL_NS, h as OFFICE_REL } from "./st-enums-bVnezkMJ.js";
5
+ //#region src/gen/utils.ts
6
+ /**
7
+ * ts-pptx: shared generator helpers.
8
+ *
9
+ * The small cross-cutting pieces the OOXML writers all need and that belong to
10
+ * no single part:
11
+ * - XML text encodeXmlEntities
12
+ * - Identifiers & naming getUuid, validateObjectName, getDuplicateObjectNames
13
+ * - Slide relationships getNewRelId, isHyperlinkRel
14
+ *
15
+ * DrawingML fragment builders moved to `gen/drawingml/{color,effect,fill,line}.ts`;
16
+ * unit conversion to `units-internal.ts` (over the public primitives in `units.ts`);
17
+ * base64/image-header decoding and media content types to `media/`.
18
+ */
19
+ /**
20
+ * Basic UUID Generator Adapted
21
+ * @link https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript#answer-2117523
22
+ * @param {string} uuidFormat - UUID format
23
+ * @returns {string} UUID
24
+ */
25
+ function getUuid(uuidFormat) {
26
+ return uuidFormat.replace(/[xy]/g, function(c) {
27
+ const r = Math.random() * 16 | 0;
28
+ return (c === "x" ? r : r & 3 | 8).toString(16);
29
+ });
30
+ }
31
+ /**
32
+ * Replace special XML characters with HTML-encoded strings.
33
+ *
34
+ * ELEMENT TEXT ONLY. A literal tab, carriage return or line feed is left alone here because in
35
+ * character data it *is* the content (`<a:t>` line breaks depend on it). Inside an attribute value
36
+ * the same three characters are destroyed by the parser rather than preserved — see
37
+ * {@link encodeXmlAttrValue}, which every attribute-emitting path must use instead.
38
+ * @param {string | number} xml - value to encode (numbers are stringified, as callers pass counts/sizes)
39
+ * @returns {string} escaped XML
40
+ */
41
+ function encodeXmlEntities(xml) {
42
+ if (typeof xml === "undefined" || xml == null) return "";
43
+ const cc = String.fromCharCode;
44
+ const illegalXmlCharsRe = new RegExp(`[${cc(0)}-${cc(8)}${cc(11)}${cc(12)}${cc(14)}-${cc(31)}${cc(127)}]`, "g");
45
+ return xml.toString().replace(illegalXmlCharsRe, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
46
+ }
47
+ /**
48
+ * Escape a value destined for an XML **attribute**.
49
+ *
50
+ * Everything {@link encodeXmlEntities} does, plus the three whitespace characters that survive in
51
+ * element text but not in an attribute: XML 1.0 section 3.3.3 requires a parser to normalise a
52
+ * literal tab, carriage return or line feed inside an attribute value to a single space *before any
53
+ * consumer sees it*. Carrying one across therefore requires a character reference, so a caller's
54
+ * `objectName: 'Abschnitts-\nuberschrift'` reads back with its line break rather than a space.
55
+ *
56
+ * Deliberately a separate function rather than a widening of `encodeXmlEntities`: that helper also
57
+ * escapes element text, where a raw newline is meaningful content emitted as-is, and escaping it
58
+ * there would change bytes across every text-bearing part in the package.
59
+ * @param {string | number} xml - value to encode (numbers are stringified, as callers pass ids/sizes)
60
+ * @returns {string} escaped XML, safe to place between attribute quotes
61
+ */
62
+ function encodeXmlAttrValue(xml) {
63
+ return encodeXmlEntities(xml).replace(/\t/g, "&#9;").replace(/\n/g, "&#10;").replace(/\r/g, "&#13;");
64
+ }
65
+ /**
66
+ * Practical maximum length for a `p:cNvPr` object name. PowerPoint does not
67
+ * enforce a hard spec limit, but very long names are a strong signal of a bug
68
+ * and are unwieldy in the Selection Pane.
69
+ */
70
+ const MAX_OBJECT_NAME_LENGTH = 255;
71
+ /**
72
+ * Validate a user-supplied object name and warn (does not throw) when the value
73
+ * cannot be preserved as a stable PowerPoint Selection Pane identity. This keeps
74
+ * semantic-identity bugs visible at generation time without breaking existing
75
+ * decks that pass loose names.
76
+ * - Empty/whitespace-only names provide no usable identity.
77
+ * - Control characters are stripped by `encodeXmlEntities`, silently changing
78
+ * the stored name.
79
+ * - Excessively long names may not round-trip through PowerPoint/consumers.
80
+ * @param {string} name - the raw (pre-encoding) object name
81
+ * @param {string} kind - object kind for the warning message (e.g. 'text')
82
+ * @returns {string} the name unchanged (validation only)
83
+ */
84
+ function validateObjectName(name, kind) {
85
+ if (typeof name !== "string") return name;
86
+ if (name.trim().length === 0) {
87
+ warn("object-name/empty", `${kind} objectName is empty or whitespace-only; it will not provide a stable Selection Pane identity.`);
88
+ return name;
89
+ }
90
+ const cc = String.fromCharCode;
91
+ if (new RegExp(`[${cc(0)}-${cc(8)}${cc(11)}${cc(12)}${cc(14)}-${cc(31)}${cc(127)}]`).test(name)) warn("object-name/control-characters", `${kind} objectName "${name}" contains control characters that will be stripped, changing the stored name.`);
92
+ if (name.length > MAX_OBJECT_NAME_LENGTH) warn("object-name/too-long", `${kind} objectName exceeds ${MAX_OBJECT_NAME_LENGTH} characters and may not be preserved by PowerPoint.`);
93
+ return name;
94
+ }
95
+ /**
96
+ * Return object names that appear more than once in the given list. Used to warn
97
+ * when duplicate Selection Pane identities would be emitted on a single slide,
98
+ * which breaks consumers (e.g. semantic manifests) that rely on unique names.
99
+ * @param {string[]} names - object names emitted on one slide
100
+ * @returns {string[]} the duplicated names (each listed once)
101
+ */
102
+ function getDuplicateObjectNames(names) {
103
+ const seen = /* @__PURE__ */ new Set();
104
+ const dupes = /* @__PURE__ */ new Set();
105
+ names.forEach((name) => {
106
+ if (typeof name !== "string" || name.length === 0) return;
107
+ if (seen.has(name)) dupes.add(name);
108
+ else seen.add(name);
109
+ });
110
+ return Array.from(dupes);
111
+ }
112
+ /**
113
+ * Get a new rel ID (rId) for charts, media, etc.
114
+ *
115
+ * Counting is the normal case: a slide's rels are minted in order, so one past the
116
+ * total is free. It is not enough on its own, because a rel can arrive on a slide
117
+ * carrying an id minted somewhere else — auto-paging re-registers a repeated header
118
+ * row's hyperlink on each overflow slide under the id stamped on the shared
119
+ * hyperlink object (`gen/define/hyperlinks.ts`). That id is then held but not
120
+ * counted-to, so the next mint could hand out the same number and the part would
121
+ * declare two `Relationship` elements sharing an `Id` — invalid OPC, and PowerPoint
122
+ * resolves such a reference to whichever comes first. Hence the scan: start where
123
+ * the count says, then step over anything the slide already holds.
124
+ * @param {PresSlideInternal} target - the slide to use
125
+ * @returns {number} the lowest free rId at or above the current rel count plus 1
126
+ */
127
+ function getNewRelId(target) {
128
+ const held = /* @__PURE__ */ new Set();
129
+ target._rels.forEach((rel) => held.add(rel.rId));
130
+ target._relsChart.forEach((rel) => held.add(rel.rId));
131
+ target._relsMedia.forEach((rel) => held.add(rel.rId));
132
+ let rId = target._rels.length + target._relsChart.length + target._relsMedia.length + 1;
133
+ while (held.has(rId)) rId++;
134
+ return rId;
135
+ }
136
+ /**
137
+ * Whether a slide relationship is a hyperlink (external URL or internal slide
138
+ * link). The relationship `type` is stringly-typed (`'hyperlink'`, `'online'`,
139
+ * mixed-case variants), so this centralizes the case-insensitive predicate that
140
+ * was duplicated across the slide-rels writer (`gen/slide/slide.ts`) and the inspect path
141
+ * (presentation.ts). For an internal slide-to-slide link, `rel.data === 'slide'` and
142
+ * `rel.Target` is the 1-based target slide number.
143
+ * @param {{ type: string }} rel - a slide relationship
144
+ * @returns {boolean} true if the rel is any kind of hyperlink
145
+ */
146
+ function isHyperlinkRel(rel) {
147
+ return rel.type.toLowerCase().includes("hyperlink");
148
+ }
149
+ //#endregion
150
+ //#region src/gen/oxml/el.ts
151
+ /**
152
+ * ts-pptx: XML element builder (write-side substrate)
153
+ *
154
+ * The write path historically concatenated template strings and called
155
+ * `encodeXmlEntities` by hand at every interpolation — the source of escaping,
156
+ * attribute-order and child-sequence bugs. This builder centralizes escaping
157
+ * while reproducing today's exact byte layout, so a migration onto it can be
158
+ * gated on strict byte-identity (`pnpm run byte-identity:check`) rather than on
159
+ * looser schema equivalence.
160
+ *
161
+ * Mirrors `src/read/oxml/dom.ts` on the write side.
162
+ *
163
+ * Two rules make byte-identity achievable:
164
+ *
165
+ * 1. Self-closing is decided by ARITY, never by child value. `el()` always emits
166
+ * a paired tag; `voidEl()` always self-closes. This matters because
167
+ * `encodeXmlEntities(undefined)` returns `''`, so a value-based rule would
168
+ * turn today's `<dc:title></dc:title>` into `<dc:title/>` — a silent byte
169
+ * regression on every optional-text element in the tree.
170
+ *
171
+ * Escaping is split to match: attribute values go through `encodeXmlAttrValue`
172
+ * (which adds the `&#9;`/`&#10;`/`&#13;` character references an XML parser
173
+ * would otherwise normalise to a space), text children through
174
+ * `encodeXmlEntities`, where those same characters are content.
175
+ *
176
+ * 2. Whitespace is explicit, per element, via `fmt`. Most emitted parts are flat
177
+ * (no `fmt` needed at all); the pretty-printed ones carry indentation that is
178
+ * not always depth-regular, so it is described rather than derived. Quirks
179
+ * like a misindented closing tag stay visible in the call site instead of
180
+ * being hidden inside a template literal.
181
+ */
182
+ /** Wrap already-serialized XML so it is interpolated verbatim. */
183
+ function raw(xml) {
184
+ return { raw: xml };
185
+ }
186
+ function isRaw(child) {
187
+ return typeof child === "object" && child !== null && typeof child.raw === "string";
188
+ }
189
+ function openTag(name, attrs, fmt) {
190
+ let out = (fmt?.openPrefix ?? "") + "<" + name;
191
+ for (const [key, value] of Object.entries(attrs ?? {})) {
192
+ if (value === void 0 || value === null) continue;
193
+ out += " " + key + "=\"" + encodeXmlAttrValue(value) + "\"";
194
+ }
195
+ return out;
196
+ }
197
+ /**
198
+ * Paired element: `<name …>children</name>` — emitted even when `children` is
199
+ * empty or `undefined`. Text children are escaped; `raw()` children are not.
200
+ */
201
+ function el(name, attrs, children, fmt) {
202
+ let out = openTag(name, attrs, fmt) + ">";
203
+ const childPrefix = fmt?.childPrefix ?? "";
204
+ for (const child of Array.isArray(children) ? children : [children]) {
205
+ if (child === void 0 || child === null) continue;
206
+ out += childPrefix + (isRaw(child) ? child.raw : encodeXmlEntities(child));
207
+ }
208
+ return out + (fmt?.closePrefix ?? "") + "</" + name + ">";
209
+ }
210
+ /**
211
+ * Self-closing element: `<name …/>`. `fmt.closePrefix` goes before the `/>` — a
212
+ * handful of emitters write `<a:avLst />` with a space there, and that space is
213
+ * byte-significant like any other.
214
+ */
215
+ function voidEl(name, attrs, fmt) {
216
+ return openTag(name, attrs, fmt) + (fmt?.closePrefix ?? "") + "/>";
217
+ }
218
+ //#endregion
219
+ //#region src/gen/drawingml/color.ts
220
+ /**
221
+ * ts-pptx: DrawingML color elements
222
+ *
223
+ * Emit the bare `<a:srgbClr>` / `<a:schemeClr>` color element that every other
224
+ * color context builds on (solid fills, gradient stops, effect alphas, line
225
+ * fills, highlights, patterns).
226
+ *
227
+ * The RGB->hex helpers this module used to carry now live in
228
+ * `gen/table/html-dom.ts`, their only caller: they are reachable exclusively from
229
+ * the browser-only `tableToSlides` path, so keeping them here made them permanent
230
+ * dead weight in the Node chunk's coverage report (33% functions on a file that is
231
+ * otherwise fully exercised) while html-dom.ts is coverage-excluded wholesale.
232
+ */
233
+ /**
234
+ * Opening delimiter of an `<a:alpha>` child, for detecting one a caller already
235
+ * supplied. A prefix rather than a whole element: the value is the caller's, and
236
+ * this asks only whether the element is present.
237
+ */
238
+ const ALPHA_TAG = "<a:alpha";
239
+ /**
240
+ * Emit a bare DrawingML color element — either `<a:schemeClr>` (scheme color) or `<a:srgbClr>`
241
+ * (hex RGB). This is a low-level primitive: it is the shared building block for every color
242
+ * context (solid fills, gradient stops, `<a:alpha>` on shadows/glow, line fills, highlight,
243
+ * underline, patterns, …), so it is intentionally NOT limited to solid fills and cannot be
244
+ * folded into `genXmlColorSelection`.
245
+ *
246
+ * When you specifically want a *solid fill*, prefer `genXmlColorSelection` (`fill.ts`), which
247
+ * wraps this in `<a:solidFill>…</a:solidFill>` and also handles `alpha`/`transparency` and the
248
+ * gradient/pattern/image fill types — reach for `createColorElement` directly only when you
249
+ * need the raw color element (e.g. inside `<a:ln>`, `<a:gs>`, an effect, or a highlight).
250
+ * @param {string|SCHEME_COLORS} colorStr - hex RGB (e.g. "FFFF00") or a scheme color constant (e.g. SchemeColor.ACCENT1)
251
+ * @param {string} [innerElements] - additional elements that adjust the color, nested inside the color element
252
+ * @returns {string} XML string
253
+ */
254
+ function createColorElement(colorStr, innerElements) {
255
+ if (typeof colorStr !== "string") {
256
+ warn("color/not-a-string", `createColorElement: expected a string color value, got ${typeof colorStr}. "${DEF_FONT_COLOR}" used instead.`);
257
+ colorStr = DEF_FONT_COLOR;
258
+ }
259
+ let colorVal = (colorStr || "").replace("#", "");
260
+ if (/^[0-9a-fA-F]{8}$/.test(colorVal)) {
261
+ if (!innerElements?.includes(ALPHA_TAG)) {
262
+ const alphaHex = colorVal.slice(6, 8);
263
+ innerElements = voidEl("a:alpha", { val: Math.round(parseInt(alphaHex, 16) / 255 * PERCENT_SCALE) }) + (innerElements || "");
264
+ }
265
+ colorVal = colorVal.slice(0, 6);
266
+ }
267
+ if (!REGEX_HEX_COLOR.test(colorVal) && !Object.values(SchemeColor).includes(colorVal)) {
268
+ warn("color/invalid-value", `"${colorVal}" is not a valid scheme color or hex RGB! "${DEF_FONT_COLOR}" used instead. Only provide 6-digit RGB or 'SchemeColor' values!`);
269
+ colorVal = DEF_FONT_COLOR;
270
+ }
271
+ const isHex = REGEX_HEX_COLOR.test(colorVal);
272
+ const name = isHex ? "a:srgbClr" : "a:schemeClr";
273
+ const attrs = { val: isHex ? colorVal.toUpperCase() : colorVal };
274
+ return innerElements ? el(name, attrs, raw(innerElements)) : voidEl(name, attrs);
275
+ }
276
+ //#endregion
277
+ //#region src/gen/drawingml/effect.ts
278
+ /**
279
+ * ts-pptx: DrawingML effects (glow and shadow)
280
+ *
281
+ * Emit the `<a:glow>` / `<a:outerShdw>` / `<a:innerShdw>` children of an
282
+ * `<a:effectLst>`, and normalize the user-facing `shadow` options into the
283
+ * internal shape every emit site reads.
284
+ * @see http://officeopenxml.com/drwSp-effects.php
285
+ */
286
+ /**
287
+ * Creates `a:glow` element
288
+ * @param {TextGlowProps} options glow properties
289
+ * @param {TextGlowProps} defaults defaults for unspecified properties in `opts`
290
+ * @see http://officeopenxml.com/drwSp-effects.php
291
+ * { size: 8, color: 'FFFFFF', opacity: 0.75 };
292
+ */
293
+ function createGlowElement(options, defaults) {
294
+ const opts = {
295
+ ...defaults,
296
+ ...options
297
+ };
298
+ const size = Math.round(opts.size * EMU_PER_POINT);
299
+ const color = opts.color || "000000";
300
+ const opacity = opacityToAlpha(opts.opacity ?? 0);
301
+ return el("a:glow", { rad: size }, raw(createColorElement(color, alpha(opacity))));
302
+ }
303
+ /** The `<a:alpha>` child both effects hang off their color element. */
304
+ function alpha(value) {
305
+ return voidEl("a:alpha", { val: value });
306
+ }
307
+ /**
308
+ * Creates an `a:outerShdw`/`a:innerShdw` element for a text run, shape, image, or chart.
309
+ * Returns the shadow element only (no wrapping `a:effectLst`) so callers can either combine
310
+ * it with other effects (e.g. glow) inside one `a:effectLst`, or wrap a lone shadow via
311
+ * {@link createShadowEffectLst}.
312
+ *
313
+ * Colors go through {@link createColorElement}, so scheme colors (e.g. `accent1`) are honored
314
+ * — earlier per-site copies hardcoded `a:srgbClr` and silently emitted invalid OOXML for them.
315
+ * @param {ShadowPropsInternal} options shadow properties
316
+ * @param {ShadowPropsInternal} defaults defaults for unspecified properties in `options`
317
+ * @see http://officeopenxml.com/drwSp-effects.php
318
+ * @returns {string} XML string, or '' when type is 'none'
319
+ */
320
+ function createShadowElement(options, defaults) {
321
+ const opts = {
322
+ ...defaults,
323
+ ...options
324
+ };
325
+ if (opts.type === "none") return "";
326
+ const type = opts.type || "outer";
327
+ const blur = valToPts(opts.blur ?? 0);
328
+ const offset = valToPts(opts.offset ?? 0);
329
+ const angle = Math.round((opts.angle ?? 0) * ANGLE_UNITS_PER_DEGREE);
330
+ const opacity = Math.round((opts._alpha ?? .75) * PERCENT_SCALE);
331
+ const color = opts.color || "000000";
332
+ const attrs = {
333
+ ...type === "outer" ? {
334
+ sx: 1e5,
335
+ sy: 1e5,
336
+ kx: 0,
337
+ ky: 0,
338
+ algn: "bl",
339
+ rotWithShape: opts.rotateWithShape ? 1 : 0
340
+ } : {},
341
+ blurRad: blur,
342
+ dist: offset,
343
+ dir: angle
344
+ };
345
+ return el(`a:${type}Shdw`, attrs, raw(createColorElement(color, alpha(opacity))));
346
+ }
347
+ /**
348
+ * Wraps a lone shadow in the `a:effectLst` that CT_ShapeProperties (shapes/images) and chart
349
+ * marker/data-point properties require. Returns a self-closing `<a:effectLst/>` when there is
350
+ * no shadow (missing/non-object options, or `type: 'none'`), matching the "no effects" element
351
+ * PowerPoint emits. Use this instead of {@link createShadowElement} when the shadow is the only
352
+ * effect; use `createShadowElement` directly when combining with other effects (e.g. glow).
353
+ * @param {ShadowPropsInternal} options shadow properties
354
+ * @param {ShadowPropsInternal} defaults defaults for unspecified properties in `options`
355
+ * @returns {string} `<a:effectLst>…</a:effectLst>` or `<a:effectLst/>`
356
+ */
357
+ function createShadowEffectLst(options, defaults) {
358
+ if (!options || typeof options !== "object") return voidEl("a:effectLst");
359
+ const inner = createShadowElement(options, defaults);
360
+ return inner ? el("a:effectLst", null, raw(inner)) : voidEl("a:effectLst");
361
+ }
362
+ /**
363
+ * Checks shadow options passed by user and performs corrections if needed.
364
+ * @param {ShadowProps} ShadowProps - shadow options
365
+ */
366
+ function correctShadowOptions(ShadowProps) {
367
+ if (!ShadowProps || typeof ShadowProps !== "object") return;
368
+ const corrected = ShadowProps;
369
+ if (corrected.type !== "outer" && corrected.type !== "inner" && corrected.type !== "none") {
370
+ warn("shadow/invalid-type", "shadow.type options are `outer`, `inner` or `none`.");
371
+ corrected.type = "outer";
372
+ }
373
+ if (corrected.angle) {
374
+ if (isNaN(Number(corrected.angle)) || corrected.angle < 0 || corrected.angle > 359) {
375
+ warn("shadow/angle-out-of-range", "shadow.angle can only be 0-359");
376
+ corrected.angle = 270;
377
+ }
378
+ corrected.angle = Math.round(Number(corrected.angle));
379
+ }
380
+ if (corrected.transparency !== void 0) {
381
+ const pct = Number(corrected.transparency);
382
+ if (isNaN(pct) || pct < 0 || pct > 100) warn("shadow/transparency-out-of-range", "shadow.transparency can only be 0-100");
383
+ else corrected._alpha = 1 - pct / 100;
384
+ }
385
+ if (corrected.color) {
386
+ if (corrected.color.startsWith("#")) {
387
+ warn("shadow/color-has-hash", "shadow.color should not include hash (#) character, , e.g. \"FF0000\"");
388
+ corrected.color = corrected.color.replace("#", "");
389
+ }
390
+ if (/^[0-9a-fA-F]{8}$/.test(corrected.color)) {
391
+ const alphaHex = corrected.color.slice(6, 8);
392
+ if (corrected._alpha === void 0) corrected._alpha = parseInt(alphaHex, 16) / 255;
393
+ corrected.color = corrected.color.slice(0, 6);
394
+ }
395
+ }
396
+ return corrected;
397
+ }
398
+ //#endregion
399
+ //#region src/embedded-fonts.ts
400
+ /**
401
+ * Shared model + serializer for PresentationML embedded fonts, used by both the
402
+ * write side (author-side `pptx.embedFont`, `gen/pres/presentation.ts`) and the read side
403
+ * (`importSlide({ embedFonts: true })`, `src/read/api/ops/embedded-fonts.ts`).
404
+ *
405
+ * Embedded fonts are three coordinated pieces (ECMA-376 transitional):
406
+ * 1. binary font parts `/ppt/fonts/fontN.fntdata` — the **raw** TTF/OTF bytes
407
+ * (PresentationML does not obfuscate embedded fonts), content type
408
+ * `application/x-fontdata` via a single `fntdata` Default extension;
409
+ * 2. one `font` relationship per face from `presentation.xml`;
410
+ * 3. a `p:embeddedFontLst` in `presentation.xml`, at index 7 of the
411
+ * `CT_Presentation` child sequence (after `smartTags`, before `custShowLst`
412
+ * … `defaultTextStyle`).
413
+ *
414
+ * This module owns only the OOXML-shape knowledge; rId allocation and part
415
+ * placement stay with each caller (their packaging models differ).
416
+ */
417
+ /** Extension for the binary font parts (`/ppt/fonts/fontN.fntdata`). */
418
+ const FONT_DATA_EXTENSION = "fntdata";
419
+ /** Content type for a `.fntdata` part — one `Default` covers every font part. */
420
+ const FONT_DATA_CONTENT_TYPE = "application/x-fontdata";
421
+ /** Relationship type from `presentation.xml` to a font part (one per face). */
422
+ const FONT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
423
+ /**
424
+ * The four face slots of `CT_EmbeddedFontListEntry`, in schema child order
425
+ * (`font` first, then these). Iterate in this order everywhere so the read- and
426
+ * write-side emitters agree on element order.
427
+ */
428
+ const EMBEDDED_FONT_SLOTS = [
429
+ "regular",
430
+ "bold",
431
+ "italic",
432
+ "boldItalic"
433
+ ];
434
+ /**
435
+ * Flatten `fonts` into one ordered face list, assigning sequential part indices
436
+ * (1-based) and relationship ids (starting at `firstRId`). Faces are emitted in
437
+ * `fonts` order, each font's faces in {@link EMBEDDED_FONT_SLOTS} order. Faces
438
+ * without `bytes` are skipped (nothing to write).
439
+ */
440
+ function flattenEmbeddedFaces(fonts, firstRId) {
441
+ const flat = [];
442
+ let partIndex = 0;
443
+ let rId = firstRId;
444
+ fonts.forEach((font, fontIndex) => {
445
+ for (const slot of EMBEDDED_FONT_SLOTS) {
446
+ const face = font.faces.find((f) => f.slot === slot);
447
+ if (!face || !face.bytes) continue;
448
+ flat.push({
449
+ fontIndex,
450
+ slot,
451
+ bytes: face.bytes,
452
+ partIndex: ++partIndex,
453
+ rId: rId++
454
+ });
455
+ }
456
+ });
457
+ return flat;
458
+ }
459
+ /** Minimal XML attribute escaper (kept local so this module stays dependency-free). */
460
+ function escapeAttr(value) {
461
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
462
+ }
463
+ /**
464
+ * Serialize a `<p:embeddedFontLst>` from `fonts` and a `(fontIndex, slot) → rId`
465
+ * lookup (typically backed by {@link flattenEmbeddedFaces}). Returns `''` when no
466
+ * font has any face with an allocated rId, so callers can emit nothing. Assumes
467
+ * the enclosing document declares the `p:` and `r:` prefixes (presentation.xml
468
+ * does), so no namespace declarations are emitted here.
469
+ */
470
+ function serializeEmbeddedFontLst(fonts, rIdForFace) {
471
+ const entries = [];
472
+ fonts.forEach((font, fontIndex) => {
473
+ const faceXml = [];
474
+ for (const slot of EMBEDDED_FONT_SLOTS) {
475
+ const rId = rIdForFace(fontIndex, slot);
476
+ if (rId === void 0) continue;
477
+ faceXml.push(`<p:${slot} r:id="rId${rId}"/>`);
478
+ }
479
+ if (faceXml.length === 0) return;
480
+ let fontAttrs = `typeface="${escapeAttr(font.typeface)}"`;
481
+ if (font.panose !== void 0) fontAttrs += ` panose="${escapeAttr(font.panose)}"`;
482
+ if (font.pitchFamily !== void 0) fontAttrs += ` pitchFamily="${font.pitchFamily}"`;
483
+ if (font.charset !== void 0) fontAttrs += ` charset="${font.charset}"`;
484
+ entries.push(`<p:embeddedFont><p:font ${fontAttrs}/>${faceXml.join("")}</p:embeddedFont>`);
485
+ });
486
+ if (entries.length === 0) return "";
487
+ return `<p:embeddedFontLst>${entries.join("")}</p:embeddedFontLst>`;
488
+ }
489
+ //#endregion
490
+ //#region src/gen/drawingml/fill.ts
491
+ /**
492
+ * ts-pptx: DrawingML fills
493
+ *
494
+ * Emit the fill group a shape, cell, chart element, or line paint can carry:
495
+ * `<a:solidFill>`, `<a:gradFill>`, `<a:pattFill>`, `<a:blipFill>`.
496
+ * {@link genXmlColorSelection} is the dispatch every caller should reach for;
497
+ * the per-kind builders are exported for the few sites that know their fill kind.
498
+ */
499
+ function boolToXml(value) {
500
+ return value ? "1" : "0";
501
+ }
502
+ function normalizeGradientAngle(angle) {
503
+ const degrees = angle ?? 0;
504
+ if (typeof degrees !== "number" || !Number.isFinite(degrees)) throw new InvalidOptionError("gradient/angle-non-finite", "Gradient angle must be a finite number.");
505
+ return convertRotationDegrees((degrees % 360 + 360) % 360);
506
+ }
507
+ function alphaFromTransparency(transparency) {
508
+ return transparency ? voidEl("a:alpha", { val: transparencyToAlpha(transparency) }) : "";
509
+ }
510
+ function normalizeGradientStops(stops) {
511
+ if (!Array.isArray(stops) || stops.length < 2) throw new InvalidOptionError("gradient/too-few-stops", "Gradient fill requires at least two stops.");
512
+ return stops.map((stop) => {
513
+ if (!stop || typeof stop.position !== "number" || !Number.isFinite(stop.position)) throw new InvalidOptionError("gradient/stop-position-non-finite", "Gradient stop position must be a finite number from 0 to 100.");
514
+ if (stop.position < 0 || stop.position > 100) throw new InvalidOptionError("gradient/stop-position-out-of-range", "Gradient stop position must be from 0 to 100.");
515
+ return stop;
516
+ }).sort((a, b) => a.position - b.position);
517
+ }
518
+ /**
519
+ * Create a native DrawingML gradient fill.
520
+ * @param {GradientFillProps} gradient gradient fill options
521
+ * @returns XML string
522
+ */
523
+ function genXmlGradientFill(gradient) {
524
+ if (!gradient || gradient.kind !== "linear" && gradient.kind !== "radial") throw new UnsupportedFeatureError("gradient/type-unsupported", "Gradient fill currently supports only linear and radial gradients.");
525
+ if (typeof gradient.rotateWithShape !== "undefined" && typeof gradient.rotateWithShape !== "boolean") throw new InvalidOptionError("gradient/rotate-with-shape-not-boolean", "Gradient rotateWithShape must be a boolean.");
526
+ const stops = normalizeGradientStops(gradient.stops);
527
+ const rotWithShape = gradient.rotateWithShape ?? true;
528
+ const gsLst = el("a:gsLst", null, stops.map((stop) => raw(el("a:gs", { pos: Math.round(stop.position * FIXED_PCT_PER_PERCENT) }, raw(createColorElement(stop.color, alphaFromTransparency(stop.transparency)))))));
529
+ let shape;
530
+ if (gradient.kind === "radial") {
531
+ const cx = Math.max(0, Math.min(100, gradient.center?.x ?? 50));
532
+ const cy = Math.max(0, Math.min(100, gradient.center?.y ?? 50));
533
+ shape = el("a:path", { path: "circle" }, raw(voidEl("a:fillToRect", {
534
+ l: Math.round(cx * FIXED_PCT_PER_PERCENT),
535
+ t: Math.round(cy * FIXED_PCT_PER_PERCENT),
536
+ r: Math.round((100 - cx) * FIXED_PCT_PER_PERCENT),
537
+ b: Math.round((100 - cy) * FIXED_PCT_PER_PERCENT)
538
+ })));
539
+ } else {
540
+ if (typeof gradient.scaled !== "undefined" && typeof gradient.scaled !== "boolean") throw new InvalidOptionError("gradient/scaled-not-boolean", "Gradient scaled must be a boolean.");
541
+ shape = voidEl("a:lin", {
542
+ ang: normalizeGradientAngle(gradient.angle),
543
+ scaled: typeof gradient.scaled === "boolean" ? boolToXml(gradient.scaled) : void 0
544
+ });
545
+ }
546
+ return el("a:gradFill", { rotWithShape: boolToXml(rotWithShape) }, [raw(gsLst), raw(shape)]);
547
+ }
548
+ /**
549
+ * Create a native DrawingML pattern fill.
550
+ * @param {PatternFillProps} pattern pattern fill options
551
+ * @returns XML string
552
+ */
553
+ function genXmlPatternFill(pattern) {
554
+ if (!pattern) throw new InvalidOptionError("pattern-fill/missing-pattern", "Pattern fill requires a pattern object.");
555
+ const fgColor = pattern.fgColor ?? "000000";
556
+ const bgColor = pattern.bgColor ?? "FFFFFF";
557
+ return el("a:pattFill", { prst: pattern.preset }, [raw(el("a:fgClr", null, raw(createColorElement(fgColor)))), raw(el("a:bgClr", null, raw(createColorElement(bgColor))))]);
558
+ }
559
+ /**
560
+ * Create a native DrawingML picture (image) fill.
561
+ * The media relationship is registered when the object is added; this only emits
562
+ * the `<a:blipFill>` referencing the pre-resolved rId.
563
+ * @param {ShapeFillProps} props fill props (must carry a resolved `_imgRid`)
564
+ * @returns XML string
565
+ */
566
+ function genXmlImageFill(props) {
567
+ if (!props || typeof props._imgRid !== "number") {
568
+ warn("image-fill/unresolved-media", "image fill is missing its resolved media reference; falling back to no fill. Provide `image: { path }` or `image: { data }`.");
569
+ return voidEl("a:noFill");
570
+ }
571
+ const alpha = props.transparency;
572
+ const blipInner = alpha ? voidEl("a:alphaModFix", { amt: Math.round((100 - alpha) * FIXED_PCT_PER_PERCENT) }) : "";
573
+ return el("a:blipFill", {
574
+ dpi: 0,
575
+ rotWithShape: 1
576
+ }, [
577
+ raw(el("a:blip", { "r:embed": `rId${props._imgRid}` }, raw(blipInner))),
578
+ raw(voidEl("a:srcRect")),
579
+ raw(el("a:stretch", null, raw(voidEl("a:fillRect"))))
580
+ ]);
581
+ }
582
+ /**
583
+ * Create color selection
584
+ * @param {Color | ShapeFillProps | ShapeLineProps} props fill props
585
+ * @returns XML string
586
+ */
587
+ function genXmlColorSelection(props) {
588
+ let fillType = "solid";
589
+ let colorVal = "";
590
+ let internalElements = "";
591
+ let outText = "";
592
+ if (props) {
593
+ if (typeof props === "string") colorVal = props;
594
+ else {
595
+ if (props.type) fillType = props.type;
596
+ if (props.color) colorVal = props.color;
597
+ internalElements += alphaFromTransparency(props.transparency);
598
+ }
599
+ switch (fillType) {
600
+ case "none":
601
+ outText += voidEl("a:noFill");
602
+ break;
603
+ case "inherit":
604
+ outText += "";
605
+ break;
606
+ case "solid":
607
+ outText += el("a:solidFill", null, raw(createColorElement(colorVal, internalElements)));
608
+ break;
609
+ case "gradient":
610
+ outText += genXmlGradientFill(typeof props === "string" ? void 0 : props.gradient);
611
+ break;
612
+ case "pattern":
613
+ outText += genXmlPatternFill(typeof props === "string" ? void 0 : props.pattern);
614
+ break;
615
+ case "image":
616
+ outText += genXmlImageFill(typeof props === "string" ? void 0 : props);
617
+ break;
618
+ default: outText += "";
619
+ }
620
+ }
621
+ return outText;
622
+ }
623
+ //#endregion
624
+ //#region src/gen/drawingml/clamp.ts
625
+ /**
626
+ * ts-pptx: DrawingML value clamps
627
+ *
628
+ * Clamp font/character/line spacing values (points) into their ST_* schema
629
+ * ranges and convert to the hundredths-of-a-point units the attributes expect.
630
+ * Out-of-range values make PowerPoint report the package as needing repair.
631
+ */
632
+ /**
633
+ * Clamp a font size (points) into ST_TextFontSize (1-4000pt) and return it in
634
+ * hundredths of a point for the `sz` attribute. Out-of-range sizes make
635
+ * PowerPoint report the package as needing repair (e.g. `sz` > 400000 or < 100).
636
+ */
637
+ function clampFontSizeSz(fontSizePts) {
638
+ const raw = ptToHundredths(fontSizePts);
639
+ const clamped = Math.min(4e5, Math.max(100, raw));
640
+ if (clamped !== raw) warnOnce("font/size-out-of-range", `fontSize ${fontSizePts} is outside the valid range 1-4000pt; using ${clamped / 100}.`);
641
+ return clamped;
642
+ }
643
+ /** Clamp character spacing (points) into ST_TextPoint (-4000..4000pt); returns hundredths for the `spc` attribute. */
644
+ function clampCharSpacingSpc(charSpacingPts) {
645
+ const raw = ptToHundredths(charSpacingPts);
646
+ const clamped = Math.min(4e5, Math.max(-4e5, raw));
647
+ if (clamped !== raw) warnOnce("text/char-spacing-out-of-range", `charSpacing ${charSpacingPts} is outside the valid range -4000..4000pt; using ${clamped / 100}.`);
648
+ return clamped;
649
+ }
650
+ /**
651
+ * Clamp a paragraph's left margin (points) into ST_TextMargin (0..4032pt, i.e. 0..51206400 EMU);
652
+ * returns EMU for `a:pPr/@marL`. The type is unsigned — a negative margin is not a narrower one,
653
+ * it is a value PowerPoint reports as needing repair.
654
+ */
655
+ function clampParaMarginEmu(marginPts) {
656
+ const raw = valToPts(marginPts);
657
+ const clamped = Math.min(51206400, Math.max(0, raw));
658
+ if (clamped !== raw) warnOnce("text/paragraph-margin-out-of-range", `paraMarginLeft ${marginPts} is outside the valid range 0-4032pt; using ${clamped / EMU_PER_POINT}.`);
659
+ return clamped;
660
+ }
661
+ /**
662
+ * Clamp a first-line indent (points) into ST_TextIndent (-4032..4032pt); returns EMU for
663
+ * `a:pPr/@indent`. Unlike the margin this one is signed: a negative indent is the hanging
664
+ * indent every bulleted paragraph uses.
665
+ */
666
+ function clampParaIndentEmu(indentPts) {
667
+ const raw = valToPts(indentPts);
668
+ const clamped = Math.min(51206400, Math.max(-51206400, raw));
669
+ if (clamped !== raw) warnOnce("text/paragraph-indent-out-of-range", `paraIndent ${indentPts} is outside the valid range -4032..4032pt; using ${clamped / EMU_PER_POINT}.`);
670
+ return clamped;
671
+ }
672
+ /** Clamp line spacing (points) into ST_TextSpacingPoint (0..1584pt); returns hundredths for `<a:spcPts val>`. */
673
+ function clampLineSpacingPts(lineSpacingPts) {
674
+ const raw = ptToHundredths(lineSpacingPts);
675
+ const clamped = Math.min(158400, Math.max(0, raw));
676
+ if (clamped !== raw) warnOnce("text/line-spacing-out-of-range", `lineSpacing ${lineSpacingPts} is outside the valid range 0-1584pt; using ${clamped / 100}.`);
677
+ return clamped;
678
+ }
679
+ //#endregion
680
+ //#region src/gen/drawingml/math.ts
681
+ /**
682
+ * ts-pptx: DrawingML native equations (OMML)
683
+ *
684
+ * Wrap raw OMML markup into the `<a14:m>` marker PowerPoint uses for editable
685
+ * equations — as a standalone display paragraph (`genXmlMathParagraph`) or as an
686
+ * inline run that flows between the surrounding `<a:r>` runs (`genXmlInlineMath`).
687
+ */
688
+ const M_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math";
689
+ const A14_NS = "http://schemas.microsoft.com/office/drawing/2010/main";
690
+ /** The `<a14:m>` marker, carrying both namespace declarations so the supplied OMML needs none. */
691
+ function a14Math(mathXml) {
692
+ return el("a14:m", {
693
+ "xmlns:a14": A14_NS,
694
+ "xmlns:m": M_NS
695
+ }, raw(mathXml));
696
+ }
697
+ /**
698
+ * Build a native-equation paragraph (`<a:p>`) from raw OMML.
699
+ *
700
+ * PowerPoint stores an editable equation inside a text body as an `<a14:m>` marker wrapping
701
+ * `<m:oMathPara><m:oMath>…`. We declare both the `a14` (drawing-2010) and `m` (math) namespaces
702
+ * on the `<a14:m>` element so the supplied OMML needs no namespace declarations of its own, then
703
+ * accept three input shapes: a full `<m:oMathPara>`, a full `<m:oMath>`, or the inner OMML
704
+ * (children of `<m:oMath>`). A trailing `<a:endParaRPr>` matches what PowerPoint authors.
705
+ *
706
+ * @param {string} omml - raw OMML markup for the equation
707
+ * @returns {string} an `<a:p>` math paragraph
708
+ */
709
+ function genXmlMathParagraph(omml) {
710
+ const trimmed = (omml || "").trim();
711
+ const paraPr = el("m:oMathParaPr", null, raw(voidEl("m:jc", { "m:val": "centerGroup" })));
712
+ return el("a:p", null, [a14Math(trimmed.startsWith("<m:oMathPara") ? trimmed : trimmed.includes("<m:oMath") ? el("m:oMathPara", null, [paraPr, trimmed].map(raw)) : el("m:oMathPara", null, [paraPr, el("m:oMath", null, raw(trimmed))].map(raw))), voidEl("a:endParaRPr", { lang: "en-US" })].map(raw));
713
+ }
714
+ /**
715
+ * Build an INLINE native-equation run (dn-inline-math) from raw OMML.
716
+ *
717
+ * Unlike the display form ({@link genXmlMathParagraph}), an inline equation is *not* its own
718
+ * paragraph: PowerPoint authors an `<a14:m>` marker wrapping a bare `<m:oMath>` (no `<m:oMathPara>`,
719
+ * no `<m:oMathParaPr>`/`<m:jc>`) that flows between the surrounding `<a:r>` runs in one `<a:p>` —
720
+ * pinned by the `math-omml-inline.pptx` oracle. The `mc:AlternateContent` envelope stays at the
721
+ * shape level (see `objectHasMath`), so nothing wraps the run itself. We declare the `a14` and `m`
722
+ * namespaces on the `<a14:m>` element so the supplied OMML needs none of its own, and accept the
723
+ * same three input shapes as the display helper: a full `<m:oMathPara>` (its inner `<m:oMath>` is
724
+ * unwrapped, since a paragraph block cannot flow inline), a full `<m:oMath>`, or the inner OMML.
725
+ *
726
+ * @param {string} omml - raw OMML markup for the equation
727
+ * @returns {string} an `<a14:m>` inline equation run
728
+ */
729
+ function genXmlInlineMath(omml) {
730
+ const trimmed = (omml || "").trim();
731
+ const oMathMatch = trimmed.match(/<m:oMath[\s>][\s\S]*<\/m:oMath>/);
732
+ return a14Math(oMathMatch ? oMathMatch[0] : el("m:oMath", null, raw(trimmed)));
733
+ }
734
+ //#endregion
735
+ //#region src/gen/drawingml/text-run.ts
736
+ /** The 2018 hyperlink-color extension namespace, written on the `<ahyp:hlinkClr>` element itself. */
737
+ const AHYP_NS = "http://schemas.microsoft.com/office/drawing/2018/hyperlinkcolor";
738
+ /**
739
+ * `<a:buChar>` cannot go through the element builder. Its `char` attribute carries a *pre-escaped*
740
+ * numeric character reference (`&#x2022;` — see `BulletType`), and the builder escapes every
741
+ * attribute value, which would emit `&amp;#x2022;` and render that text literally as the bullet.
742
+ * There is no raw-attribute escape hatch, so this one stays a template.
743
+ */
744
+ function buChar(char) {
745
+ return `<a:buChar char="${char}"/>`;
746
+ }
747
+ /**
748
+ * `<a:extLst>` marking a hyperlink whose color should follow the text color rather than the
749
+ * theme's hyperlink color. The leading spaces are byte-significant: this block was authored as
750
+ * indented string concatenation and that indentation reaches the file, so it is described here
751
+ * with `openPrefix`/`closePrefix` rather than being tidied away.
752
+ */
753
+ const HLINK_TEXT_COLOR_EXT = el("a:extLst", null, raw(el("a:ext", { uri: "{A12FA001-AC4F-418D-AE19-62706E023703}" }, raw(voidEl("ahyp:hlinkClr", {
754
+ "xmlns:ahyp": AHYP_NS,
755
+ val: "tx"
756
+ }, { openPrefix: " " })), {
757
+ openPrefix: " ",
758
+ closePrefix: " "
759
+ })), {
760
+ openPrefix: " ",
761
+ closePrefix: " "
762
+ });
763
+ /**
764
+ * Whether a `bullet` value makes the paragraph DRAW a bullet (a glyph, a number or a picture).
765
+ *
766
+ * Two call sites downstream of the emitter act on "this paragraph has a bullet" rather than on
767
+ * "this paragraph says something about its bullet", and both were plain truthiness tests until
768
+ * `'inherit'` — a truthy string that draws nothing — joined the union. Left as truthiness they
769
+ * would have broken the line grouping (every run starting its own paragraph) and eaten a real
770
+ * leading `•` from text that emits no `a:buChar` to duplicate it.
771
+ * @param {TextPropsOptions['bullet']} bullet - the option value
772
+ * @return {boolean} true when bullet markup is emitted
773
+ */
774
+ function emitsBulletMarkup(bullet) {
775
+ return !!bullet && bullet !== "inherit";
776
+ }
777
+ /**
778
+ * One paragraph margin attribute's value in EMU, or `null` when the attribute is not written.
779
+ *
780
+ * Three inputs, because the option has three states and so does the attribute: an explicit
781
+ * number is clamped and written, `'inherit'` writes nothing, and an absent option falls back to
782
+ * whatever the `bullet` arm decided — which is also `null` for `bullet: 'inherit'`, the one arm
783
+ * that states nothing on its own.
784
+ * @param {number|'inherit'|undefined} option - `paraMarginLeft` / `paraIndent` as given
785
+ * @param {number|null} fallbackEmu - the bullet arm's default, `null` for no attribute
786
+ * @param {Function} clamp - the attribute's ST_* range clamp (points in, EMU out)
787
+ * @return {number|null} the EMU value to write, or null to omit the attribute
788
+ */
789
+ function resolveParagraphMargin(option, fallbackEmu, clamp) {
790
+ if (option === void 0) return fallbackEmu;
791
+ if (option === "inherit") return null;
792
+ return clamp(option);
793
+ }
794
+ /**
795
+ * Generate XML Paragraph Properties
796
+ * @param {SlideObject|TextProps} textObj - text object
797
+ * @param {boolean} isDefault - array of default relations
798
+ * @return {string} XML
799
+ */
800
+ function genXmlParagraphProperties(textObj, isDefault) {
801
+ const opts = textObj.options ?? {};
802
+ let strXmlBullet = "";
803
+ let strXmlBulletColor = "";
804
+ let strXmlLnSpc = "";
805
+ let strXmlParaSpc = "";
806
+ let strXmlTabStops = "";
807
+ const tag = isDefault ? "a:lvl1pPr" : "a:pPr";
808
+ let bulletMarL = valToPts(27);
809
+ let defaultMarL = null;
810
+ let defaultIndent = null;
811
+ let indentBeforeMarL = false;
812
+ let paragraphPropXml = `<${tag}${opts.rtlMode ? " rtl=\"1\" " : ""}`;
813
+ {
814
+ if (opts.align) switch (opts.align) {
815
+ case "left":
816
+ paragraphPropXml += " algn=\"l\"";
817
+ break;
818
+ case "right":
819
+ paragraphPropXml += " algn=\"r\"";
820
+ break;
821
+ case "center":
822
+ paragraphPropXml += " algn=\"ctr\"";
823
+ break;
824
+ case "justify":
825
+ paragraphPropXml += " algn=\"just\"";
826
+ break;
827
+ default: paragraphPropXml += "";
828
+ }
829
+ if (opts.lineSpacing) strXmlLnSpc = el("a:lnSpc", null, raw(voidEl("a:spcPts", { val: clampLineSpacingPts(opts.lineSpacing) })));
830
+ else if (opts.lineSpacingMultiple) strXmlLnSpc = el("a:lnSpc", null, raw(voidEl("a:spcPct", { val: Math.round(opts.lineSpacingMultiple * PERCENT_SCALE) })));
831
+ if (opts.indentLevel && !isNaN(Number(opts.indentLevel)) && opts.indentLevel > 0) paragraphPropXml += ` lvl="${opts.indentLevel}"`;
832
+ if (opts.paraSpaceBefore && !isNaN(Number(opts.paraSpaceBefore)) && opts.paraSpaceBefore > 0) strXmlParaSpc += el("a:spcBef", null, raw(voidEl("a:spcPts", { val: ptToHundredths(opts.paraSpaceBefore) })));
833
+ if (opts.paraSpaceAfter && !isNaN(Number(opts.paraSpaceAfter)) && opts.paraSpaceAfter > 0) strXmlParaSpc += el("a:spcAft", null, raw(voidEl("a:spcPts", { val: ptToHundredths(opts.paraSpaceAfter) })));
834
+ if (typeof opts.bullet === "object") {
835
+ const bulletImage = opts.bullet.image;
836
+ const isPictureBullet = !!(bulletImage && (bulletImage.path || bulletImage.data));
837
+ if (opts.bullet?.indent) bulletMarL = valToPts(opts.bullet.indent);
838
+ defaultMarL = opts.indentLevel && opts.indentLevel > 0 ? bulletMarL + bulletMarL * opts.indentLevel : bulletMarL;
839
+ defaultIndent = -bulletMarL;
840
+ if (opts.bullet.color && !isPictureBullet) strXmlBulletColor = el("a:buClr", null, raw(createColorElement(opts.bullet.color)));
841
+ let bulletSizePct;
842
+ if (opts.bullet.size !== void 0) {
843
+ const bulletSize = Number(opts.bullet.size);
844
+ if (isNaN(bulletSize) || bulletSize < 25 || bulletSize > 400) warn("bullet/size-out-of-range", "`bullet.size` must be a percentage between 25 and 400!");
845
+ else bulletSizePct = Math.round(bulletSize * FIXED_PCT_PER_PERCENT);
846
+ }
847
+ const strXmlBulletSize = bulletSizePct === void 0 ? "" : voidEl("a:buSzPct", { val: bulletSizePct });
848
+ const strXmlBulletFont = opts.bullet.fontFace ? voidEl("a:buFont", { typeface: opts.bullet.fontFace }) : "";
849
+ if (isPictureBullet) {
850
+ if (opts.bullet._rId) {
851
+ const svgExt = opts.bullet._rIdSvg ? el("a:extLst", null, raw(el("a:ext", { uri: "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" }, raw(voidEl("asvg:svgBlip", {
852
+ "xmlns:asvg": "http://schemas.microsoft.com/office/drawing/2016/SVG/main",
853
+ "r:embed": `rId${opts.bullet._rIdSvg}`
854
+ }))))) : "";
855
+ strXmlBullet = strXmlBulletSize + el("a:buBlip", null, raw(svgExt ? el("a:blip", { "r:embed": `rId${opts.bullet._rId}` }, raw(svgExt)) : voidEl("a:blip", { "r:embed": `rId${opts.bullet._rId}` })));
856
+ } else {
857
+ warn("bullet/image-embed-failed", "picture `bullet.image` could not be embedded; using a default bullet glyph");
858
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + buChar("&#x2022;");
859
+ }
860
+ } else if (opts.bullet.type && opts.bullet.type.toString().toLowerCase() === "number") strXmlBullet = strXmlBulletSize + (strXmlBulletFont || voidEl("a:buFont", { typeface: "+mj-lt" })) + voidEl("a:buAutoNum", {
861
+ type: opts.bullet.numberType || "arabicPeriod",
862
+ startAt: opts.bullet.numberStartAt || "1"
863
+ });
864
+ else if (opts.bullet.characterCode) {
865
+ let bulletCode = `&#x${opts.bullet.characterCode};`;
866
+ if (!/^[0-9A-Fa-f]{4}$/.test(opts.bullet.characterCode)) {
867
+ warn("bullet/invalid-character-code", "`bullet.characterCode` should be a 4-digit unicode character (ex: 22AB)!");
868
+ bulletCode = "&#x2022;";
869
+ }
870
+ strXmlBullet = strXmlBulletSize + strXmlBulletFont + buChar(bulletCode);
871
+ } else strXmlBullet = strXmlBulletSize + strXmlBulletFont + buChar("&#x2022;");
872
+ } else if (opts.bullet === "inherit") {} else if (opts.bullet) {
873
+ defaultMarL = opts.indentLevel && opts.indentLevel > 0 ? bulletMarL + bulletMarL * opts.indentLevel : bulletMarL;
874
+ defaultIndent = -bulletMarL;
875
+ strXmlBullet = buChar("&#x2022;");
876
+ } else if (!opts.bullet) {
877
+ defaultMarL = 0;
878
+ defaultIndent = 0;
879
+ indentBeforeMarL = true;
880
+ strXmlBullet = voidEl("a:buNone");
881
+ }
882
+ const marLEmu = resolveParagraphMargin(opts.paraMarginLeft, defaultMarL, clampParaMarginEmu);
883
+ const indentEmu = resolveParagraphMargin(opts.paraIndent, defaultIndent, clampParaIndentEmu);
884
+ const marLAttr = marLEmu === null ? "" : ` marL="${marLEmu}"`;
885
+ const indentAttr = indentEmu === null ? "" : ` indent="${indentEmu}"`;
886
+ paragraphPropXml += indentBeforeMarL ? indentAttr + marLAttr : marLAttr + indentAttr;
887
+ if (opts.tabStops && Array.isArray(opts.tabStops)) strXmlTabStops = el("a:tabLst", null, raw(opts.tabStops.map((stop) => voidEl("a:tab", {
888
+ pos: inch2Emu(stop.position || 1),
889
+ algn: stop.alignment || "l"
890
+ })).join("")));
891
+ paragraphPropXml += ">" + strXmlLnSpc + strXmlParaSpc + strXmlBulletColor + strXmlBullet + strXmlTabStops;
892
+ if (isDefault) paragraphPropXml += genXmlTextRunProperties(opts, true);
893
+ paragraphPropXml += "</" + tag + ">";
894
+ }
895
+ return paragraphPropXml;
896
+ }
897
+ /**
898
+ * Generate XML Text Run Properties (`a:rPr`)
899
+ * @param {ObjectOptions|TextPropsOptions} opts - text options
900
+ * @param {boolean} isDefault - whether these are the default text run properties
901
+ * @return {string} XML
902
+ */
903
+ function genXmlTextRunProperties(opts, isDefault) {
904
+ let runProps = "";
905
+ const runPropsTag = isDefault ? "a:defRPr" : "a:rPr";
906
+ const underline = typeof opts.underline === "object" && opts.underline?.style ? opts.underline.style : opts.hyperlink ? "sng" : null;
907
+ const baseline = opts.baseline ? Math.round(opts.baseline * 50) : opts.subscript ? -4e4 : opts.superscript ? 3e4 : null;
908
+ const attrs = {
909
+ lang: opts.lang ? opts.lang : "en-US",
910
+ altLang: opts.lang ? "en-US" : null,
911
+ sz: opts.fontSize ? clampFontSizeSz(opts.fontSize) : null,
912
+ b: opts?.bold ? "1" : null,
913
+ i: opts?.italic ? "1" : null,
914
+ strike: opts?.strike ? typeof opts.strike === "string" ? opts.strike : "sngStrike" : null,
915
+ cap: opts?.caps ? opts.caps : null,
916
+ u: underline,
917
+ baseline,
918
+ spc: opts.charSpacing ? clampCharSpacingSpc(opts.charSpacing) : null,
919
+ kern: opts.charSpacing ? 0 : null,
920
+ dirty: "0"
921
+ };
922
+ const hasShadow = !!opts.shadow && opts.shadow.type !== "none";
923
+ if (opts.color || opts.fontFace || opts.outline || opts.glow || hasShadow || typeof opts.underline === "object" && opts.underline.color) {
924
+ if (opts.outline && typeof opts.outline === "object") runProps += el("a:ln", { w: lineWidthToEmu(opts.outline.size || .75) }, raw(genXmlColorSelection(opts.outline.color || "FFFFFF")));
925
+ if (opts.color) runProps += genXmlColorSelection({
926
+ color: opts.color,
927
+ transparency: opts.transparency
928
+ });
929
+ if (opts.glow || hasShadow) runProps += el("a:effectLst", null, [opts.glow ? raw(createGlowElement(opts.glow, DEF_TEXT_GLOW)) : null, hasShadow ? raw(createShadowElement(opts.shadow, DEF_TEXT_SHADOW)) : null]);
930
+ if (opts.highlight) runProps += el("a:highlight", null, raw(createColorElement(opts.highlight)));
931
+ if (typeof opts.underline === "object" && opts.underline.color) runProps += el("a:uFill", null, raw(genXmlColorSelection(opts.underline.color)));
932
+ if (opts.fontFace) {
933
+ runProps += voidEl("a:latin", {
934
+ typeface: opts.fontFace,
935
+ pitchFamily: "34",
936
+ charset: "0"
937
+ });
938
+ if (opts.fontFaceEA) runProps += voidEl("a:ea", { typeface: opts.fontFaceEA });
939
+ runProps += voidEl("a:cs", { typeface: opts.fontFace });
940
+ }
941
+ }
942
+ if (opts.hyperlink) {
943
+ if (typeof opts.hyperlink !== "object") throw new InvalidOptionError("hyperlink/not-an-object", "text `hyperlink` option should be an object. Ex: `hyperlink:{url:'https://github.com'}` ");
944
+ else if (!opts.hyperlink.url && !opts.hyperlink.slide && !opts.hyperlink.action) throw new InvalidOptionError("hyperlink/missing-target", "text `hyperlink` requires either `url`, `slide`, or `action`");
945
+ else if (opts.hyperlink.url || opts.hyperlink.slide) {
946
+ const linkAttrs = opts.hyperlink.url ? {
947
+ "r:id": `rId${opts.hyperlink._rId}`,
948
+ invalidUrl: "",
949
+ action: "",
950
+ tgtFrame: "",
951
+ tooltip: opts.hyperlink.tooltip ?? "",
952
+ history: "1",
953
+ highlightClick: "0",
954
+ endSnd: "0"
955
+ } : {
956
+ "r:id": `rId${opts.hyperlink._rId}`,
957
+ action: "ppaction://hlinksldjump",
958
+ tooltip: opts.hyperlink.tooltip ?? ""
959
+ };
960
+ runProps += opts.color ? el("a:hlinkClick", linkAttrs, raw(HLINK_TEXT_COLOR_EXT)) : voidEl("a:hlinkClick", linkAttrs);
961
+ }
962
+ }
963
+ return el(runPropsTag, attrs, raw(runProps));
964
+ }
965
+ /**
966
+ * Build textBody text runs [`<a:r></a:r>`] for paragraphs [`<a:p>`]
967
+ * @param {TextProps} textObj - Text object
968
+ * @return {string} XML string
969
+ */
970
+ function genXmlTextRun(textObj) {
971
+ if (textObj.text === void 0 || textObj.text === null) return "";
972
+ return el("a:r", null, [raw(genXmlTextRunProperties(textObj.options ?? {}, false)), raw(el("a:t", null, String(textObj.text)))]);
973
+ }
974
+ /**
975
+ * Builds `<a:normAutofit>` with explicit fontScale/lnSpcReduction for "shrink text on overflow"
976
+ * @param {TextFitShrinkProps} fit - shrink fit options
977
+ * @return {string} XML string (`<a:normAutofit .../>`)
978
+ * @see ECMA-376 CT_TextNormAutofit (attributes in 1000ths of a percent)
979
+ */
980
+ function genXmlNormAutofit(fit) {
981
+ const pct = (val, name) => {
982
+ if (val === void 0 || val === null) return null;
983
+ if (typeof val !== "number" || isNaN(val) || val < 0 || val > 100) {
984
+ warn("text/invalid-fit-percentage", `fit.${name} must be a number between 0 and 100 (percent); received ${String(val)} - attribute ignored.`);
985
+ return null;
986
+ }
987
+ return Math.round(val * FIXED_PCT_PER_PERCENT);
988
+ };
989
+ return voidEl("a:normAutofit", {
990
+ fontScale: pct(fit.fontScale, "fontScale"),
991
+ lnSpcReduction: pct(fit.lnSpcReduction, "lnSpcReduction")
992
+ });
993
+ }
994
+ /**
995
+ * Group the flat run list into paragraphs (lines): a new line starts on a display-math run,
996
+ * an alignment change, or a bullet, and closes after a breakLine. Returns the per-line run arrays.
997
+ */
998
+ function groupRunsIntoLines(arrTextObjects, opts) {
999
+ const arrLines = [];
1000
+ let arrTexts = [];
1001
+ arrTextObjects.forEach((textObj, idx) => {
1002
+ if (textObj.math && !textObj.inline) {
1003
+ if (arrTexts.length > 0) {
1004
+ arrLines.push(arrTexts);
1005
+ arrTexts = [];
1006
+ }
1007
+ arrLines.push([textObj]);
1008
+ return;
1009
+ }
1010
+ if (arrTexts.length > 0 && (textObj.options.align || opts.align)) {
1011
+ if (textObj.options.align !== arrTextObjects[idx - 1]?.options.align) {
1012
+ arrLines.push(arrTexts);
1013
+ arrTexts = [];
1014
+ }
1015
+ } else if (arrTexts.length > 0 && emitsBulletMarkup(textObj.options.bullet)) {
1016
+ arrLines.push(arrTexts);
1017
+ arrTexts = [];
1018
+ textObj.options.breakLine = false;
1019
+ }
1020
+ arrTexts.push(textObj);
1021
+ if (arrTexts.length > 0 && textObj.options.breakLine) {
1022
+ if (idx + 1 < arrTextObjects.length) {
1023
+ arrLines.push(arrTexts);
1024
+ arrTexts = [];
1025
+ }
1026
+ }
1027
+ if (idx + 1 === arrTextObjects.length) arrLines.push(arrTexts);
1028
+ });
1029
+ return arrLines;
1030
+ }
1031
+ /**
1032
+ * Render each grouped line to an `<a:p>` paragraph: paragraph props, inherited run options,
1033
+ * text runs (and inline/display math), and the closing endParaRPr.
1034
+ */
1035
+ function renderTextParagraphsXml(arrLines, slideObj, opts) {
1036
+ let strSlideXml = "";
1037
+ arrLines.forEach((line) => {
1038
+ const firstRun = line[0];
1039
+ if (line.length === 1 && firstRun?.math && !firstRun.inline) {
1040
+ strSlideXml += genXmlMathParagraph(firstRun.math);
1041
+ return;
1042
+ }
1043
+ let reqsClosingFontSize = false;
1044
+ let paraXml = "";
1045
+ let paragraphPropEmitted = false;
1046
+ line.forEach((textObj, idx) => {
1047
+ textObj.options._lineIdx = idx;
1048
+ if (idx > 0 && textObj.options.softBreakBefore) paraXml += voidEl("a:br");
1049
+ textObj.options.align = textObj.options.align || opts.align;
1050
+ textObj.options.lineSpacing = textObj.options.lineSpacing || opts.lineSpacing;
1051
+ textObj.options.lineSpacingMultiple = textObj.options.lineSpacingMultiple || opts.lineSpacingMultiple;
1052
+ textObj.options.indentLevel = textObj.options.indentLevel || opts.indentLevel;
1053
+ textObj.options.paraSpaceBefore = textObj.options.paraSpaceBefore || opts.paraSpaceBefore;
1054
+ textObj.options.paraSpaceAfter = textObj.options.paraSpaceAfter || opts.paraSpaceAfter;
1055
+ textObj.options.paraMarginLeft = textObj.options.paraMarginLeft ?? opts.paraMarginLeft;
1056
+ textObj.options.paraIndent = textObj.options.paraIndent ?? opts.paraIndent;
1057
+ if (!paragraphPropEmitted) {
1058
+ paragraphPropEmitted = true;
1059
+ paraXml += genXmlParagraphProperties(textObj, false).replace("<a:pPr></a:pPr>", "");
1060
+ }
1061
+ const textOptions = textObj.options;
1062
+ Object.entries(opts).filter(([key]) => !(textObj.options.hyperlink && key === "color")).forEach(([key, val]) => {
1063
+ if (key !== "bullet" && !textOptions[key]) textOptions[key] = val;
1064
+ });
1065
+ let _textRunObj = textObj;
1066
+ if (idx === 0 && emitsBulletMarkup(line[0]?.options.bullet) && typeof textObj.text === "string") {
1067
+ const _stripped = textObj.text.replace(/^[\u2022\u25E6\u25AA\u25AB\u25CF\u25CB\u2023\u2043\u2219]\s*/, "");
1068
+ if (_stripped !== textObj.text) _textRunObj = {
1069
+ text: _stripped,
1070
+ options: textObj.options
1071
+ };
1072
+ }
1073
+ const isEmptyBreakArtifact = _textRunObj.text === "" && line.length > 1;
1074
+ if (_textRunObj.math) paraXml += genXmlInlineMath(_textRunObj.math);
1075
+ else if (!isEmptyBreakArtifact) paraXml += genXmlTextRun(_textRunObj);
1076
+ if (!textObj.text && opts.fontSize || textObj.options.fontSize) {
1077
+ reqsClosingFontSize = true;
1078
+ opts.fontSize = opts.fontSize || textObj.options.fontSize;
1079
+ }
1080
+ });
1081
+ const sizedAttrs = () => ({
1082
+ lang: opts.lang || "en-US",
1083
+ sz: opts.fontSize ? clampFontSizeSz(opts.fontSize) : null,
1084
+ dirty: "0"
1085
+ });
1086
+ if (slideObj._type === "tablecell" && (opts.fontSize || opts.fontFace)) {
1087
+ if (opts.fontFace) paraXml += el("a:endParaRPr", sizedAttrs(), [
1088
+ raw(voidEl("a:latin", {
1089
+ typeface: opts.fontFace,
1090
+ charset: "0"
1091
+ })),
1092
+ opts.fontFaceEA ? raw(voidEl("a:ea", { typeface: opts.fontFaceEA })) : null,
1093
+ raw(voidEl("a:cs", { typeface: opts.fontFace }))
1094
+ ]);
1095
+ else paraXml += voidEl("a:endParaRPr", sizedAttrs());
1096
+ } else if (reqsClosingFontSize) paraXml += voidEl("a:endParaRPr", sizedAttrs());
1097
+ else paraXml += voidEl("a:endParaRPr", {
1098
+ lang: opts.lang || "en-US",
1099
+ dirty: "0"
1100
+ });
1101
+ strSlideXml += el("a:p", null, raw(paraXml));
1102
+ });
1103
+ return strSlideXml;
1104
+ }
1105
+ //#endregion
1106
+ //#region src/gen/slide/notes.ts
1107
+ /**
1108
+ * Collect the speaker-notes runs for a slide (flattened across any number of `addNotes()` calls).
1109
+ * @param {PresSlideInternal} slide - the slide object
1110
+ * @return {TextProps[]} notes text runs in document order
1111
+ */
1112
+ function getNotesRuns(slide) {
1113
+ const runs = [];
1114
+ slide._slideObjects.forEach((obj) => {
1115
+ if (obj._type === "notes" && obj.text) runs.push(...obj.text);
1116
+ });
1117
+ return runs;
1118
+ }
1119
+ /**
1120
+ * Build (and cache) the hyperlink relationships for a slide's notes part (`notesSlideN.xml.rels`).
1121
+ *
1122
+ * Notes rels use their own namespace, independent of `slide._rels` (which serialize to
1123
+ * `slideN.xml.rels`). The notes part always reserves rId1=notesMaster and rId2=slide, so
1124
+ * dynamic hyperlink rels are allocated starting at rId3. Each notes hyperlink run is tagged
1125
+ * with its `_rId` so the body serializer and the rels file agree.
1126
+ *
1127
+ * Idempotent: the result is cached on `slide._relsNotes` and reused by both callers.
1128
+ * Only external `url` hyperlinks are supported; `slide` targets are ignored with a warning.
1129
+ * @param {PresSlideInternal} slide - the slide object
1130
+ * @return {SlideRel[]} notes hyperlink relationships
1131
+ */
1132
+ function buildNotesSlideRels(slide) {
1133
+ if (slide._relsNotes) return slide._relsNotes;
1134
+ const NOTES_REL_RESERVED = 2;
1135
+ const rels = [];
1136
+ let lastRid = NOTES_REL_RESERVED;
1137
+ getNotesRuns(slide).forEach((run) => {
1138
+ const hyperlink = run.options?.hyperlink;
1139
+ if (!hyperlink) return;
1140
+ if (!hyperlink.url) {
1141
+ if (hyperlink.slide) warn("notes/hyperlink-slide-unsupported", "notes hyperlinks support `url` only (ignoring `slide` target)");
1142
+ if (run.options) delete run.options.hyperlink;
1143
+ return;
1144
+ }
1145
+ lastRid++;
1146
+ hyperlink._rId = lastRid;
1147
+ rels.push({
1148
+ type: "hyperlink",
1149
+ data: "dummy",
1150
+ rId: lastRid,
1151
+ Target: hyperlink.url
1152
+ });
1153
+ });
1154
+ slide._relsNotes = rels;
1155
+ return rels;
1156
+ }
1157
+ /**
1158
+ * Build the `<p:txBody>` paragraphs for the notes placeholder.
1159
+ * Runs are split into `<a:p>` paragraphs on newlines; each run is serialized with the standard
1160
+ * text-run generator so inline formatting and `<a:hlinkClick>` markup are emitted consistently.
1161
+ * @param {PresSlideInternal} slide - the slide object
1162
+ * @return {string} XML string of `<a:p>` paragraphs
1163
+ */
1164
+ function genXmlNotesParagraphs(slide) {
1165
+ const paragraphs = [[]];
1166
+ getNotesRuns(slide).forEach((run) => {
1167
+ String(run.text ?? "").split("\n").forEach((segment, idx) => {
1168
+ if (idx > 0) paragraphs.push([]);
1169
+ const text = segment.replace(/\r/g, "");
1170
+ if (text !== "") paragraphs[paragraphs.length - 1]?.push({
1171
+ text,
1172
+ options: run.options || {}
1173
+ });
1174
+ });
1175
+ });
1176
+ return paragraphs.map((runs) => el("a:p", null, [raw(runs.map((run) => genXmlTextRun(run)).join("")), raw(voidEl("a:endParaRPr", {
1177
+ lang: "en-US",
1178
+ dirty: 0
1179
+ }))])).join("");
1180
+ }
1181
+ /**
1182
+ * Generate XML for Notes Master (notesMaster1.xml)
1183
+ * @returns {string} XML
1184
+ */
1185
+ function makeXmlNotesMaster() {
1186
+ const xfrmOffExt = (x, y, cx, cy) => el("a:xfrm", null, [raw(voidEl("a:off", {
1187
+ x,
1188
+ y
1189
+ })), raw(voidEl("a:ext", {
1190
+ cx,
1191
+ cy
1192
+ }))]);
1193
+ const prstGeomRect = el("a:prstGeom", { prst: "rect" }, raw(voidEl("a:avLst")));
1194
+ const spLocks = (attrs) => el("p:cNvSpPr", null, raw(voidEl("a:spLocks", attrs)));
1195
+ const nvPr = (phAttrs) => el("p:nvPr", null, raw(voidEl("p:ph", phAttrs)));
1196
+ const bodyPr = (attrs) => voidEl("a:bodyPr", attrs);
1197
+ const lstStyleLvl1 = (algn) => el("a:lstStyle", null, raw(el("a:lvl1pPr", { algn }, raw(voidEl("a:defRPr", { sz: 1200 })))));
1198
+ const emptyPara = el("a:p", null, raw(voidEl("a:endParaRPr", { lang: "en-US" })));
1199
+ const header = el("p:sp", null, [
1200
+ raw(el("p:nvSpPr", null, [
1201
+ raw(voidEl("p:cNvPr", {
1202
+ id: 2,
1203
+ name: "Header Placeholder 1"
1204
+ })),
1205
+ raw(spLocks({ noGrp: 1 })),
1206
+ raw(nvPr({
1207
+ type: "hdr",
1208
+ sz: "quarter"
1209
+ }))
1210
+ ])),
1211
+ raw(el("p:spPr", null, [raw(xfrmOffExt(0, 0, 2971800, 458788)), raw(prstGeomRect)])),
1212
+ raw(el("p:txBody", null, [
1213
+ raw(bodyPr({
1214
+ vert: "horz",
1215
+ lIns: 91440,
1216
+ tIns: 45720,
1217
+ rIns: 91440,
1218
+ bIns: 45720,
1219
+ rtlCol: 0
1220
+ })),
1221
+ raw(lstStyleLvl1("l")),
1222
+ raw(emptyPara)
1223
+ ]))
1224
+ ]);
1225
+ const date = el("p:sp", null, [
1226
+ raw(el("p:nvSpPr", null, [
1227
+ raw(voidEl("p:cNvPr", {
1228
+ id: 3,
1229
+ name: "Date Placeholder 2"
1230
+ })),
1231
+ raw(spLocks({ noGrp: 1 })),
1232
+ raw(nvPr({
1233
+ type: "dt",
1234
+ idx: 1
1235
+ }))
1236
+ ])),
1237
+ raw(el("p:spPr", null, [raw(xfrmOffExt(3884613, 0, 2971800, 458788)), raw(prstGeomRect)])),
1238
+ raw(el("p:txBody", null, [
1239
+ raw(bodyPr({
1240
+ vert: "horz",
1241
+ lIns: 91440,
1242
+ tIns: 45720,
1243
+ rIns: 91440,
1244
+ bIns: 45720,
1245
+ rtlCol: 0
1246
+ })),
1247
+ raw(lstStyleLvl1("r")),
1248
+ raw(el("a:p", null, [raw(el("a:fld", {
1249
+ id: "{5282F153-3F37-0F45-9E97-73ACFA13230C}",
1250
+ type: "datetimeFigureOut"
1251
+ }, [raw(voidEl("a:rPr", { lang: "en-US" })), raw(el("a:t", null, "7/23/19"))])), raw(voidEl("a:endParaRPr", { lang: "en-US" }))]))
1252
+ ]))
1253
+ ]);
1254
+ const slideImg = el("p:sp", null, [
1255
+ raw(el("p:nvSpPr", null, [
1256
+ raw(voidEl("p:cNvPr", {
1257
+ id: 4,
1258
+ name: "Slide Image Placeholder 3"
1259
+ })),
1260
+ raw(spLocks({
1261
+ noGrp: 1,
1262
+ noRot: 1,
1263
+ noChangeAspect: 1
1264
+ })),
1265
+ raw(nvPr({
1266
+ type: "sldImg",
1267
+ idx: 2
1268
+ }))
1269
+ ])),
1270
+ raw(el("p:spPr", null, [
1271
+ raw(xfrmOffExt(685800, 1143e3, 5486400, 3086100)),
1272
+ raw(prstGeomRect),
1273
+ raw(voidEl("a:noFill")),
1274
+ raw(el("a:ln", { w: 12700 }, raw(el("a:solidFill", null, raw(voidEl("a:prstClr", { val: "black" }))))))
1275
+ ])),
1276
+ raw(el("p:txBody", null, [
1277
+ raw(bodyPr({
1278
+ vert: "horz",
1279
+ lIns: 91440,
1280
+ tIns: 45720,
1281
+ rIns: 91440,
1282
+ bIns: 45720,
1283
+ rtlCol: 0,
1284
+ anchor: "ctr"
1285
+ })),
1286
+ raw(voidEl("a:lstStyle")),
1287
+ raw(emptyPara)
1288
+ ]))
1289
+ ]);
1290
+ const notesPlaceholder = el("p:sp", null, [
1291
+ raw(el("p:nvSpPr", null, [
1292
+ raw(voidEl("p:cNvPr", {
1293
+ id: 5,
1294
+ name: "Notes Placeholder 4"
1295
+ })),
1296
+ raw(spLocks({ noGrp: 1 })),
1297
+ raw(nvPr({
1298
+ type: "body",
1299
+ sz: "quarter",
1300
+ idx: 3
1301
+ }))
1302
+ ])),
1303
+ raw(el("p:spPr", null, [raw(xfrmOffExt(685800, 4400550, 5486400, 3600450)), raw(prstGeomRect)])),
1304
+ raw(el("p:txBody", null, [
1305
+ raw(bodyPr({
1306
+ vert: "horz",
1307
+ lIns: 91440,
1308
+ tIns: 45720,
1309
+ rIns: 91440,
1310
+ bIns: 45720,
1311
+ rtlCol: 0
1312
+ })),
1313
+ raw(voidEl("a:lstStyle")),
1314
+ raw([
1315
+ "Click to edit Master text styles",
1316
+ "Second level",
1317
+ "Third level",
1318
+ "Fourth level",
1319
+ "Fifth level"
1320
+ ].map((text, lvl) => el("a:p", null, [raw(voidEl("a:pPr", { lvl })), raw(el("a:r", null, [raw(voidEl("a:rPr", { lang: "en-US" })), raw(el("a:t", null, text))]))])).join(""))
1321
+ ]))
1322
+ ]);
1323
+ const footer = el("p:sp", null, [
1324
+ raw(el("p:nvSpPr", null, [
1325
+ raw(voidEl("p:cNvPr", {
1326
+ id: 6,
1327
+ name: "Footer Placeholder 5"
1328
+ })),
1329
+ raw(spLocks({ noGrp: 1 })),
1330
+ raw(nvPr({
1331
+ type: "ftr",
1332
+ sz: "quarter",
1333
+ idx: 4
1334
+ }))
1335
+ ])),
1336
+ raw(el("p:spPr", null, [raw(xfrmOffExt(0, 8685213, 2971800, 458787)), raw(prstGeomRect)])),
1337
+ raw(el("p:txBody", null, [
1338
+ raw(bodyPr({
1339
+ vert: "horz",
1340
+ lIns: 91440,
1341
+ tIns: 45720,
1342
+ rIns: 91440,
1343
+ bIns: 45720,
1344
+ rtlCol: 0,
1345
+ anchor: "b"
1346
+ })),
1347
+ raw(lstStyleLvl1("l")),
1348
+ raw(emptyPara)
1349
+ ]))
1350
+ ]);
1351
+ const slideNum = el("p:sp", null, [
1352
+ raw(el("p:nvSpPr", null, [
1353
+ raw(voidEl("p:cNvPr", {
1354
+ id: 7,
1355
+ name: "Slide Number Placeholder 6"
1356
+ })),
1357
+ raw(spLocks({ noGrp: 1 })),
1358
+ raw(nvPr({
1359
+ type: "sldNum",
1360
+ sz: "quarter",
1361
+ idx: 5
1362
+ }))
1363
+ ])),
1364
+ raw(el("p:spPr", null, [raw(xfrmOffExt(3884613, 8685213, 2971800, 458787)), raw(prstGeomRect)])),
1365
+ raw(el("p:txBody", null, [
1366
+ raw(bodyPr({
1367
+ vert: "horz",
1368
+ lIns: 91440,
1369
+ tIns: 45720,
1370
+ rIns: 91440,
1371
+ bIns: 45720,
1372
+ rtlCol: 0,
1373
+ anchor: "b"
1374
+ })),
1375
+ raw(lstStyleLvl1("r")),
1376
+ raw(el("a:p", null, [raw(el("a:fld", {
1377
+ id: "{CE5E9CC1-C706-0F49-92D6-E571CC5EEA8F}",
1378
+ type: "slidenum"
1379
+ }, [raw(voidEl("a:rPr", { lang: "en-US" })), raw(el("a:t", null, "‹#›"))])), raw(voidEl("a:endParaRPr", { lang: "en-US" }))]))
1380
+ ]))
1381
+ ]);
1382
+ const spTree = el("p:spTree", null, [
1383
+ raw(el("p:nvGrpSpPr", null, [
1384
+ raw(voidEl("p:cNvPr", {
1385
+ id: 1,
1386
+ name: ""
1387
+ })),
1388
+ raw(voidEl("p:cNvGrpSpPr")),
1389
+ raw(voidEl("p:nvPr"))
1390
+ ])),
1391
+ raw(el("p:grpSpPr", null, raw(el("a:xfrm", null, [
1392
+ raw(voidEl("a:off", {
1393
+ x: 0,
1394
+ y: 0
1395
+ })),
1396
+ raw(voidEl("a:ext", {
1397
+ cx: 0,
1398
+ cy: 0
1399
+ })),
1400
+ raw(voidEl("a:chOff", {
1401
+ x: 0,
1402
+ y: 0
1403
+ })),
1404
+ raw(voidEl("a:chExt", {
1405
+ cx: 0,
1406
+ cy: 0
1407
+ }))
1408
+ ])))),
1409
+ raw(header),
1410
+ raw(date),
1411
+ raw(slideImg),
1412
+ raw(notesPlaceholder),
1413
+ raw(footer),
1414
+ raw(slideNum)
1415
+ ]);
1416
+ const extLst = el("p:extLst", null, raw(el("p:ext", { uri: "{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}" }, raw(voidEl("p14:creationId", {
1417
+ "xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main",
1418
+ val: 1024086991
1419
+ })))));
1420
+ const cSld = el("p:cSld", null, [
1421
+ raw(el("p:bg", null, raw(el("p:bgRef", { idx: 1001 }, raw(voidEl("a:schemeClr", { val: "bg1" })))))),
1422
+ raw(spTree),
1423
+ raw(extLst)
1424
+ ]);
1425
+ const clrMap = voidEl("p:clrMap", {
1426
+ bg1: "lt1",
1427
+ tx1: "dk1",
1428
+ bg2: "lt2",
1429
+ tx2: "dk2",
1430
+ accent1: "accent1",
1431
+ accent2: "accent2",
1432
+ accent3: "accent3",
1433
+ accent4: "accent4",
1434
+ accent5: "accent5",
1435
+ accent6: "accent6",
1436
+ hlink: "hlink",
1437
+ folHlink: "folHlink"
1438
+ });
1439
+ const notesStyleLevel = (n, marL) => el(`a:lvl${n}pPr`, {
1440
+ marL,
1441
+ algn: "l",
1442
+ defTabSz: 914400,
1443
+ rtl: 0,
1444
+ eaLnBrk: 1,
1445
+ latinLnBrk: 0,
1446
+ hangingPunct: 1
1447
+ }, raw(el("a:defRPr", {
1448
+ sz: 1200,
1449
+ kern: 1200
1450
+ }, [
1451
+ raw(el("a:solidFill", null, raw(voidEl("a:schemeClr", { val: "tx1" })))),
1452
+ raw(voidEl("a:latin", { typeface: "+mn-lt" })),
1453
+ raw(voidEl("a:ea", { typeface: "+mn-ea" })),
1454
+ raw(voidEl("a:cs", { typeface: "+mn-cs" }))
1455
+ ])));
1456
+ const notesStyle = el("p:notesStyle", null, raw([
1457
+ 0,
1458
+ 457200,
1459
+ 914400,
1460
+ 1371600,
1461
+ 1828800,
1462
+ 2286e3,
1463
+ 2743200,
1464
+ 3200400,
1465
+ 3657600
1466
+ ].map((marL, i) => notesStyleLevel(i + 1, marL)).join("")));
1467
+ return XML_DECL + "\r\n" + el("p:notesMaster", {
1468
+ "xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
1469
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
1470
+ "xmlns:p": "http://schemas.openxmlformats.org/presentationml/2006/main"
1471
+ }, [
1472
+ raw(cSld),
1473
+ raw(clrMap),
1474
+ raw(notesStyle)
1475
+ ]);
1476
+ }
1477
+ /**
1478
+ * Creates Notes Slide (`ppt/notesSlides/notesSlide1.xml`)
1479
+ * @param {PresSlideInternal} slide - the slide object to transform into XML
1480
+ * @return {string} XML
1481
+ */
1482
+ function makeXmlNotesSlide(slide) {
1483
+ buildNotesSlideRels(slide);
1484
+ return makeXmlNotesSlideSkeleton(genXmlNotesParagraphs(slide), slide._slideNum);
1485
+ }
1486
+ /**
1487
+ * The notes slide's fixed frame, with the only two slide-dependent strings passed in.
1488
+ *
1489
+ * A notes slide is the same three placeholders every time — the thumbnail (`sldImg`),
1490
+ * the notes body, and the slide-number field — so everything except the body's
1491
+ * paragraphs and the cached slide number is a constant. It is a parameter of its own
1492
+ * because the read model authors notes onto a *loaded* deck (`Slide.addNotes`), where
1493
+ * there is no `PresSlideInternal` to serialize but the part must come out identical:
1494
+ * a second private copy of this frame is exactly the drift `src/ooxml/` exists to
1495
+ * prevent (see docs/architecture.md).
1496
+ * @param {string} bodyParagraphsXml - `a:p` children of the body placeholder's `p:txBody`
1497
+ * @param {number} slideNum - cached value of the slide-number `a:fld`
1498
+ * @return {string} XML
1499
+ */
1500
+ function makeXmlNotesSlideSkeleton(bodyParagraphsXml, slideNum) {
1501
+ const slideImgPh = el("p:sp", null, [raw(el("p:nvSpPr", null, [
1502
+ raw(voidEl("p:cNvPr", {
1503
+ id: 2,
1504
+ name: "Slide Image Placeholder 1"
1505
+ })),
1506
+ raw(el("p:cNvSpPr", null, raw(voidEl("a:spLocks", {
1507
+ noGrp: 1,
1508
+ noRot: 1,
1509
+ noChangeAspect: 1
1510
+ })))),
1511
+ raw(el("p:nvPr", null, raw(voidEl("p:ph", { type: "sldImg" }))))
1512
+ ])), raw(voidEl("p:spPr"))]);
1513
+ const notesPh = el("p:sp", null, [
1514
+ raw(el("p:nvSpPr", null, [
1515
+ raw(voidEl("p:cNvPr", {
1516
+ id: 3,
1517
+ name: "Notes Placeholder 2"
1518
+ })),
1519
+ raw(el("p:cNvSpPr", null, raw(voidEl("a:spLocks", { noGrp: 1 })))),
1520
+ raw(el("p:nvPr", null, raw(voidEl("p:ph", {
1521
+ type: "body",
1522
+ idx: 1
1523
+ }))))
1524
+ ])),
1525
+ raw(voidEl("p:spPr")),
1526
+ raw(el("p:txBody", null, [
1527
+ raw(voidEl("a:bodyPr")),
1528
+ raw(voidEl("a:lstStyle")),
1529
+ raw(bodyParagraphsXml)
1530
+ ]))
1531
+ ]);
1532
+ const slideNumPh = el("p:sp", null, [
1533
+ raw(el("p:nvSpPr", null, [
1534
+ raw(voidEl("p:cNvPr", {
1535
+ id: 4,
1536
+ name: "Slide Number Placeholder 3"
1537
+ })),
1538
+ raw(el("p:cNvSpPr", null, raw(voidEl("a:spLocks", { noGrp: 1 })))),
1539
+ raw(el("p:nvPr", null, raw(voidEl("p:ph", {
1540
+ type: "sldNum",
1541
+ sz: "quarter",
1542
+ idx: 10
1543
+ }))))
1544
+ ])),
1545
+ raw(voidEl("p:spPr")),
1546
+ raw(el("p:txBody", null, [
1547
+ raw(voidEl("a:bodyPr")),
1548
+ raw(voidEl("a:lstStyle")),
1549
+ raw(el("a:p", null, [raw(el("a:fld", {
1550
+ id: SLDNUMFLDID,
1551
+ type: "slidenum"
1552
+ }, [raw(voidEl("a:rPr", { lang: "en-US" })), raw(el("a:t", null, slideNum))])), raw(voidEl("a:endParaRPr", { lang: "en-US" }))]))
1553
+ ]))
1554
+ ]);
1555
+ const spTree = el("p:spTree", null, [
1556
+ raw(el("p:nvGrpSpPr", null, [
1557
+ raw(voidEl("p:cNvPr", {
1558
+ id: 1,
1559
+ name: ""
1560
+ })),
1561
+ raw(voidEl("p:cNvGrpSpPr")),
1562
+ raw(voidEl("p:nvPr"))
1563
+ ])),
1564
+ raw(el("p:grpSpPr", null, raw(el("a:xfrm", null, [
1565
+ raw(voidEl("a:off", {
1566
+ x: 0,
1567
+ y: 0
1568
+ })),
1569
+ raw(voidEl("a:ext", {
1570
+ cx: 0,
1571
+ cy: 0
1572
+ })),
1573
+ raw(voidEl("a:chOff", {
1574
+ x: 0,
1575
+ y: 0
1576
+ })),
1577
+ raw(voidEl("a:chExt", {
1578
+ cx: 0,
1579
+ cy: 0
1580
+ }))
1581
+ ])))),
1582
+ raw(slideImgPh),
1583
+ raw(notesPh),
1584
+ raw(slideNumPh)
1585
+ ]);
1586
+ const extLst = el("p:extLst", null, raw(el("p:ext", { uri: "{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}" }, raw(voidEl("p14:creationId", {
1587
+ "xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main",
1588
+ val: 1024086991
1589
+ })))));
1590
+ const cSld = el("p:cSld", null, [raw(spTree), raw(extLst)]);
1591
+ return XML_DECL + "\r\n" + el("p:notes", {
1592
+ "xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
1593
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
1594
+ "xmlns:p": "http://schemas.openxmlformats.org/presentationml/2006/main"
1595
+ }, [raw(cSld), raw(el("p:clrMapOvr", null, raw(voidEl("a:masterClrMapping"))))]);
1596
+ }
1597
+ /**
1598
+ * Generates XML string for a notes-slide relation file (`ppt/notesSlides/_rels/notesSlideN.xml.rels`).
1599
+ * rId1=notesMaster and rId2=slide are always reserved; any notes hyperlink rels follow (rId3+).
1600
+ * @param {PresSlideInternal} slide - the slide whose notes part is being related
1601
+ * @param {number} slideNumber - 1-indexed slide number the notes part belongs to
1602
+ * @return {string} XML
1603
+ */
1604
+ function makeXmlNotesSlideRel(slide, slideNumber) {
1605
+ const hlinkRels = buildNotesSlideRels(slide).map((rel) => voidEl("Relationship", {
1606
+ Id: `rId${rel.rId}`,
1607
+ Type: OFFICE_REL + "hyperlink",
1608
+ Target: rel.Target,
1609
+ TargetMode: "External"
1610
+ })).join("");
1611
+ return XML_DECL + el("Relationships", { xmlns: PACKAGE_REL_NS }, [
1612
+ raw(voidEl("Relationship", {
1613
+ Id: "rId1",
1614
+ Type: OFFICE_REL + "notesMaster",
1615
+ Target: "../notesMasters/notesMaster1.xml"
1616
+ })),
1617
+ raw(voidEl("Relationship", {
1618
+ Id: "rId2",
1619
+ Type: OFFICE_REL + "slide",
1620
+ Target: `../slides/slide${slideNumber}.xml`
1621
+ })),
1622
+ raw(hlinkRels)
1623
+ ], {
1624
+ openPrefix: "\n ",
1625
+ childPrefix: "\n ",
1626
+ closePrefix: ""
1627
+ });
1628
+ }
1629
+ /**
1630
+ * Creates `ppt/notesMasters/_rels/notesMaster1.xml.rels`
1631
+ * @return {string} XML
1632
+ */
1633
+ function makeXmlNotesMasterRel() {
1634
+ return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS }, raw(voidEl("Relationship", {
1635
+ Id: "rId1",
1636
+ Type: OFFICE_REL + "theme",
1637
+ Target: "../theme/theme2.xml"
1638
+ })), {
1639
+ childPrefix: "\n ",
1640
+ closePrefix: "\n "
1641
+ });
1642
+ }
1643
+ //#endregion
1644
+ export { getNewRelId as A, createColorElement as C, encodeXmlAttrValue as D, voidEl as E, isHyperlinkRel as M, validateObjectName as N, encodeXmlEntities as O, createShadowEffectLst as S, raw as T, FONT_DATA_EXTENSION as _, makeXmlNotesSlideRel as a, serializeEmbeddedFontLst as b, genXmlParagraphProperties as c, clampFontSizeSz as d, genXmlColorSelection as f, FONT_DATA_CONTENT_TYPE as g, EMBEDDED_FONT_SLOTS as h, makeXmlNotesSlide as i, getUuid as j, getDuplicateObjectNames as k, groupRunsIntoLines as l, genXmlPatternFill as m, makeXmlNotesMaster as n, makeXmlNotesSlideSkeleton as o, genXmlGradientFill as p, makeXmlNotesMasterRel as r, genXmlNormAutofit as s, buildNotesSlideRels as t, renderTextParagraphsXml as u, FONT_REL_TYPE as v, el as w, correctShadowOptions as x, flattenEmbeddedFaces as y };
1645
+
1646
+ //# sourceMappingURL=notes-SCvpJz0l.js.map