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