@shbernal/ts-pptx 4.0.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -114
- package/dist/anim-presets-Qbgh8uut.js +55 -0
- package/dist/anim-presets-Qbgh8uut.js.map +1 -0
- package/dist/{autopage-odPz7zZO.js → autopage-DqZ37VpL.js} +11 -12
- package/dist/autopage-DqZ37VpL.js.map +1 -0
- package/dist/{binary-string-CKVAYI3M.js → binary-string-Ba8VM3b9.js} +3 -4
- package/dist/binary-string-Ba8VM3b9.js.map +1 -0
- package/dist/{body-insets-Dw_BeWD_.js → body-insets-BDHymJvz.js} +7 -9
- package/dist/body-insets-BDHymJvz.js.map +1 -0
- package/dist/browser.d.ts +24 -16
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +19 -19
- package/dist/browser.js.map +1 -1
- package/dist/{image-size-CvN2iCA4.js → chart-metadata-CwiTTcJq.js} +634 -537
- package/dist/chart-metadata-CwiTTcJq.js.map +1 -0
- package/dist/{clip-Bl01BPcN.d.ts → clip-ISfCMtWA.d.ts} +6 -10
- package/dist/clip-ISfCMtWA.d.ts.map +1 -0
- package/dist/{notes-Dh12JAbn.js → comments-CKIZYRbZ.js} +530 -401
- package/dist/comments-CKIZYRbZ.js.map +1 -0
- package/dist/{diagnostics-D8ot4Ei7.d.ts → diagnostics-MoA4MBq5.d.ts} +55 -53
- package/dist/diagnostics-MoA4MBq5.d.ts.map +1 -0
- package/dist/{embedded-fonts-DarT9aoa.d.ts → embedded-fonts-U7HIIhPO.d.ts} +4 -6
- package/dist/embedded-fonts-U7HIIhPO.d.ts.map +1 -0
- package/dist/{entry-compose-DCnvBZz2.d.ts → entry-compose-CiakSt-f.d.ts} +62 -81
- package/dist/entry-compose-CiakSt-f.d.ts.map +1 -0
- package/dist/{errors-C4oENPcu.js → entry-errors-JMthgJ2g.js} +45 -29
- package/dist/entry-errors-JMthgJ2g.js.map +1 -0
- package/dist/{table-cell-margins-EVg0EwxT.js → enums-BP9wsb9Q.js} +44 -47
- package/dist/enums-BP9wsb9Q.js.map +1 -0
- package/dist/{enums-BUKTUB6V.d.ts → enums-PcXr3o3J.d.ts} +13 -14
- package/dist/{enums-BUKTUB6V.d.ts.map → enums-PcXr3o3J.d.ts.map} +1 -1
- package/dist/families.d.ts +24 -26
- package/dist/families.d.ts.map +1 -1
- package/dist/families.js +3 -4
- package/dist/{fit-Cb7naFj6.js → fit-Bpmzep6O.js} +64 -56
- package/dist/fit-Bpmzep6O.js.map +1 -0
- package/dist/{font-metrics-v5fn7s7e.d.ts → font-metrics-DTppw4t3.d.ts} +62 -38
- package/dist/font-metrics-DTppw4t3.d.ts.map +1 -0
- package/dist/{grid-DikTjFey.js → grid-DUIMxjfl.js} +23 -26
- package/dist/grid-DUIMxjfl.js.map +1 -0
- package/dist/{html-dom-Iuog2wdK.js → html-dom-LZCgntnJ.js} +46 -47
- package/dist/html-dom-LZCgntnJ.js.map +1 -0
- package/dist/html.d.ts +53 -9
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +12 -13
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +21 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +16 -18
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +18 -19
- package/dist/inspect.js.map +1 -1
- package/dist/{text-anchor-CrYXAfUu.js → lock-attrs-CSQnFDHP.js} +62 -12
- package/dist/lock-attrs-CSQnFDHP.js.map +1 -0
- package/dist/math.d.ts +2 -4
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +12 -13
- package/dist/math.js.map +1 -1
- package/dist/measure.d.ts +23 -12
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +3 -3
- package/dist/node.d.ts +21 -13
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +12 -12
- package/dist/node.js.map +1 -1
- package/dist/{options-internal-WqgMf720.js → options-internal-BWgmLPdx.js} +4 -5
- package/dist/options-internal-BWgmLPdx.js.map +1 -0
- package/dist/{package-J49Ck6S2.d.ts → package-CANBWuSm.d.ts} +9 -13
- package/dist/package-CANBWuSm.d.ts.map +1 -0
- package/dist/{playbtn-CUqQdoDf.js → playbtn-BQZFJir9.js} +4 -5
- package/dist/playbtn-BQZFJir9.js.map +1 -0
- package/dist/{pres-C6qgOFAa.d.ts → pres-BkEkUAEM.d.ts} +410 -237
- package/dist/pres-BkEkUAEM.d.ts.map +1 -0
- package/dist/{presentation-DGYlAWhl.js → presentation-4Ggq0_Xa.js} +470 -315
- package/dist/presentation-4Ggq0_Xa.js.map +1 -0
- package/dist/{presentation-types-8jLQHI4M.d.ts → presentation-types-BgnZR-0a.d.ts} +1119 -468
- package/dist/presentation-types-BgnZR-0a.d.ts.map +1 -0
- package/dist/read.d.ts +18 -13
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +4 -4
- package/dist/script.d.ts +116 -65
- package/dist/script.d.ts.map +1 -1
- package/dist/script.js +2700 -780
- package/dist/script.js.map +1 -1
- package/dist/{shapes-re0KyQk_.js → shapes-CAPOjU0Q.js} +1564 -491
- package/dist/shapes-CAPOjU0Q.js.map +1 -0
- package/dist/{slide-Dx2ZeVZ6.d.ts → slide-BmT4kMoO.d.ts} +102 -89
- package/dist/slide-BmT4kMoO.d.ts.map +1 -0
- package/dist/{slide-size-BalTjFuv.js → slide-size-SkScz1ig.js} +2 -3
- package/dist/{slide-size-BalTjFuv.js.map → slide-size-SkScz1ig.js.map} +1 -1
- package/dist/{table-dom-BTDP3lEP.js → table-dom-BYga4FYY.js} +4 -5
- package/dist/{table-dom-BTDP3lEP.js.map → table-dom-BYga4FYY.js.map} +1 -1
- package/dist/{text-sN64Otf_.js → text-CU08xJGs.js} +393 -344
- package/dist/text-CU08xJGs.js.map +1 -0
- package/dist/{transition-speed-kUEWDWTx.js → transition-speed-CNJ0fSpQ.js} +2 -3
- package/dist/{transition-speed-kUEWDWTx.js.map → transition-speed-CNJ0fSpQ.js.map} +1 -1
- package/dist/{units-internal-XJlf-hOl.js → units-internal-BR3a4cIi.js} +34 -32
- package/dist/units-internal-BR3a4cIi.js.map +1 -0
- package/dist/{zip-CKd8ihcf.js → zip-7JTh61LX.js} +8 -10
- package/dist/zip-7JTh61LX.js.map +1 -0
- package/dist/{zip-C0leZZMj.d.ts → zip-uUjs9Cqx.d.ts} +4 -6
- package/dist/zip-uUjs9Cqx.d.ts.map +1 -0
- package/dist/zip.d.ts +3 -3
- package/dist/zip.js +3 -3
- package/dist/{zoom-DPUHh85d.js → zoom-CSSeOwM8.js} +1204 -620
- package/dist/zoom-CSSeOwM8.js.map +1 -0
- package/package.json +27 -28
- package/skills/{ts-pptx-upstream → pptx-ts-upstream}/SKILL.md +37 -84
- package/dist/autopage-odPz7zZO.js.map +0 -1
- package/dist/binary-string-CKVAYI3M.js.map +0 -1
- package/dist/body-insets-Dw_BeWD_.js.map +0 -1
- package/dist/clip-Bl01BPcN.d.ts.map +0 -1
- package/dist/diagnostics-D8ot4Ei7.d.ts.map +0 -1
- package/dist/embedded-fonts-DarT9aoa.d.ts.map +0 -1
- package/dist/entry-compose-DCnvBZz2.d.ts.map +0 -1
- package/dist/errors-C4oENPcu.js.map +0 -1
- package/dist/fit-Cb7naFj6.js.map +0 -1
- package/dist/font-metrics-v5fn7s7e.d.ts.map +0 -1
- package/dist/grid-DikTjFey.js.map +0 -1
- package/dist/html-dom-Iuog2wdK.js.map +0 -1
- package/dist/image-size-CvN2iCA4.js.map +0 -1
- package/dist/notes-Dh12JAbn.js.map +0 -1
- package/dist/options-internal-WqgMf720.js.map +0 -1
- package/dist/package-J49Ck6S2.d.ts.map +0 -1
- package/dist/playbtn-CUqQdoDf.js.map +0 -1
- package/dist/pres-C6qgOFAa.d.ts.map +0 -1
- package/dist/presentation-DGYlAWhl.js.map +0 -1
- package/dist/presentation-types-8jLQHI4M.d.ts.map +0 -1
- package/dist/shapes-re0KyQk_.js.map +0 -1
- package/dist/slide-Dx2ZeVZ6.d.ts.map +0 -1
- package/dist/table-cell-margins-EVg0EwxT.js.map +0 -1
- package/dist/text-anchor-CrYXAfUu.js.map +0 -1
- package/dist/text-sN64Otf_.js.map +0 -1
- package/dist/units-internal-XJlf-hOl.js.map +0 -1
- package/dist/zip-C0leZZMj.d.ts.map +0 -1
- package/dist/zip-CKd8ihcf.js.map +0 -1
- package/dist/zoom-DPUHh85d.js.map +0 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as PackageReadError, n as InternalError, r as InvalidOptionError, s as UnsupportedFeatureError, u as warn } from "./entry-errors-JMthgJ2g.js";
|
|
2
|
+
import { Ht as SCHEME_COLOR_VALUES, Kt as TEXT_HORZ_OVERFLOW, Lt as PRESET_LINE_DASHES, M as IMAGE_REL, Qt as THEME_COLOR_SLOTS, Tt as EMBEDDED_FONT_SLOTS, U as OFFICE_DOCUMENT_REL, Ut as TEXT_ANCHORS, Xt as TEXT_UNDERLINE_TYPES, Zt as TEXT_VERTICAL, a as getImageSizeFromBytes, dn as stripHash, et as SLIDE_LAYOUT_REL, ft as OOXML_NS, in as checkEnumOrThrow, it as TABLE_STYLES_CONTENT_TYPE, ln as isHexColor, mt as TABLE_GRAPHIC_DATA_URI, nt as SLIDE_MASTER_REL, on as boolValue, r as fitSrcRectPercents, st as THEME_REL, t as CHART_METADATA_EXT_URI, vn as imageFormatForContentType, xt as COLOR_MAP_TOKENS, z as NOTES_MASTER_REL } from "./chart-metadata-CwiTTcJq.js";
|
|
3
|
+
import { F as EMU_PER_INCH, I as EMU_PER_POINT, P as ptToHundredths, b as FIXED_PCT_PER_PERCENT, k as emuToPoints, v as ANGLE_UNITS_PER_DEGREE, w as PERCENT_SCALE } from "./enums-BP9wsb9Q.js";
|
|
4
4
|
import { DOMParser, MIME_TYPE, XMLSerializer, onErrorStopParsing } from "@xmldom/xmldom";
|
|
5
5
|
/** Parse an XML string strictly: any well-formedness error throws. */
|
|
6
6
|
function parseXml(xml) {
|
|
7
7
|
return new DOMParser({ onError: onErrorStopParsing }).parseFromString(xml, MIME_TYPE.XML_TEXT);
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Serialize a Document to an XML string. Does not pretty-print or
|
|
11
|
-
* whitespace. The XML declaration is not part of the DOM; callers that need
|
|
12
|
-
*
|
|
10
|
+
* Serialize a Document, or one element of it, to an XML string. Does not pretty-print or
|
|
11
|
+
* normalize whitespace. The XML declaration is not part of the DOM; callers that need it (see
|
|
12
|
+
* `Part.serialize`) must prepend it themselves. An element is written with the namespace
|
|
13
|
+
* declarations its subtree uses but inherits from an ancestor, so it stands alone.
|
|
13
14
|
*/
|
|
14
|
-
function serializeXml(
|
|
15
|
-
return new XMLSerializer().serializeToString(
|
|
15
|
+
function serializeXml(node) {
|
|
16
|
+
return new XMLSerializer().serializeToString(node);
|
|
16
17
|
}
|
|
17
18
|
function splitQName(qname) {
|
|
18
19
|
const colon = qname.indexOf(":");
|
|
@@ -49,7 +50,7 @@ function childElements(parent) {
|
|
|
49
50
|
* All *descendant* elements of `root` matching a namespace + local name, snapshotted into an
|
|
50
51
|
* array. Unlike {@link getElements} (direct children only) this searches the whole subtree, and
|
|
51
52
|
* unlike the live `HTMLCollection` that `getElementsByTagNameNS` returns, the result does not
|
|
52
|
-
* change as the caller mutates the tree
|
|
53
|
+
* change as the caller mutates the tree, which is what makes it safe to iterate while replacing
|
|
53
54
|
* the very elements being iterated.
|
|
54
55
|
*
|
|
55
56
|
* Note the DOM's own exclusion: `root` itself is never a candidate, only its descendants.
|
|
@@ -137,7 +138,7 @@ function removeAttr(element, qname) {
|
|
|
137
138
|
* Get the first child element matching `qname`, creating and inserting it when
|
|
138
139
|
* absent. A newly created element is inserted before the first existing child
|
|
139
140
|
* whose qname appears in `before` (the schema successors of the new element),
|
|
140
|
-
* or appended when none are present
|
|
141
|
+
* or appended when none are present, keeping the parent's children in the
|
|
141
142
|
* sequence order OOXML mandates.
|
|
142
143
|
*/
|
|
143
144
|
function getOrAddChild(parent, qname, before = []) {
|
|
@@ -207,7 +208,7 @@ function boolAttr(element, qname) {
|
|
|
207
208
|
*
|
|
208
209
|
* `a:ST_Percentage` is a *union* in the Transitional profile: the fixed-point integer form
|
|
209
210
|
* Office writes (`100%` → `100000`) and a decimal string with a literal `%` (`-?[0-9]+(\.[0-9]+)?%`),
|
|
210
|
-
* which is the only form the Strict profile has. Both are read here
|
|
211
|
+
* which is the only form the Strict profile has. Both are read here. A reader that took only the
|
|
211
212
|
* first dropped a schema-legal value silently, and the two are one `endsWith` apart.
|
|
212
213
|
* @param value - the raw attribute value, or `null` when the attribute is absent
|
|
213
214
|
*/
|
|
@@ -262,8 +263,7 @@ function concatDrawingMLText(rich) {
|
|
|
262
263
|
for (const t of rich.getElementsByTagNameNS(OOXML_NS.a, "t")) out += t.textContent ?? "";
|
|
263
264
|
return out === "" ? null : out;
|
|
264
265
|
}
|
|
265
|
-
|
|
266
|
-
//#region src/read/opc/partnames.ts
|
|
266
|
+
// src/read/opc/partnames.ts
|
|
267
267
|
/**
|
|
268
268
|
* OPC partname helpers (ECMA-376 Part 2 pack-URI grammar).
|
|
269
269
|
*
|
|
@@ -272,8 +272,8 @@ function concatDrawingMLText(rich) {
|
|
|
272
272
|
*/
|
|
273
273
|
/**
|
|
274
274
|
* Resolve the single relationship of `type` owned by `partName` to its target partname, or
|
|
275
|
-
* `null` when the part declares none. For the one-of-a-kind links in a deck's spine
|
|
276
|
-
* its layout, a layout to its master, a master to its theme
|
|
275
|
+
* `null` when the part declares none. For the one-of-a-kind links in a deck's spine (a slide to
|
|
276
|
+
* its layout, a layout to its master, a master to its theme), where a second relationship of the
|
|
277
277
|
* same type would itself be malformed.
|
|
278
278
|
*
|
|
279
279
|
* The `OpcPackage` import is type-only, so this does not create a cycle with `package.ts` (which
|
|
@@ -289,7 +289,7 @@ function resolveSingleRel(opc, partName, type) {
|
|
|
289
289
|
*
|
|
290
290
|
* Ten sites in the API layer wrote the `resolveTarget` + `opc.part` pair out; two more wrote
|
|
291
291
|
* the `byType(T)[0]` that precedes it (see {@link singleRelPart}). `resolveTarget` keeps its
|
|
292
|
-
* throw on an id the part does not declare
|
|
292
|
+
* throw on an id the part does not declare: a dangling `r:id` is a malformed package, not a
|
|
293
293
|
* missing part, and the graphic-frame accessors rely on the distinction.
|
|
294
294
|
*/
|
|
295
295
|
function relPart(opc, rels, relId) {
|
|
@@ -321,7 +321,7 @@ function resolvePartName(relId, rels) {
|
|
|
321
321
|
* {@link resolveSingleRel}, resolved the rest of the way to the {@link Part}.
|
|
322
322
|
*
|
|
323
323
|
* For the one-of-a-kind links in a deck's spine, where the caller wants the part rather than
|
|
324
|
-
* its name
|
|
324
|
+
* its name: a slide to its notes slide or its layout, a layout to its master, a part to its
|
|
325
325
|
* `core.xml` or `app.xml`.
|
|
326
326
|
*/
|
|
327
327
|
function singleRelPart(opc, partName, type) {
|
|
@@ -337,7 +337,7 @@ function partNameToZipPath(partName) {
|
|
|
337
337
|
}
|
|
338
338
|
/**
|
|
339
339
|
* Lowercased extension without the dot, or `''` when the part has none.
|
|
340
|
-
* Per OPC, the extension is everything after the last dot
|
|
340
|
+
* Per OPC, the extension is everything after the last dot, so the leading
|
|
341
341
|
* dot of `/_rels/.rels` still yields `rels`.
|
|
342
342
|
*/
|
|
343
343
|
function partNameExtension(partName) {
|
|
@@ -352,7 +352,7 @@ function relsPartNameFor(sourcePartName) {
|
|
|
352
352
|
}
|
|
353
353
|
/**
|
|
354
354
|
* Build a relationship `Target` for `targetPartName` relative to its source
|
|
355
|
-
* part's directory
|
|
355
|
+
* part's directory. It is the inverse of {@link resolveRelativePartName}. Both names
|
|
356
356
|
* are absolute partnames. E.g. source `/ppt/slides/slide1.xml`, target
|
|
357
357
|
* `/ppt/media/image1.png` → `../media/image1.png`.
|
|
358
358
|
*/
|
|
@@ -384,15 +384,14 @@ function resolveRelativePartName(sourcePartName, target) {
|
|
|
384
384
|
}
|
|
385
385
|
return `/${segments.join("/")}`;
|
|
386
386
|
}
|
|
387
|
-
|
|
388
|
-
//#region src/ooxml/sequence.ts
|
|
387
|
+
// src/ooxml/sequence.ts
|
|
389
388
|
/**
|
|
390
389
|
* Schema child-sequence order for the OOXML complexTypes both halves of the library insert into.
|
|
391
390
|
*
|
|
392
391
|
* **What these are for.** `getOrAddChild(parent, qname, before)` / `insertInOrder(parent, node,
|
|
393
392
|
* before)` place a newly created child before the first sibling named in `before`. So `before`
|
|
394
393
|
* must list exactly the children that legally *follow* the one being inserted. Get it wrong and
|
|
395
|
-
* the part is schema-invalid
|
|
394
|
+
* the part is schema-invalid, which PowerPoint reports as a corrupt file, not as a bad option,
|
|
396
395
|
* so the failure surfaces far from its cause with no compile-time signal.
|
|
397
396
|
*
|
|
398
397
|
* **Why they are derived, not written out.** These successor lists used to be hand-maintained,
|
|
@@ -409,7 +408,7 @@ function resolveRelativePartName(sourcePartName, target) {
|
|
|
409
408
|
* both are bound by the same ECMA-376 sequence.
|
|
410
409
|
*/
|
|
411
410
|
/**
|
|
412
|
-
* `EG_FillProperties
|
|
411
|
+
* `EG_FillProperties`: the mutually exclusive fill choices, in schema order.
|
|
413
412
|
* @see ECMA-376 Part 1 §20.1.8 (DrawingML fill group)
|
|
414
413
|
*/
|
|
415
414
|
const FILL_CHOICES = [
|
|
@@ -420,16 +419,16 @@ const FILL_CHOICES = [
|
|
|
420
419
|
"a:pattFill",
|
|
421
420
|
"a:grpFill"
|
|
422
421
|
];
|
|
423
|
-
/** `EG_EffectProperties
|
|
422
|
+
/** `EG_EffectProperties`: the two mutually exclusive effect containers. */
|
|
424
423
|
const EFFECT_CHOICES = ["a:effectLst", "a:effectDag"];
|
|
425
|
-
/** `EG_Geometry
|
|
424
|
+
/** `EG_Geometry`: the two mutually exclusive geometry choices. */
|
|
426
425
|
const GEOMETRY_CHOICES = ["a:custGeom", "a:prstGeom"];
|
|
427
426
|
/**
|
|
428
427
|
* Everything that legally follows `member` in `sequence`, flattened.
|
|
429
428
|
*
|
|
430
429
|
* `member` may name a single element or any member of a choice group; either way the result
|
|
431
430
|
* starts after the step it occupies, so a choice member never lists its own siblings. Throws on
|
|
432
|
-
* an unknown name rather than returning `[]
|
|
431
|
+
* an unknown name rather than returning `[]`. An empty successor list silently *appends*, which
|
|
433
432
|
* is exactly the corruption these lists exist to prevent, so a typo must fail loudly and at
|
|
434
433
|
* module-load time rather than produce a subtly out-of-order part at run time.
|
|
435
434
|
*/
|
|
@@ -456,7 +455,7 @@ const SPPR_SEQUENCE = [
|
|
|
456
455
|
* `CT_Shape` (`p:sp`), in declaration order.
|
|
457
456
|
*
|
|
458
457
|
* `p:pic` (`CT_Picture`) and `p:cxnSp` (`CT_Connector`) are not this type, but their successors
|
|
459
|
-
* of `p:spPr` are a *subset* of this one's in the same relative order
|
|
458
|
+
* of `p:spPr` are a *subset* of this one's in the same relative order: both run `p:spPr`,
|
|
460
459
|
* `p:style`, `p:extLst` and neither has a `p:txBody`. So {@link SHAPE_AFTER_SPPR} serves all
|
|
461
460
|
* three: naming an element that cannot occur in a given parent is inert, because the successor
|
|
462
461
|
* list is only ever matched against children that are actually there.
|
|
@@ -471,7 +470,7 @@ const SP_SEQUENCE = [
|
|
|
471
470
|
];
|
|
472
471
|
/**
|
|
473
472
|
* `CT_GroupShapeProperties` (`p:grpSpPr`), in declaration order. A group carries no `a:ln` and
|
|
474
|
-
* no `a:sp3d
|
|
473
|
+
* no `a:sp3d`, which is exactly why it needs its own successor lists rather than borrowing the
|
|
475
474
|
* shape ones.
|
|
476
475
|
* @see ECMA-376 Part 1 §20.1.2.2.19
|
|
477
476
|
*/
|
|
@@ -557,7 +556,7 @@ const TCPR_SEQUENCE = [
|
|
|
557
556
|
"a:extLst"
|
|
558
557
|
];
|
|
559
558
|
/**
|
|
560
|
-
* For each `a:tcPr` child, the children that must follow it
|
|
559
|
+
* For each `a:tcPr` child, the children that must follow it, i.e. what a new one is inserted
|
|
561
560
|
* *before*. Every child gets an entry, so a caller can look one up by qname.
|
|
562
561
|
*/
|
|
563
562
|
const TCPR_AFTER = Object.fromEntries(TCPR_SEQUENCE.map((qname, index) => [qname, TCPR_SEQUENCE.slice(index + 1)]));
|
|
@@ -578,13 +577,13 @@ const GRPSPPR_AFTER_XFRM = successorsOf(GRPSPPR_SEQUENCE, "a:xfrm");
|
|
|
578
577
|
/** Successors of a fill choice inside `p:grpSpPr`. */
|
|
579
578
|
const GRPSPPR_FILL_AFTER = successorsOf(GRPSPPR_SEQUENCE, "a:solidFill");
|
|
580
579
|
/**
|
|
581
|
-
* Successors of `p:grpSpPr` within `p:grpSp
|
|
580
|
+
* Successors of `p:grpSpPr` within `p:grpSp`: every drawable child, plus `p:contentPart` and
|
|
582
581
|
* `p:extLst`. Derived rather than written out, which is the whole point of this module: the
|
|
583
582
|
* literal it replaces named five of the seven.
|
|
584
583
|
*/
|
|
585
584
|
const GROUP_AFTER_GRPSPPR = successorsOf(GROUP_SHAPE_SEQUENCE, "p:grpSpPr");
|
|
586
585
|
/**
|
|
587
|
-
* Successors of `p:spPr` within `p:sp`, and equally within `p:pic` / `p:cxnSp
|
|
586
|
+
* Successors of `p:spPr` within `p:sp`, and equally within `p:pic` / `p:cxnSp`. See
|
|
588
587
|
* {@link SP_SEQUENCE} for why one list covers all three. `p:blipFill` and the `p:nv*Pr` wrapper
|
|
589
588
|
* precede `p:spPr`, so slicing the sequence drops them without anyone having to remember to.
|
|
590
589
|
*/
|
|
@@ -601,8 +600,8 @@ const RPR_FILL_AFTER = successorsOf(RPR_SEQUENCE, "a:solidFill");
|
|
|
601
600
|
/** Successors of `a:latin` inside `a:rPr` (and `a:defRPr`/`a:endParaRPr`). */
|
|
602
601
|
const RPR_LATIN_AFTER = successorsOf(RPR_SEQUENCE, "a:latin");
|
|
603
602
|
/**
|
|
604
|
-
* `CT_Presentation` (`p:presentation`), in declaration order. Every child is a single element
|
|
605
|
-
* there are no choice groups
|
|
603
|
+
* `CT_Presentation` (`p:presentation`), in declaration order. Every child is a single element
|
|
604
|
+
* (there are no choice groups), so the declaration is a plain string list.
|
|
606
605
|
* @see ECMA-376 Part 1 §19.2.1.26
|
|
607
606
|
*/
|
|
608
607
|
const PRESENTATION_SEQUENCE = [
|
|
@@ -638,7 +637,7 @@ const PRESENTATION_AFTER_EMBEDDED_FONT_LST = successorsOf(PRESENTATION_SEQUENCE,
|
|
|
638
637
|
*/
|
|
639
638
|
const EMBEDDED_FONT_ENTRY_SEQUENCE = ["p:font", ...EMBEDDED_FONT_SLOTS.map((slot) => `p:${slot}`)];
|
|
640
639
|
/**
|
|
641
|
-
* For each `p:embeddedFont` child, the children that must follow it
|
|
640
|
+
* For each `p:embeddedFont` child, the children that must follow it, i.e. what a new one is
|
|
642
641
|
* inserted *before*. Every child gets an entry, so a caller can look one up by qname.
|
|
643
642
|
*/
|
|
644
643
|
const EMBEDDED_FONT_ENTRY_AFTER = Object.fromEntries(EMBEDDED_FONT_ENTRY_SEQUENCE.map((qname, index) => [qname, EMBEDDED_FONT_ENTRY_SEQUENCE.slice(index + 1)]));
|
|
@@ -731,8 +730,7 @@ const TEXT_BODY_SEQUENCE = [
|
|
|
731
730
|
const TXBODY_AFTER_BODYPR = successorsOf(TEXT_BODY_SEQUENCE, "a:bodyPr");
|
|
732
731
|
/** Successors of `a:lstStyle` inside a text body. */
|
|
733
732
|
const TXBODY_AFTER_LSTSTYLE = successorsOf(TEXT_BODY_SEQUENCE, "a:lstStyle");
|
|
734
|
-
|
|
735
|
-
//#region src/read/oxml/color-transform.ts
|
|
733
|
+
// src/read/oxml/color-transform.ts
|
|
736
734
|
/**
|
|
737
735
|
* Apply DrawingML colour-transform modifiers (`lumMod`/`lumOff`/`shade`/`tint`/
|
|
738
736
|
* `satMod`/`alpha`/…) to a base sRGB colour, returning the *effective* colour a
|
|
@@ -744,22 +742,22 @@ const TXBODY_AFTER_LSTSTYLE = successorsOf(TEXT_BODY_SEQUENCE, "a:lstStyle");
|
|
|
744
742
|
* turns that base hex + transform list into the final rendered hex, so a consumer
|
|
745
743
|
* never hand-computes a tint/shade again.
|
|
746
744
|
*
|
|
747
|
-
* Colour-space model (verified against PowerPoint output
|
|
748
|
-
* `test/read/color-transform.test.
|
|
745
|
+
* Colour-space model (verified against PowerPoint output; see
|
|
746
|
+
* `test/read/color-transform.test.ts`): the working colour is kept canonical as
|
|
749
747
|
* sRGB between transforms; each transform converts into the space its family
|
|
750
748
|
* operates in, applies, and converts back to sRGB. Transforms apply in **document
|
|
751
749
|
* order** (ECMA-376 §20.1.2.3).
|
|
752
750
|
*
|
|
753
|
-
* - `lumMod` / `lumOff
|
|
751
|
+
* - `lumMod` / `lumOff`: luminance (L) in HSL: `L' = L·mod + off`. PowerPoint's
|
|
754
752
|
* "Lighter/Darker N%" family.
|
|
755
|
-
* - `satMod` / `satOff
|
|
756
|
-
* - `hueMod` / `hueOff
|
|
753
|
+
* - `satMod` / `satOff`: saturation (S) in HSL.
|
|
754
|
+
* - `hueMod` / `hueOff`: hue (H) in HSL (`hueMod` scales, `hueOff` offsets by
|
|
757
755
|
* degrees). Not exercised by any current source; implemented for completeness.
|
|
758
|
-
* - `shade
|
|
759
|
-
* - `tint
|
|
760
|
-
* - `alpha` / `alphaMod` / `alphaOff
|
|
756
|
+
* - `shade`: toward black in **linear** RGB: `c' = c·k`.
|
|
757
|
+
* - `tint`: toward white in **linear** RGB: `c' = c·k + (1−k)`.
|
|
758
|
+
* - `alpha` / `alphaMod` / `alphaOff`: opacity, returned alongside the hex
|
|
761
759
|
* (never folded into RGB).
|
|
762
|
-
* - `comp` / `inv` / `gray` / `gamma` / `invGamma
|
|
760
|
+
* - `comp` / `inv` / `gray` / `gamma` / `invGamma`: pass-through for now (no
|
|
763
761
|
* current source needs them; add when one does).
|
|
764
762
|
*
|
|
765
763
|
* All DrawingML percentage values are stored in thousandths of a percent
|
|
@@ -854,8 +852,8 @@ function hslToRgb({ h, s, l }) {
|
|
|
854
852
|
/**
|
|
855
853
|
* An `a:hslClr` triple → 6-hex sRGB, or `null` when any of the three is unparseable.
|
|
856
854
|
*
|
|
857
|
-
* The colour space is the one `lumMod`/`satMod` already work in
|
|
858
|
-
* which is what the transform family below is verified against
|
|
855
|
+
* The colour space is the one `lumMod`/`satMod` already work in (DrawingML's HSL is sRGB-HSL,
|
|
856
|
+
* which is what the transform family below is verified against), so this shares that conversion
|
|
859
857
|
* rather than introducing a second one. Units are the schema's: `@hue` is
|
|
860
858
|
* `a:ST_PositiveFixedAngle`, i.e. 60,000ths of a degree, and `@sat`/`@lum` are `a:ST_Percentage`
|
|
861
859
|
* (see {@link parsePercent}). Saturation and luminance are clamped to 0–1; hue wraps.
|
|
@@ -877,7 +875,7 @@ function hslClrToHex(hue, sat, lum) {
|
|
|
877
875
|
/**
|
|
878
876
|
* Apply an ordered list of DrawingML colour transforms to a base sRGB hex,
|
|
879
877
|
* returning the effective sRGB hex (and opacity, when an `alpha*` modifier set
|
|
880
|
-
* it). Pure: no DOM, no theme lookup
|
|
878
|
+
* it). Pure: no DOM, no theme lookup; the caller has already resolved the base
|
|
881
879
|
* colour and extracted each modifier's `{ name, value }`.
|
|
882
880
|
*
|
|
883
881
|
* An unparseable base hex is returned unchanged (with no transforms applied); an
|
|
@@ -960,15 +958,14 @@ function applyColorTransforms(baseHex, transforms) {
|
|
|
960
958
|
alpha
|
|
961
959
|
};
|
|
962
960
|
}
|
|
963
|
-
|
|
964
|
-
//#region src/read/oxml/fill.ts
|
|
961
|
+
// src/read/oxml/fill.ts
|
|
965
962
|
/**
|
|
966
963
|
* Shared solid-fill read/mutation helpers for DrawingML colour-bearing parents:
|
|
967
964
|
* a run's `a:rPr`, a shape's `p:spPr` / `p:grpSpPr`, and a line's `a:ln`.
|
|
968
965
|
*
|
|
969
966
|
* A parent carries at most one `EG_FillProperties` choice, so setting a solid
|
|
970
967
|
* fill first clears any competing choice, then inserts `a:solidFill` in document
|
|
971
|
-
* order. These helpers never mark a part dirty
|
|
968
|
+
* order. These helpers never mark a part dirty; callers own that.
|
|
972
969
|
*/
|
|
973
970
|
/** Normalize a 6-hex RGB string (optional leading `#`) to upper-case, or throw. */
|
|
974
971
|
function normalizeHex(value) {
|
|
@@ -1062,18 +1059,17 @@ function setSolidFill(parent, afterOrder, color) {
|
|
|
1062
1059
|
setAttr(clr, "val", color.val);
|
|
1063
1060
|
fill.appendChild(clr);
|
|
1064
1061
|
}
|
|
1065
|
-
|
|
1066
|
-
//#region src/read/oxml/preset-color.ts
|
|
1062
|
+
// src/read/oxml/preset-color.ts
|
|
1067
1063
|
/**
|
|
1068
1064
|
* The ECMA-376 preset colour table (`a:prstClr/@val` → literal RGB).
|
|
1069
1065
|
*
|
|
1070
1066
|
* `ST_PresetColorVal` (§20.1.10.47) enumerates 190 names, and the standard gives each one an
|
|
1071
1067
|
* RGB triple. They are the HTML/CSS named colours: every value in the enumeration has the same
|
|
1072
1068
|
* RGB here as the CSS colour of the same name, and the 190 names collapse to the 140 distinct
|
|
1073
|
-
* CSS names below through three mechanical spelling rules the enumeration adds on top
|
|
1069
|
+
* CSS names below through three mechanical spelling rules the enumeration adds on top:
|
|
1074
1070
|
*
|
|
1075
1071
|
* - abbreviated prefixes: `dk*`/`lt*`/`med*` alongside `dark*`/`light*`/`medium*`
|
|
1076
|
-
* (`dkSlateBlue` is `darkSlateBlue`), and only as a *prefix*
|
|
1072
|
+
* (`dkSlateBlue` is `darkSlateBlue`), and only as a *prefix* (`medium…` is not `mediumium…`);
|
|
1077
1073
|
* - both spellings of grey: `dimGrey` is `dimGray`;
|
|
1078
1074
|
* - case: the enumeration is camelCase, the table below is folded to lower case.
|
|
1079
1075
|
*
|
|
@@ -1084,7 +1080,7 @@ function setSolidFill(parent, afterOrder, color) {
|
|
|
1084
1080
|
*
|
|
1085
1081
|
* This is spec data, not observed behaviour: the values come from the enumeration's own
|
|
1086
1082
|
* documentation, and the table is asserted complete against the schema's 190-value list in
|
|
1087
|
-
* `test/read/preset-color.test.
|
|
1083
|
+
* `test/read/preset-color.test.ts`. A name the reader cannot resolve is a hole in the table,
|
|
1088
1084
|
* and that test is what finds it.
|
|
1089
1085
|
*/
|
|
1090
1086
|
/**
|
|
@@ -1248,8 +1244,7 @@ function presetColorHex(name) {
|
|
|
1248
1244
|
const canonical = name.toLowerCase().replace(/grey/g, "gray").replace(/^dk/, "dark").replace(/^lt/, "light").replace(/^med(?!ium)/, "medium");
|
|
1249
1245
|
return PRESET_COLOR_HEX[canonical] ?? null;
|
|
1250
1246
|
}
|
|
1251
|
-
|
|
1252
|
-
//#region src/read/oxml/theme.ts
|
|
1247
|
+
// src/read/oxml/theme.ts
|
|
1253
1248
|
/**
|
|
1254
1249
|
* Theme colour resolution: turning a DrawingML colour reference or a style-matrix reference into
|
|
1255
1250
|
* something literal.
|
|
@@ -1269,7 +1264,7 @@ function presetColorHex(name) {
|
|
|
1269
1264
|
* order to *bake* it into a slide.
|
|
1270
1265
|
*
|
|
1271
1266
|
* They must agree, so they share this. The passes that write results back into a part live with
|
|
1272
|
-
* the other import operations, not here
|
|
1267
|
+
* the other import operations, not here; this module deliberately has no mutating exports.
|
|
1273
1268
|
* Placeholder-inheritance resolution (what a placeholder gets from its layout/master chain) is
|
|
1274
1269
|
* its own concern and lives in `placeholder-inherit.ts`.
|
|
1275
1270
|
*/
|
|
@@ -1323,8 +1318,8 @@ function isA(el, local) {
|
|
|
1323
1318
|
* `50%` is linear-light or sRGB-encoded decides the answer and neither the schema nor the
|
|
1324
1319
|
* transform work in `color-transform.ts` settles it. Guessing would produce a colour that is
|
|
1325
1320
|
* wrong by a gamma curve and says nothing about it, which is worse than reporting no colour.
|
|
1326
|
-
* Closing it needs a render oracle
|
|
1327
|
-
* and the painted pixel read back
|
|
1321
|
+
* Closing it needs a render oracle (a deck with an `a:scrgbClr` fill authored in PowerPoint
|
|
1322
|
+
* and the painted pixel read back), not a reading of the schema.
|
|
1328
1323
|
*/
|
|
1329
1324
|
function colorElementHex(color) {
|
|
1330
1325
|
if (!color) return null;
|
|
@@ -1349,7 +1344,7 @@ function themeElementsOfRoot(themeRoot) {
|
|
|
1349
1344
|
* literal (unmapped token, or a colour model we do not flatten).
|
|
1350
1345
|
*
|
|
1351
1346
|
* Five of `a:EG_ColorChoice`'s six members resolve: `a:srgbClr`, `a:sysClr`, `a:schemeClr`,
|
|
1352
|
-
* `a:prstClr` (through the {@link presetColorHex} table) and `a:hslClr`. `a:scrgbClr` does not
|
|
1347
|
+
* `a:prstClr` (through the {@link presetColorHex} table) and `a:hslClr`. `a:scrgbClr` does not;
|
|
1353
1348
|
* see {@link colorElementHex} for why that one is a `null` rather than a guess.
|
|
1354
1349
|
*/
|
|
1355
1350
|
function resolveColor(color, ctx) {
|
|
@@ -1399,11 +1394,11 @@ function resolveThemeFont(typeface, fontScheme) {
|
|
|
1399
1394
|
}
|
|
1400
1395
|
/**
|
|
1401
1396
|
* Swap one colour element for another, MOVING the old element's transform children onto the new
|
|
1402
|
-
* one
|
|
1397
|
+
* one and, optionally, seeding it with a set of transforms of its own first.
|
|
1403
1398
|
*
|
|
1404
1399
|
* The move is the whole rule the flatten path exists to preserve: only the base reference is
|
|
1405
1400
|
* swapped, never the transform math. Three sites implemented it and a fourth cloned instead of
|
|
1406
|
-
* moving, each with its own `while (old.firstChild) …` loop
|
|
1401
|
+
* moving, each with its own `while (old.firstChild) …` loop: duplication of exactly the one
|
|
1407
1402
|
* invariant that must not vary.
|
|
1408
1403
|
*
|
|
1409
1404
|
* Transform ORDER matters and is the reason `seed` is separate: `substitutePhClr` puts the
|
|
@@ -1438,7 +1433,7 @@ function fmtEntry(ctx, listName, idx) {
|
|
|
1438
1433
|
return entry ? entry.cloneNode(true) : null;
|
|
1439
1434
|
}
|
|
1440
1435
|
/**
|
|
1441
|
-
* Build the explicit fill element a `p:style` `a:fillRef` resolves to
|
|
1436
|
+
* Build the explicit fill element a `p:style` `a:fillRef` resolves to: the indexed
|
|
1442
1437
|
* `fmtScheme` `fillStyleLst`/`bgFillStyleLst` entry (deep-cloned) with its `phClr`
|
|
1443
1438
|
* replaced by the ref's resolved colour. Pure: mutates neither the ref nor the
|
|
1444
1439
|
* theme. `null` when the ref is absent, `idx` is 0/unset, or the entry or its
|
|
@@ -1456,7 +1451,7 @@ function styleRefFill(fillRef, ctx) {
|
|
|
1456
1451
|
return fill;
|
|
1457
1452
|
}
|
|
1458
1453
|
/**
|
|
1459
|
-
* Build the explicit `a:ln` element a `p:style` `a:lnRef` resolves to
|
|
1454
|
+
* Build the explicit `a:ln` element a `p:style` `a:lnRef` resolves to: the indexed
|
|
1460
1455
|
* `fmtScheme` `lnStyleLst` entry (deep-cloned) with its `phClr` replaced by the
|
|
1461
1456
|
* ref's resolved colour. Pure; the line/read counterpart of {@link styleRefFill}.
|
|
1462
1457
|
*/
|
|
@@ -1470,13 +1465,12 @@ function styleRefLine(lnRef, ctx) {
|
|
|
1470
1465
|
substitutePhClr(ln, ref);
|
|
1471
1466
|
return ln;
|
|
1472
1467
|
}
|
|
1473
|
-
|
|
1474
|
-
//#region src/read/oxml/slide-dom.ts
|
|
1468
|
+
// src/read/oxml/slide-dom.ts
|
|
1475
1469
|
/**
|
|
1476
1470
|
* The `p:cSld` of a slide/layout/master/notes part root.
|
|
1477
1471
|
*
|
|
1478
1472
|
* Takes a nullable root and returns a nullable element so a caller can chain from
|
|
1479
|
-
* `part?.dom.documentElement` without a guard of its own
|
|
1473
|
+
* `part?.dom.documentElement` without a guard of its own, which is what a dozen callers were
|
|
1480
1474
|
* writing by hand, each in a slightly different spelling (`root && …`, `root ? … : null`).
|
|
1481
1475
|
*/
|
|
1482
1476
|
function cSldOf(root) {
|
|
@@ -1490,8 +1484,8 @@ function spTreeOf(root) {
|
|
|
1490
1484
|
/**
|
|
1491
1485
|
* The `p:nvPr` of a shape, whichever `*nvPr` wrapper its kind uses.
|
|
1492
1486
|
*
|
|
1493
|
-
* The wrapper's name varies by shape kind
|
|
1494
|
-
* `p:nvGraphicFramePr`, `p:nvCxnSpPr`, `p:nvGrpSpPr`
|
|
1487
|
+
* The wrapper's name varies by shape kind (`p:nvSpPr` on a `p:sp`, `p:nvPicPr` on a `p:pic`,
|
|
1488
|
+
* `p:nvGraphicFramePr`, `p:nvCxnSpPr`, `p:nvGrpSpPr`), but it is always the shape's first child
|
|
1495
1489
|
* element, and `p:nvPr` is always inside it. Taking the first child element rather than naming
|
|
1496
1490
|
* the wrapper is what lets one helper serve every kind; the callers that named `p:nvSpPr`
|
|
1497
1491
|
* silently returned null on a picture.
|
|
@@ -1633,8 +1627,7 @@ function collectElements(node, out) {
|
|
|
1633
1627
|
out.push(node);
|
|
1634
1628
|
for (let child = node.firstChild; child; child = child.nextSibling) if (child.nodeType === 1) collectElements(child, out);
|
|
1635
1629
|
}
|
|
1636
|
-
|
|
1637
|
-
//#region src/read/oxml/placeholder-inherit.ts
|
|
1630
|
+
// src/read/oxml/placeholder-inherit.ts
|
|
1638
1631
|
/**
|
|
1639
1632
|
* Resolving what a placeholder shape *inherits* from its source layout/master chain.
|
|
1640
1633
|
*
|
|
@@ -1783,6 +1776,22 @@ function lstStyleLevelDefRPr(listStyle, level) {
|
|
|
1783
1776
|
const lvl = firstChild(listStyle, `a:lvl${level + 1}pPr`) ?? firstChild(listStyle, "a:defPPr");
|
|
1784
1777
|
return lvl ? firstChild(lvl, "a:defRPr") : null;
|
|
1785
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* The paragraph properties for `level` (0-based) in a `CT_TextListStyle`: the level-specific
|
|
1781
|
+
* `a:lvlNpPr`, else the `a:defPPr` fallback, as {@link lstStyleLevelDefRPr} reads them.
|
|
1782
|
+
*/
|
|
1783
|
+
function lstStyleLevel(listStyle, level) {
|
|
1784
|
+
if (!listStyle) return null;
|
|
1785
|
+
return firstChild(listStyle, `a:lvl${level + 1}pPr`) ?? firstChild(listStyle, "a:defPPr");
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* The paragraph-property tiers a placeholder's paragraph at `level` inherits from, most specific
|
|
1789
|
+
* first: each source list style's level element. The paragraph-level twin of
|
|
1790
|
+
* {@link placeholderInheritedDefRPrs}.
|
|
1791
|
+
*/
|
|
1792
|
+
function placeholderInheritedLevels(type, idx, level, ctx) {
|
|
1793
|
+
return styleTiers(type, idx, ctx).map((tier) => lstStyleLevel(tier, level)).filter((t) => t !== null);
|
|
1794
|
+
}
|
|
1786
1795
|
/** The `a:solidFill` of a level's `a:defRPr`, or `null` when none is defined there. */
|
|
1787
1796
|
function lstStyleLevelFill(listStyle, level) {
|
|
1788
1797
|
const defRPr = lstStyleLevelDefRPr(listStyle, level);
|
|
@@ -1934,15 +1943,14 @@ function slideDefinesProp(name, run, pPr, slideLst, level) {
|
|
|
1934
1943
|
const slideDefRPr = lstStyleLevelDefRPr(slideLst, level);
|
|
1935
1944
|
return !!(slideDefRPr && attr(slideDefRPr, name) != null);
|
|
1936
1945
|
}
|
|
1937
|
-
|
|
1938
|
-
//#region src/read/api/coords.ts
|
|
1946
|
+
// src/read/api/coords.ts
|
|
1939
1947
|
/**
|
|
1940
|
-
* ts
|
|
1948
|
+
* pptx-ts: units at the read model's boundary, validated on the way in and converted on the way out.
|
|
1941
1949
|
*
|
|
1942
1950
|
* Every read-side setter that takes a measurement has to answer the same two questions before
|
|
1943
1951
|
* the value reaches an attribute: is it finite, and (for an extent) is it positive. `NaN` or
|
|
1944
1952
|
* `Infinity` written verbatim makes the part invalid, and silently coercing it to something
|
|
1945
|
-
* drawable is exactly the footgun the project's API policy rules out
|
|
1953
|
+
* drawable is exactly the footgun the project's API policy rules out, so both are a throw.
|
|
1946
1954
|
*
|
|
1947
1955
|
* The two halves used to be split across `table-edit.ts` (threw, then rounded) and `slide.ts`
|
|
1948
1956
|
* (threw only, with the rounding done eight lines later at each build site), under two spellings
|
|
@@ -1960,7 +1968,7 @@ function checkFiniteEmu(value, field, code = "coord/non-finite") {
|
|
|
1960
1968
|
return Math.round(value);
|
|
1961
1969
|
}
|
|
1962
1970
|
/**
|
|
1963
|
-
* As {@link checkFiniteEmu}, and additionally rejects a non-positive value
|
|
1971
|
+
* As {@link checkFiniteEmu}, and additionally rejects a non-positive value: the guard an
|
|
1964
1972
|
* *extent* needs, since `ST_PositiveCoordinate` has no room for zero or below and a zero-size
|
|
1965
1973
|
* shape is a degenerate result rather than a small one.
|
|
1966
1974
|
*/
|
|
@@ -1989,8 +1997,8 @@ function checkLineWidthEmu(value, field, code) {
|
|
|
1989
1997
|
* insets in points while the DOM stores EMU, so `x === null ? null : x / EMU_PER_POINT` stood at
|
|
1990
1998
|
* five getters across `chart.ts`, `shapes/base.ts`, `table.ts` and `text.ts`.
|
|
1991
1999
|
*
|
|
1992
|
-
* The accumulator sites whose target is `number | undefined` do not come through here
|
|
1993
|
-
* propagating a `null` would only have to be unwrapped again
|
|
2000
|
+
* The accumulator sites whose target is `number | undefined` do not come through here
|
|
2001
|
+
* (propagating a `null` would only have to be unwrapped again), but they do not open-code the
|
|
1994
2002
|
* division either: they unwrap the `null` themselves and call `emuToPoints`. {@link emuToPoints}
|
|
1995
2003
|
* states the whole policy and names the one place still allowed to divide inline.
|
|
1996
2004
|
*/
|
|
@@ -2015,8 +2023,7 @@ function ptFromEmu(emu) {
|
|
|
2015
2023
|
function ptFromHundredths(value) {
|
|
2016
2024
|
return value === null ? null : value / 100;
|
|
2017
2025
|
}
|
|
2018
|
-
|
|
2019
|
-
//#region src/read/api/shapes/geometry.ts
|
|
2026
|
+
// src/read/api/shapes/geometry.ts
|
|
2020
2027
|
/**
|
|
2021
2028
|
* Reading geometry out of a shape: transform boxes, rotation and flips, and custom-geometry paths.
|
|
2022
2029
|
*
|
|
@@ -2024,12 +2031,18 @@ function ptFromHundredths(value) {
|
|
|
2024
2031
|
* readers with no theme or relationship context.
|
|
2025
2032
|
*/
|
|
2026
2033
|
/**
|
|
2027
|
-
*
|
|
2028
|
-
* (the
|
|
2029
|
-
* a non-numeric value degrades to `0` rather than crashing (documented edge).
|
|
2034
|
+
* An `ST_AdjCoordinate` attribute of a path segment: a raw path-unit integer, or the guide name it
|
|
2035
|
+
* references (the string form). Absent reads `0`.
|
|
2030
2036
|
*/
|
|
2031
|
-
function
|
|
2032
|
-
|
|
2037
|
+
function adjValue(element, name) {
|
|
2038
|
+
const value = element ? attr(element, name) : null;
|
|
2039
|
+
if (value === null || value === "") return 0;
|
|
2040
|
+
return numberValue(value) ?? value;
|
|
2041
|
+
}
|
|
2042
|
+
/** An `ST_AdjAngle` attribute of an `arcTo` in degrees, or the guide name it references. Absent reads `0`. */
|
|
2043
|
+
function adjAngle(seg, name) {
|
|
2044
|
+
const value = adjValue(seg, name);
|
|
2045
|
+
return typeof value === "number" ? value / ANGLE_UNITS_PER_DEGREE : value;
|
|
2033
2046
|
}
|
|
2034
2047
|
/** Parse one `<a:path>` into its viewport attrs (with schema defaults) and ordered segments. */
|
|
2035
2048
|
function readGeometryPath(path) {
|
|
@@ -2043,44 +2056,44 @@ function readGeometryPath(path) {
|
|
|
2043
2056
|
case "moveTo":
|
|
2044
2057
|
commands.push({
|
|
2045
2058
|
cmd: "moveTo",
|
|
2046
|
-
x:
|
|
2047
|
-
y:
|
|
2059
|
+
x: adjValue(pts[0], "x"),
|
|
2060
|
+
y: adjValue(pts[0], "y")
|
|
2048
2061
|
});
|
|
2049
2062
|
break;
|
|
2050
2063
|
case "lnTo":
|
|
2051
2064
|
commands.push({
|
|
2052
2065
|
cmd: "lnTo",
|
|
2053
|
-
x:
|
|
2054
|
-
y:
|
|
2066
|
+
x: adjValue(pts[0], "x"),
|
|
2067
|
+
y: adjValue(pts[0], "y")
|
|
2055
2068
|
});
|
|
2056
2069
|
break;
|
|
2057
2070
|
case "cubicBezTo":
|
|
2058
2071
|
commands.push({
|
|
2059
2072
|
cmd: "cubicBezTo",
|
|
2060
|
-
x1:
|
|
2061
|
-
y1:
|
|
2062
|
-
x2:
|
|
2063
|
-
y2:
|
|
2064
|
-
x:
|
|
2065
|
-
y:
|
|
2073
|
+
x1: adjValue(pts[0], "x"),
|
|
2074
|
+
y1: adjValue(pts[0], "y"),
|
|
2075
|
+
x2: adjValue(pts[1], "x"),
|
|
2076
|
+
y2: adjValue(pts[1], "y"),
|
|
2077
|
+
x: adjValue(pts[2], "x"),
|
|
2078
|
+
y: adjValue(pts[2], "y")
|
|
2066
2079
|
});
|
|
2067
2080
|
break;
|
|
2068
2081
|
case "quadBezTo":
|
|
2069
2082
|
commands.push({
|
|
2070
2083
|
cmd: "quadBezTo",
|
|
2071
|
-
x1:
|
|
2072
|
-
y1:
|
|
2073
|
-
x:
|
|
2074
|
-
y:
|
|
2084
|
+
x1: adjValue(pts[0], "x"),
|
|
2085
|
+
y1: adjValue(pts[0], "y"),
|
|
2086
|
+
x: adjValue(pts[1], "x"),
|
|
2087
|
+
y: adjValue(pts[1], "y")
|
|
2075
2088
|
});
|
|
2076
2089
|
break;
|
|
2077
2090
|
case "arcTo":
|
|
2078
2091
|
commands.push({
|
|
2079
2092
|
cmd: "arcTo",
|
|
2080
|
-
wR:
|
|
2081
|
-
hR:
|
|
2082
|
-
stAng: (
|
|
2083
|
-
swAng: (
|
|
2093
|
+
wR: adjValue(seg, "wR"),
|
|
2094
|
+
hR: adjValue(seg, "hR"),
|
|
2095
|
+
stAng: adjAngle(seg, "stAng"),
|
|
2096
|
+
swAng: adjAngle(seg, "swAng")
|
|
2084
2097
|
});
|
|
2085
2098
|
break;
|
|
2086
2099
|
case "close": commands.push({ cmd: "close" });
|
|
@@ -2120,17 +2133,16 @@ function transformFlipH(xfrm) {
|
|
|
2120
2133
|
function transformFlipV(xfrm) {
|
|
2121
2134
|
return boolValue(attr(xfrm, "flipV")) === true;
|
|
2122
2135
|
}
|
|
2123
|
-
|
|
2124
|
-
//#region src/read/api/theme-context.ts
|
|
2136
|
+
// src/read/api/theme-context.ts
|
|
2125
2137
|
/**
|
|
2126
2138
|
* Resolve a slide's theme colour context: the slide → slideLayout → slideMaster
|
|
2127
2139
|
* → theme walk that turns a `schemeClr` token into a literal hex, plus the
|
|
2128
2140
|
* `a:solidFill` helper the read-model colour getters share.
|
|
2129
2141
|
*
|
|
2130
|
-
* One implementation backs two callers
|
|
2142
|
+
* One implementation backs two callers: the read-model getters
|
|
2131
2143
|
* (`Slide.themeContext` → `Shape.resolvedFill` / `Run.resolvedColor`) and the
|
|
2132
2144
|
* `importSlide` `theme: 'preserve'` flatten path, which layers its `fmtScheme` /
|
|
2133
|
-
* background needs on top of {@link resolveSlideThemeParts}
|
|
2145
|
+
* background needs on top of {@link resolveSlideThemeParts}. So a token resolves
|
|
2134
2146
|
* identically whether it is read or baked.
|
|
2135
2147
|
*/
|
|
2136
2148
|
/** The document element of a part, or `null` when the partname/part is absent. */
|
|
@@ -2140,7 +2152,7 @@ function documentElement(opc, partName) {
|
|
|
2140
2152
|
/**
|
|
2141
2153
|
* The presentation's `p:defaultTextStyle` (`presentation.xml`, reached via the
|
|
2142
2154
|
* package `officeDocument` relationship), or `null` when absent. This is
|
|
2143
|
-
* PowerPoint's lowest-priority text fallback
|
|
2155
|
+
* PowerPoint's lowest-priority text fallback: the bottom tier of a slide run's
|
|
2144
2156
|
* size/colour/face/bold resolution (see `ThemeContext.defaultTextStyle`).
|
|
2145
2157
|
*/
|
|
2146
2158
|
function presentationDefaultTextStyle(opc) {
|
|
@@ -2207,7 +2219,7 @@ function resolveSlideColorContext(opc, parts) {
|
|
|
2207
2219
|
* are deliberately left absent. Instead the notesMaster's `p:notesStyle` is carried
|
|
2208
2220
|
* as `notesStyle`, and the notes body `TextFrame` is built with a placeholder
|
|
2209
2221
|
* context (see `NotesPlaceholder`) so a body run's
|
|
2210
|
-
* effective size/face/bold (and inherited colour) resolve against it
|
|
2222
|
+
* effective size/face/bold (and inherited colour) resolve against it, the notes
|
|
2211
2223
|
* analogue of the slide placeholder chain. The maps are empty when the
|
|
2212
2224
|
* notesMaster/theme chain is incomplete, in which case tokens stay unresolved.
|
|
2213
2225
|
*/
|
|
@@ -2226,7 +2238,7 @@ function resolveNotesColorContext(opc, notesPartName) {
|
|
|
2226
2238
|
* The colour context a *slide master*'s own getters resolve against: the master's
|
|
2227
2239
|
* `p:clrMap` plus its theme's `clrScheme`/`fmtScheme`/`fontScheme` (walked master →
|
|
2228
2240
|
* theme). Backs the text frames of `SlideMaster`'s
|
|
2229
|
-
* placeholders
|
|
2241
|
+
* placeholders: a master placeholder run's own `schemeClr` resolves to a literal
|
|
2230
2242
|
* hex the same way a slide run's does. `masterRoot` is carried so an inherited
|
|
2231
2243
|
* placeholder value still resolves against the master's own text styles. The maps
|
|
2232
2244
|
* are empty when the theme is missing, in which case tokens stay unresolved.
|
|
@@ -2245,7 +2257,7 @@ function resolveMasterColorContext(opc, masterPartName) {
|
|
|
2245
2257
|
* The colour context a *slide layout*'s own getters resolve against: walk layout →
|
|
2246
2258
|
* master → theme. The effective `clrMap` is the layout's own
|
|
2247
2259
|
* `p:clrMapOvr/a:overrideClrMapping` when present (rare), else the master's
|
|
2248
|
-
* `p:clrMap
|
|
2260
|
+
* `p:clrMap`. The layout's usual `a:masterClrMapping` means "inherit the master
|
|
2249
2261
|
* map". `clrScheme`/`fmtScheme`/`fontScheme` come from the master's theme, and both
|
|
2250
2262
|
* `layoutRoot`/`masterRoot` are carried for inherited-placeholder resolution. Backs
|
|
2251
2263
|
* the text frames of `SlideLayout`'s placeholders.
|
|
@@ -2297,8 +2309,8 @@ function themeElementsOf(opc, themePartName) {
|
|
|
2297
2309
|
* The colour a run effectively renders when its own `a:rPr` defines none,
|
|
2298
2310
|
* resolved to a full {@link ResolvedColor}. Walks the inheritance the way
|
|
2299
2311
|
* PowerPoint does: the paragraph's `a:pPr/a:defRPr` colour, then the slide text
|
|
2300
|
-
* body's `a:lstStyle` colour for the run's `level`, then
|
|
2301
|
-
* run
|
|
2312
|
+
* body's `a:lstStyle` colour for the run's `level`, then (only for a placeholder
|
|
2313
|
+
* run) the placeholder's layout → master → master-`p:txStyles` chain (via
|
|
2302
2314
|
* {@link placeholderInheritedFill}), then the presentation's `p:defaultTextStyle`
|
|
2303
2315
|
* for the run's level (`ctx.defaultTextStyle`). The first tier that defines a
|
|
2304
2316
|
* colour wins. `ph` is `null` for a non-placeholder run, which skips the
|
|
@@ -2321,11 +2333,11 @@ function resolveInheritedRunColor(ph, level, pPr, slideLstStyle, ctx) {
|
|
|
2321
2333
|
/**
|
|
2322
2334
|
* The `a:defRPr` tiers a run resolves an inherited character property against, in
|
|
2323
2335
|
* priority order: the paragraph's `a:pPr/a:defRPr`, then the slide text body's
|
|
2324
|
-
* `a:lstStyle` level `a:defRPr`, then
|
|
2336
|
+
* `a:lstStyle` level `a:defRPr`, then (only for a placeholder run) the
|
|
2325
2337
|
* placeholder's layout → master → master-`p:txStyles` chain (via
|
|
2326
2338
|
* {@link placeholderInheritedDefRPrs}), then the presentation's
|
|
2327
2339
|
* `p:defaultTextStyle` level `a:defRPr` (`ctx.defaultTextStyle`). The first tier
|
|
2328
|
-
* that defines the property wins
|
|
2340
|
+
* that defines the property wins; each property resolves independently, mirroring
|
|
2329
2341
|
* how the colour resolver walks the same chain. `ph` is `null` for a
|
|
2330
2342
|
* non-placeholder run, which skips the placeholder chain but still reaches
|
|
2331
2343
|
* `p:defaultTextStyle`.
|
|
@@ -2380,8 +2392,8 @@ function resolveInheritedRunFontFace(ph, level, pPr, slideLstStyle, ctx) {
|
|
|
2380
2392
|
* {@link resolveInheritedRunSize}: the first `a:defRPr/@<name>` in the paragraph →
|
|
2381
2393
|
* slide → (placeholder) layout → master → `p:txStyles` → `p:defaultTextStyle`
|
|
2382
2394
|
* chain. The first tier that *states* the attribute wins, and its value is parsed
|
|
2383
|
-
* by {@link boolValue}
|
|
2384
|
-
* getters use
|
|
2395
|
+
* by {@link boolValue} (the same `xsd:boolean` parser the run's own `bold`/`italic`
|
|
2396
|
+
* getters use), so an unparseable value reports `null` rather than being read as
|
|
2385
2397
|
* `false`. `null` when nothing in the chain states it.
|
|
2386
2398
|
*/
|
|
2387
2399
|
function resolveInheritedRunFlag(name, ph, level, pPr, slideLstStyle, ctx) {
|
|
@@ -2446,8 +2458,8 @@ function resolveInheritedFrame(ph, ctx) {
|
|
|
2446
2458
|
}
|
|
2447
2459
|
/**
|
|
2448
2460
|
* Resolve a DrawingML colour *element* (every `a:EG_ColorChoice` member except
|
|
2449
|
-
* `a:scrgbClr
|
|
2450
|
-
* {@link ResolvedColor}
|
|
2461
|
+
* `a:scrgbClr`; see `read/oxml/theme.ts`) against `ctx` into a full
|
|
2462
|
+
* {@link ResolvedColor}: base hex, raw transform list,
|
|
2451
2463
|
* and the `effectiveHex`/`alpha` after applying those transforms. `null` when the
|
|
2452
2464
|
* element cannot be made literal (unmapped token, or a colour model we do not
|
|
2453
2465
|
* resolve). Shared by the solid-fill and gradient-stop colour reads.
|
|
@@ -2502,7 +2514,7 @@ function resolveSolidFillColor(container, ctx) {
|
|
|
2502
2514
|
* `Shape.resolvedFill` when the shape carries no
|
|
2503
2515
|
* explicit `spPr` fill choice. `null` when there is no `fillRef`, it cannot be
|
|
2504
2516
|
* resolved, or the indexed style entry is not a solid fill (a gradient style fill
|
|
2505
|
-
* has no single colour
|
|
2517
|
+
* has no single colour; read it through `gradientStops` instead).
|
|
2506
2518
|
*/
|
|
2507
2519
|
function resolveStyleFillColor(shape, ctx) {
|
|
2508
2520
|
const style = firstChild(shape, "p:style");
|
|
@@ -2523,7 +2535,7 @@ function resolveStyleLineColor(shape, ctx) {
|
|
|
2523
2535
|
}
|
|
2524
2536
|
/**
|
|
2525
2537
|
* Resolve the text colour and typeface a shape derives from its `p:style/a:fontRef`
|
|
2526
|
-
* (the style-matrix font reference)
|
|
2538
|
+
* (the style-matrix font reference): the tier PowerPoint applies to a shape's runs
|
|
2527
2539
|
* just below their own `a:rPr` and above the placeholder/`p:defaultTextStyle` chain.
|
|
2528
2540
|
* The `a:fontRef` child colour resolves through `ctx` like any other (it can carry
|
|
2529
2541
|
* `lumMod`/`shade` transforms); `@idx` (`major`|`minor`|`none`) maps to the theme's
|
|
@@ -2541,54 +2553,12 @@ function resolveStyleFontRef(shape, ctx) {
|
|
|
2541
2553
|
face: token ? resolveThemeFont(token, ctx.fontScheme ?? null) : null
|
|
2542
2554
|
};
|
|
2543
2555
|
}
|
|
2544
|
-
|
|
2545
|
-
//#region src/read/api/gradient.ts
|
|
2546
|
-
/**
|
|
2547
|
-
* Shared gradient-fill reader for DrawingML colour-bearing containers: a shape's
|
|
2548
|
-
* `p:spPr`, a line's `a:ln`, and a slide's `p:bg/p:bgPr`. Decodes an `a:gradFill`
|
|
2549
|
-
* into its stops (colour + position) plus the linear/path geometry, resolving each
|
|
2550
|
-
* stop's colour against the slide theme.
|
|
2551
|
-
*/
|
|
2552
|
-
/**
|
|
2553
|
-
* Read `a:gradFill/a:gsLst` stops from a container (`p:spPr` for a fill, `a:ln`
|
|
2554
|
-
* for a line stroke, or `p:bgPr` for a slide background). `null` when the
|
|
2555
|
-
* container has no gradient; `[]` when the gradient carries no stop list.
|
|
2556
|
-
*/
|
|
2557
|
-
function readGradientStops(container, ctx) {
|
|
2558
|
-
const grad = firstChild(container, "a:gradFill");
|
|
2559
|
-
if (!grad) return null;
|
|
2560
|
-
const gsLst = firstChild(grad, "a:gsLst");
|
|
2561
|
-
if (!gsLst) return [];
|
|
2562
|
-
return getElements(gsLst, "a:gs").map((gs) => ({
|
|
2563
|
-
position: pctAttr(gs, "pos"),
|
|
2564
|
-
colorRef: readColorRef(firstChildElement(gs), ctx)
|
|
2565
|
-
}));
|
|
2566
|
-
}
|
|
2567
|
-
/**
|
|
2568
|
-
* Read the full `a:gradFill` (stops + linear angle / path shape) from a container
|
|
2569
|
-
* (`p:spPr` for a fill, `a:ln` for a line stroke, or `p:bgPr` for a slide
|
|
2570
|
-
* background). `null` when the container has no gradient.
|
|
2571
|
-
*/
|
|
2572
|
-
function readGradientFill(container, ctx) {
|
|
2573
|
-
const grad = firstChild(container, "a:gradFill");
|
|
2574
|
-
if (!grad) return null;
|
|
2575
|
-
const lin = firstChild(grad, "a:lin");
|
|
2576
|
-
const path = firstChild(grad, "a:path");
|
|
2577
|
-
const ang = lin ? numberValue(attr(lin, "ang")) : null;
|
|
2578
|
-
return {
|
|
2579
|
-
kind: lin ? "linear" : path ? "path" : null,
|
|
2580
|
-
angleDeg: ang === null ? null : ang / ANGLE_UNITS_PER_DEGREE,
|
|
2581
|
-
path: path ? attr(path, "path") ?? null : null,
|
|
2582
|
-
stops: readGradientStops(container, ctx) ?? []
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
//#endregion
|
|
2586
|
-
//#region src/read/api/picture-fill.ts
|
|
2556
|
+
// src/read/api/picture-fill.ts
|
|
2587
2557
|
/**
|
|
2588
2558
|
* Shared picture-fill reader for DrawingML fill-bearing containers: a shape's
|
|
2589
2559
|
* `p:spPr`, a table cell's `a:tcPr`, and a slide's `p:bg/p:bgPr`. Decodes an
|
|
2590
2560
|
* `a:blipFill` into the embedded image reference plus the geometry that decides
|
|
2591
|
-
* how the image covers the surface
|
|
2561
|
+
* how the image covers the surface: stretch vs tile, the source crop, and the
|
|
2592
2562
|
* tile offsets/scale.
|
|
2593
2563
|
*
|
|
2594
2564
|
* A picture fill is not a colour, so the solid-fill accessors
|
|
@@ -2619,7 +2589,7 @@ function readTile(tile) {
|
|
|
2619
2589
|
}
|
|
2620
2590
|
/**
|
|
2621
2591
|
* Read an `a:blipFill` from a container (`p:spPr`, `a:tcPr`, or `p:bgPr`) into a
|
|
2622
|
-
* {@link PictureFill}. `rels`
|
|
2592
|
+
* {@link PictureFill}. `rels` (the *owning part's* relationships) resolves the
|
|
2623
2593
|
* blip's `r:embed` to an absolute partname; pass `null` (or omit) to read the
|
|
2624
2594
|
* geometry without resolving the image. `null` when the container's fill is not
|
|
2625
2595
|
* a picture.
|
|
@@ -2647,14 +2617,57 @@ function readPictureFill(container, rels = null) {
|
|
|
2647
2617
|
rotWithShape: boolValue(attr(blipFill, "rotWithShape"))
|
|
2648
2618
|
};
|
|
2649
2619
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2620
|
+
// src/read/api/gradient.ts
|
|
2621
|
+
/**
|
|
2622
|
+
* Shared gradient-fill reader for DrawingML colour-bearing containers: a shape's
|
|
2623
|
+
* `p:spPr`, a line's `a:ln`, and a slide's `p:bg/p:bgPr`. Decodes an `a:gradFill`
|
|
2624
|
+
* into its stops (colour + position) plus the linear/path geometry, resolving each
|
|
2625
|
+
* stop's colour against the slide theme.
|
|
2626
|
+
*/
|
|
2627
|
+
/**
|
|
2628
|
+
* Read `a:gradFill/a:gsLst` stops from a container (`p:spPr` for a fill, `a:ln`
|
|
2629
|
+
* for a line stroke, or `p:bgPr` for a slide background). `null` when the
|
|
2630
|
+
* container has no gradient; `[]` when the gradient carries no stop list.
|
|
2631
|
+
*/
|
|
2632
|
+
function readGradientStops(container, ctx) {
|
|
2633
|
+
const grad = firstChild(container, "a:gradFill");
|
|
2634
|
+
if (!grad) return null;
|
|
2635
|
+
const gsLst = firstChild(grad, "a:gsLst");
|
|
2636
|
+
if (!gsLst) return [];
|
|
2637
|
+
return getElements(gsLst, "a:gs").map((gs) => ({
|
|
2638
|
+
position: pctAttr(gs, "pos"),
|
|
2639
|
+
colorRef: readColorRef(firstChildElement(gs), ctx)
|
|
2640
|
+
}));
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Read the full `a:gradFill` (stops + linear angle / path shape) from a container
|
|
2644
|
+
* (`p:spPr` for a fill, `a:ln` for a line stroke, or `p:bgPr` for a slide
|
|
2645
|
+
* background). `null` when the container has no gradient.
|
|
2646
|
+
*/
|
|
2647
|
+
function readGradientFill(container, ctx) {
|
|
2648
|
+
const grad = firstChild(container, "a:gradFill");
|
|
2649
|
+
if (!grad) return null;
|
|
2650
|
+
const lin = firstChild(grad, "a:lin");
|
|
2651
|
+
const path = firstChild(grad, "a:path");
|
|
2652
|
+
const ang = lin ? numberValue(attr(lin, "ang")) : null;
|
|
2653
|
+
const focus = path ? firstChild(path, "a:fillToRect") : null;
|
|
2654
|
+
return {
|
|
2655
|
+
kind: lin ? "linear" : path ? "path" : null,
|
|
2656
|
+
angleDeg: ang === null ? null : ang / ANGLE_UNITS_PER_DEGREE,
|
|
2657
|
+
path: path ? attr(path, "path") ?? null : null,
|
|
2658
|
+
stops: readGradientStops(container, ctx) ?? [],
|
|
2659
|
+
rotateWithShape: boolAttr(grad, "rotWithShape"),
|
|
2660
|
+
scaled: lin ? boolAttr(lin, "scaled") : null,
|
|
2661
|
+
focusRect: focus ? readRect(focus) : null
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
// src/read/api/pattern-fill.ts
|
|
2652
2665
|
/**
|
|
2653
2666
|
* Shared pattern-fill reader for DrawingML colour-bearing containers: a shape's `p:spPr`, a
|
|
2654
2667
|
* slide's `p:bg/p:bgPr`, and a table's `a:tblPr` / cell's `a:tcPr`.
|
|
2655
2668
|
*
|
|
2656
2669
|
* `a:pattFill` is a preset hatch name plus two wrapped colours, and decoding it is three
|
|
2657
|
-
* lines
|
|
2670
|
+
* lines, which is exactly why it had been open-coded in each place that needed it. Sharing
|
|
2658
2671
|
* it is what keeps the three from drifting into different answers for the same element, the
|
|
2659
2672
|
* same reason `readGradientFill` and
|
|
2660
2673
|
* `readPictureFill` are shared.
|
|
@@ -2678,8 +2691,7 @@ function readPatternFill(container, ctx) {
|
|
|
2678
2691
|
background: colorOf("a:bgClr")
|
|
2679
2692
|
};
|
|
2680
2693
|
}
|
|
2681
|
-
|
|
2682
|
-
//#region src/read/api/shapes/oxml.ts
|
|
2694
|
+
// src/read/api/shapes/oxml.ts
|
|
2683
2695
|
/**
|
|
2684
2696
|
* Shape-element plumbing: the small element lookups every shape kind performs, plus the
|
|
2685
2697
|
* shape-scoped re-exports of the shared schema-successor lists.
|
|
@@ -2707,8 +2719,7 @@ function emuFrom(parent, qname, attribute) {
|
|
|
2707
2719
|
function getOrAddSpPrXfrm(shapeElement) {
|
|
2708
2720
|
return getOrAddChild(getOrAddChild(shapeElement, "p:spPr", SHAPE_AFTER_SPPR), "a:xfrm", SPPR_AFTER_XFRM);
|
|
2709
2721
|
}
|
|
2710
|
-
|
|
2711
|
-
//#region src/read/api/shapes/group-transform.ts
|
|
2722
|
+
// src/read/api/shapes/group-transform.ts
|
|
2712
2723
|
/** Normalise a degree value into [0, 360). */
|
|
2713
2724
|
function normalizeDegrees(value) {
|
|
2714
2725
|
return (value % 360 + 360) % 360;
|
|
@@ -2733,8 +2744,8 @@ function rotatePoint(point, center, degrees) {
|
|
|
2733
2744
|
* `groups` runs **innermost first** (the shape's immediate `p:grpSp` parent, then
|
|
2734
2745
|
* outward). An empty list returns the shape's own box and orientation unchanged.
|
|
2735
2746
|
*
|
|
2736
|
-
* The returned `box` is the *unrotated* placement box
|
|
2737
|
-
* writes when you Ungroup
|
|
2747
|
+
* The returned `box` is the *unrotated* placement box (the same box PowerPoint
|
|
2748
|
+
* writes when you Ungroup), with the effective rotation reported separately.
|
|
2738
2749
|
* Values are exact; callers round or convert units as they see fit.
|
|
2739
2750
|
*
|
|
2740
2751
|
* Returns `null` when a group's `a:chExt` is degenerate (zero on either axis):
|
|
@@ -2806,8 +2817,7 @@ function composeGroupFrame(shape, groups) {
|
|
|
2806
2817
|
flipV
|
|
2807
2818
|
};
|
|
2808
2819
|
}
|
|
2809
|
-
|
|
2810
|
-
//#region src/read/api/line.ts
|
|
2820
|
+
// src/read/api/line.ts
|
|
2811
2821
|
/**
|
|
2812
2822
|
* Shared line reader for DrawingML strokes: a shape's `p:spPr/a:ln`, a table cell's edges
|
|
2813
2823
|
* (`a:tcPr/a:lnL` and its siblings, each a `CT_LineProperties`), and a chart series'
|
|
@@ -2832,10 +2842,9 @@ function readLineBasics(ln, ctx) {
|
|
|
2832
2842
|
noFill: !!firstChild(ln, "a:noFill")
|
|
2833
2843
|
};
|
|
2834
2844
|
}
|
|
2835
|
-
|
|
2836
|
-
//#region src/read/api/hyperlink.ts
|
|
2845
|
+
// src/read/api/hyperlink.ts
|
|
2837
2846
|
/**
|
|
2838
|
-
* `a:hlinkClick
|
|
2847
|
+
* `a:hlinkClick`: the one reading of a click hyperlink, wherever it hangs.
|
|
2839
2848
|
*
|
|
2840
2849
|
* DrawingML puts the same element in two places: on a run's `a:rPr`, which links a span of text,
|
|
2841
2850
|
* and on a shape's `p:cNvPr`, which links the whole shape. They are the same element with the same
|
|
@@ -2873,10 +2882,9 @@ function readHyperlink(parent, rels) {
|
|
|
2873
2882
|
relId
|
|
2874
2883
|
};
|
|
2875
2884
|
}
|
|
2876
|
-
|
|
2877
|
-
//#region src/read/api/shapes/effects.ts
|
|
2885
|
+
// src/read/api/shapes/effects.ts
|
|
2878
2886
|
/**
|
|
2879
|
-
* Decoding a shape's `a:effectLst
|
|
2887
|
+
* Decoding a shape's `a:effectLst`: shadow, glow, reflection, soft edge.
|
|
2880
2888
|
*
|
|
2881
2889
|
* Free functions over `(effectLst, ctx)` rather than methods, because that is all they ever
|
|
2882
2890
|
* needed: the block was five getters and two private helpers on `Shape` whose only reference
|
|
@@ -2885,7 +2893,7 @@ function readHyperlink(parent, rels) {
|
|
|
2885
2893
|
* per-concern modules `shapes/` already has (`oxml.ts`, `geometry.ts`).
|
|
2886
2894
|
*
|
|
2887
2895
|
* Every decoder returns `null` for an absent element rather than an empty object, and leaves an
|
|
2888
|
-
* unstated attribute off its result rather than defaulting it
|
|
2896
|
+
* unstated attribute off its result rather than defaulting it. "Absent" and "stated as the
|
|
2889
2897
|
* default" are different facts about the source part, and a replica that has to carry the
|
|
2890
2898
|
* effect needs to know which it is.
|
|
2891
2899
|
*/
|
|
@@ -2932,7 +2940,11 @@ function readShadow(shdw, ctx) {
|
|
|
2932
2940
|
*/
|
|
2933
2941
|
function readOuterShadow(effectLst, ctx) {
|
|
2934
2942
|
const shdw = effect(effectLst, "a:outerShdw");
|
|
2935
|
-
|
|
2943
|
+
if (!shdw) return null;
|
|
2944
|
+
const out = readShadow(shdw, ctx);
|
|
2945
|
+
const rotate = boolAttr(shdw, "rotWithShape");
|
|
2946
|
+
if (rotate !== null) out.rotateWithShape = rotate;
|
|
2947
|
+
return out;
|
|
2936
2948
|
}
|
|
2937
2949
|
/**
|
|
2938
2950
|
* The inner shadow (`a:effectLst/a:innerShdw`), or `null`.
|
|
@@ -2992,8 +3004,7 @@ function readSoftEdge(effectLst) {
|
|
|
2992
3004
|
if (!soft) return null;
|
|
2993
3005
|
return { radiusPt: scaledAttr(soft, "rad", 12700) ?? 0 };
|
|
2994
3006
|
}
|
|
2995
|
-
|
|
2996
|
-
//#region src/read/api/text/run.ts
|
|
3007
|
+
// src/read/api/text/run.ts
|
|
2997
3008
|
/** One text run (`a:r`): a span of text with uniform character formatting. */
|
|
2998
3009
|
var Run = class {
|
|
2999
3010
|
element;
|
|
@@ -3004,7 +3015,7 @@ var Run = class {
|
|
|
3004
3015
|
this.context = context;
|
|
3005
3016
|
this.inherited = inherited;
|
|
3006
3017
|
}
|
|
3007
|
-
/** The run's text (`a:t`), verbatim
|
|
3018
|
+
/** The run's text (`a:t`), verbatim; whitespace is not normalized. */
|
|
3008
3019
|
get text() {
|
|
3009
3020
|
return firstChild(this.element, "a:t")?.textContent ?? "";
|
|
3010
3021
|
}
|
|
@@ -3059,7 +3070,7 @@ var Run = class {
|
|
|
3059
3070
|
/**
|
|
3060
3071
|
* Strikethrough token (`a:rPr/@strike`: `noStrike` | `sngStrike` |
|
|
3061
3072
|
* `dblStrike`), or `null` when unset (inherited from the style). Surfaced as
|
|
3062
|
-
* the raw token
|
|
3073
|
+
* the raw token; `sngStrike` is the writer's single-strike value.
|
|
3063
3074
|
*/
|
|
3064
3075
|
get strike() {
|
|
3065
3076
|
return this.#rPrAttrRaw("strike");
|
|
@@ -3077,8 +3088,8 @@ var Run = class {
|
|
|
3077
3088
|
* `30`), negative for subscript (`-40`), or `null` when unset. Reported as the
|
|
3078
3089
|
* percentage (`@baseline` ÷ 1000).
|
|
3079
3090
|
*
|
|
3080
|
-
* `@baseline` is `a:ST_Percentage`, a union that also admits `"62.5%"`
|
|
3081
|
-
* Strict profile has
|
|
3091
|
+
* `@baseline` is `a:ST_Percentage`, a union that also admits `"62.5%"` (the only form the
|
|
3092
|
+
* Strict profile has), so it is read through `parsePercentPoints` rather than as a bare number.
|
|
3082
3093
|
*/
|
|
3083
3094
|
get baselinePct() {
|
|
3084
3095
|
const rPr = this.#rPr();
|
|
@@ -3093,6 +3104,14 @@ var Run = class {
|
|
|
3093
3104
|
return ptFromHundredths(this.#rPrAttr("spc"));
|
|
3094
3105
|
}
|
|
3095
3106
|
/**
|
|
3107
|
+
* Kerning threshold in points (`a:rPr/@kern`, authored in hundredths of a point), or `null`
|
|
3108
|
+
* when unset: text at this font size or larger is pair-kerned, and `0` means kerning is off.
|
|
3109
|
+
* PowerPoint's own default is `12`. The read counterpart of the write-side `kerning` option.
|
|
3110
|
+
*/
|
|
3111
|
+
get kerningPt() {
|
|
3112
|
+
return ptFromHundredths(this.#rPrAttr("kern"));
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3096
3115
|
* The run's highlight colour (`a:rPr/a:highlight`), resolved to a literal hex
|
|
3097
3116
|
* through the owning slide's theme, or `null` when the run has no highlight or
|
|
3098
3117
|
* its colour cannot be made literal. The writer authors highlights from a hex
|
|
@@ -3116,6 +3135,83 @@ var Run = class {
|
|
|
3116
3135
|
get hyperlink() {
|
|
3117
3136
|
return readHyperlink(this.#rPr(), this.context.rels);
|
|
3118
3137
|
}
|
|
3138
|
+
/**
|
|
3139
|
+
* The run's language tag (`a:rPr/@lang`, e.g. `fr-FR`), or `null` when unset. Decides spelling,
|
|
3140
|
+
* hyphenation and font fallback. The read counterpart of the write-side `lang` option.
|
|
3141
|
+
*/
|
|
3142
|
+
get language() {
|
|
3143
|
+
return this.#rPrAttrRaw("lang");
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* The run's own glow (`a:rPr/a:effectLst/a:glow`), resolved against the slide theme, or `null`.
|
|
3147
|
+
* The read counterpart of the write-side text `glow` option; a shape's glow is `Shape.glow`.
|
|
3148
|
+
*/
|
|
3149
|
+
get glow() {
|
|
3150
|
+
const rPr = this.#rPr();
|
|
3151
|
+
return readGlow(rPr && firstChild(rPr, "a:effectLst"), this.context.ctx);
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* The run's own outer shadow (`a:rPr/a:effectLst/a:outerShdw`), resolved against the slide
|
|
3155
|
+
* theme, or `null`. The read counterpart of a run's `shadow` option and of a table cell's,
|
|
3156
|
+
* which writes it on every run of the cell; a shape's shadow is `Shape.shadow`.
|
|
3157
|
+
*/
|
|
3158
|
+
get shadow() {
|
|
3159
|
+
const rPr = this.#rPr();
|
|
3160
|
+
return readOuterShadow(rPr && firstChild(rPr, "a:effectLst"), this.context.ctx);
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* The run's own inner shadow (`a:rPr/a:effectLst/a:innerShdw`), resolved against the slide
|
|
3164
|
+
* theme, or `null`. What a run's `shadow: { type: 'inner' }` writes.
|
|
3165
|
+
*/
|
|
3166
|
+
get innerShadow() {
|
|
3167
|
+
const rPr = this.#rPr();
|
|
3168
|
+
return readInnerShadow(rPr && firstChild(rPr, "a:effectLst"), this.context.ctx);
|
|
3169
|
+
}
|
|
3170
|
+
/** The run's text outline (`a:rPr/a:ln`), or `null` when it has none. The read counterpart of `outline`. */
|
|
3171
|
+
get outline() {
|
|
3172
|
+
const rPr = this.#rPr();
|
|
3173
|
+
const ln = rPr && firstChild(rPr, "a:ln");
|
|
3174
|
+
if (!ln) return null;
|
|
3175
|
+
const fill = firstChild(ln, "a:solidFill");
|
|
3176
|
+
return {
|
|
3177
|
+
widthPt: ptFromEmu(numberValue(attr(ln, "w"))),
|
|
3178
|
+
noFill: firstChild(ln, "a:noFill") !== null,
|
|
3179
|
+
colorRef: readColorRef(fill && firstChildElement(fill), this.context.ctx)
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* The underline's own colour (`a:rPr/a:uFill/a:solidFill`), as written and resolved, or `null`
|
|
3184
|
+
* when the underline follows the text colour. The read counterpart of `underline.color`.
|
|
3185
|
+
*/
|
|
3186
|
+
get underlineColor() {
|
|
3187
|
+
const rPr = this.#rPr();
|
|
3188
|
+
const uFill = rPr && firstChild(rPr, "a:uFill");
|
|
3189
|
+
const fill = uFill && firstChild(uFill, "a:solidFill");
|
|
3190
|
+
return fill ? readColorRef(firstChildElement(fill), this.context.ctx) : null;
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* The East Asian typeface (`a:rPr/a:ea/@typeface`), or `null` when unset. {@link fontName} is the
|
|
3194
|
+
* Latin slot. The read counterpart of the write-side `fontFaceEA` option.
|
|
3195
|
+
*/
|
|
3196
|
+
get eastAsianFontName() {
|
|
3197
|
+
const rPr = this.#rPr();
|
|
3198
|
+
const ea = rPr && firstChild(rPr, "a:ea");
|
|
3199
|
+
return ea ? attr(ea, "typeface") : null;
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* How many line breaks (`a:br`) sit directly before this run in its paragraph, with no run or
|
|
3203
|
+
* field between them. A soft break is not a run, so without this the runs on either side of one
|
|
3204
|
+
* read as plain neighbours. The read counterpart of the write-side `softBreakBefore` option.
|
|
3205
|
+
*/
|
|
3206
|
+
get breaksBefore() {
|
|
3207
|
+
let count = 0;
|
|
3208
|
+
for (let node = this.element.previousSibling; node; node = node.previousSibling) {
|
|
3209
|
+
if (node.nodeType !== 1) continue;
|
|
3210
|
+
if (node.localName !== "br") break;
|
|
3211
|
+
count++;
|
|
3212
|
+
}
|
|
3213
|
+
return count;
|
|
3214
|
+
}
|
|
3119
3215
|
/** Latin typeface name (`a:rPr/a:latin/@typeface`), or `null` when unset. */
|
|
3120
3216
|
get fontName() {
|
|
3121
3217
|
const rPr = this.#rPr();
|
|
@@ -3152,7 +3248,7 @@ var Run = class {
|
|
|
3152
3248
|
* The colour this run effectively renders, resolved against the owning slide's
|
|
3153
3249
|
* theme to a literal hex. It is the run's own solid fill
|
|
3154
3250
|
* ({@link color}/{@link schemeColor}) when set; otherwise the shape's
|
|
3155
|
-
* `p:style/a:fontRef` colour, then
|
|
3251
|
+
* `p:style/a:fontRef` colour, then (for a run inside a placeholder) the colour
|
|
3156
3252
|
* it inherits from the placeholder/list-style chain (layout → master placeholder
|
|
3157
3253
|
* `a:lstStyle` → master `p:txStyles`), then the presentation's
|
|
3158
3254
|
* `p:defaultTextStyle`. `null` when the run sets no colour and inherits none, or the
|
|
@@ -3160,12 +3256,24 @@ var Run = class {
|
|
|
3160
3256
|
* `null` too when the run's own fill is not a solid colour (`a:noFill`, a gradient):
|
|
3161
3257
|
* the run decides its own colour then, and the one it would otherwise inherit is not
|
|
3162
3258
|
* what it paints in, the rule `Shape.resolvedFill` and `TableCell.resolvedFill` follow.
|
|
3163
|
-
*
|
|
3164
|
-
*
|
|
3259
|
+
* A run with a click hyperlink (`a:hlinkClick`) and no fill of its own is painted in the
|
|
3260
|
+
* theme's `hlink` colour instead, resolved through the slide's colour map, unless the link
|
|
3261
|
+
* carries the 2018 `ahyp:hlinkClr val="tx"` extension, which asks for the text colour above.
|
|
3262
|
+
* The returned {@link ResolvedColor} carries `effectiveHex`, the base colour with
|
|
3263
|
+
* its child transforms (`lumMod`/`shade`/…) applied, for the final rendered colour.
|
|
3165
3264
|
*/
|
|
3166
3265
|
get resolvedColor() {
|
|
3167
3266
|
const rPr = this.#rPr();
|
|
3168
3267
|
if (hasFillChoice(rPr)) return resolveSolidFillColor(rPr, this.context.ctx);
|
|
3268
|
+
const hlinkClick = rPr && firstChild(rPr, "a:hlinkClick");
|
|
3269
|
+
if (hlinkClick && !followsTextColor(hlinkClick)) {
|
|
3270
|
+
const hex = resolveSchemeToken("hlink", this.context.ctx);
|
|
3271
|
+
return hex ? {
|
|
3272
|
+
hex,
|
|
3273
|
+
transforms: [],
|
|
3274
|
+
effectiveHex: hex
|
|
3275
|
+
} : null;
|
|
3276
|
+
}
|
|
3169
3277
|
const inherit = this.context.inherit;
|
|
3170
3278
|
return inherit?.fontRef?.color ?? inherit?.tableText?.color ?? this.inherited?.color() ?? null;
|
|
3171
3279
|
}
|
|
@@ -3174,7 +3282,7 @@ var Run = class {
|
|
|
3174
3282
|
* ({@link fontSizePt}) when set; otherwise, for a run inside a placeholder, the
|
|
3175
3283
|
* size it inherits from the placeholder/list-style chain (paragraph `a:defRPr` →
|
|
3176
3284
|
* slide `a:lstStyle` → layout → master placeholder `a:lstStyle` → master
|
|
3177
|
-
* `p:txStyles`). `null` when the run sets no size and inherits none
|
|
3285
|
+
* `p:txStyles`). `null` when the run sets no size and inherits none. This is the
|
|
3178
3286
|
* resolved counterpart of {@link fontSizePt}, which reports only the run's own value.
|
|
3179
3287
|
*/
|
|
3180
3288
|
get resolvedSizePt() {
|
|
@@ -3183,13 +3291,14 @@ var Run = class {
|
|
|
3183
3291
|
/**
|
|
3184
3292
|
* The typeface this run effectively renders, resolved to a literal face name. It
|
|
3185
3293
|
* is the run's own `a:latin` ({@link fontName}) when set; otherwise the face named
|
|
3186
|
-
* by the shape's `p:style/a:fontRef` (`idx` → theme major/minor font), then
|
|
3187
|
-
* run inside a placeholder
|
|
3294
|
+
* by the shape's `p:style/a:fontRef` (`idx` → theme major/minor font), then (for a
|
|
3295
|
+
* run inside a placeholder) the face it inherits from the placeholder/list-style
|
|
3188
3296
|
* chain, then the presentation's `p:defaultTextStyle`. A `+mj-*`/`+mn-*`
|
|
3189
|
-
* major/minor theme-font token
|
|
3297
|
+
* major/minor theme-font token, on the run itself or reached through the chain,
|
|
3190
3298
|
* is resolved through the theme `fontScheme` to its concrete face. `null` when the
|
|
3191
|
-
* run names no face and inherits none, or a token cannot be resolved
|
|
3192
|
-
* counterpart of {@link fontName}, which reports the raw `@typeface` (possibly a
|
|
3299
|
+
* run names no face and inherits none, or a token cannot be resolved. This is the
|
|
3300
|
+
* resolved counterpart of {@link fontName}, which reports the raw `@typeface` (possibly a
|
|
3301
|
+
* token).
|
|
3193
3302
|
*/
|
|
3194
3303
|
get resolvedFontFace() {
|
|
3195
3304
|
const own = this.fontName;
|
|
@@ -3202,7 +3311,7 @@ var Run = class {
|
|
|
3202
3311
|
* ({@link bold}) when set; otherwise, for a run inside a placeholder, the bold
|
|
3203
3312
|
* state it inherits from the placeholder/list-style chain (paragraph `a:defRPr` →
|
|
3204
3313
|
* slide `a:lstStyle` → layout → master placeholder `a:lstStyle` → master
|
|
3205
|
-
* `p:txStyles`). `null` when the run sets no `@b` and inherits none
|
|
3314
|
+
* `p:txStyles`). `null` when the run sets no `@b` and inherits none. This is the
|
|
3206
3315
|
* resolved counterpart of {@link bold}, which reports only the run's own value.
|
|
3207
3316
|
*/
|
|
3208
3317
|
get resolvedBold() {
|
|
@@ -3213,7 +3322,7 @@ var Run = class {
|
|
|
3213
3322
|
* ({@link italic}) when set; otherwise, for a run inside a placeholder, the italic
|
|
3214
3323
|
* state it inherits from the placeholder/list-style chain (paragraph `a:defRPr` →
|
|
3215
3324
|
* slide `a:lstStyle` → layout → master placeholder `a:lstStyle` → master
|
|
3216
|
-
* `p:txStyles`). `null` when the run sets no `@i` and inherits none
|
|
3325
|
+
* `p:txStyles`). `null` when the run sets no `@i` and inherits none. This is the
|
|
3217
3326
|
* resolved counterpart of {@link italic}, and the twin of {@link resolvedBold}:
|
|
3218
3327
|
* `@b` and `@i` are siblings a master text style states together, so a deck that
|
|
3219
3328
|
* can be authored with an inherited italic can be read back with one.
|
|
@@ -3261,8 +3370,31 @@ var Run = class {
|
|
|
3261
3370
|
if (edit === null ? applySolidFill(this.#rPr(), null) : applySolidFill(this.#rPr(), edit, () => this.#getOrAddRPr(), RPR_FILL_AFTER)) this.context.part.markDirty();
|
|
3262
3371
|
}
|
|
3263
3372
|
};
|
|
3264
|
-
|
|
3265
|
-
|
|
3373
|
+
/** Whether a hyperlink's `ahyp:hlinkClr val="tx"` extension asks for the text colour rather than the theme's `hlink`. */
|
|
3374
|
+
function followsTextColor(hlinkClick) {
|
|
3375
|
+
const extLst = firstChild(hlinkClick, "a:extLst");
|
|
3376
|
+
if (!extLst) return false;
|
|
3377
|
+
for (const ext of getElements(extLst, "a:ext")) for (const child of childElements(ext)) if (child.localName === "hlinkClr" && child.namespaceURI === OOXML_NS.ahyp) return attr(child, "val") === "tx";
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* One text field (`a:fld`): text PowerPoint computes as it renders, such as a slide number
|
|
3382
|
+
* (`slidenum`) or a date (`datetime1`). A field carries the same `a:rPr` a run does, so it reads
|
|
3383
|
+
* and resolves its formatting like one. {@link text} is the rendering PowerPoint last cached,
|
|
3384
|
+
* which it recomputes on open.
|
|
3385
|
+
*/
|
|
3386
|
+
var Field = class extends Run {
|
|
3387
|
+
runIndex;
|
|
3388
|
+
constructor(element, context, inherited, runIndex) {
|
|
3389
|
+
super(element, context, inherited);
|
|
3390
|
+
this.runIndex = runIndex;
|
|
3391
|
+
}
|
|
3392
|
+
/** The field type (`@type`), such as `slidenum` or `datetime1`, or `null` when unset. */
|
|
3393
|
+
get type() {
|
|
3394
|
+
return attr(this.element_, "type");
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
// src/read/api/text/edit.ts
|
|
3266
3398
|
/**
|
|
3267
3399
|
* The two free text-body mutators, shared by the three proxies that own an `a:txBody`.
|
|
3268
3400
|
*
|
|
@@ -3274,7 +3406,7 @@ var Run = class {
|
|
|
3274
3406
|
* Replace a text body's content (`a:txBody` or `a:txBody`-shaped element) with a
|
|
3275
3407
|
* single paragraph and run, preserving the `a:rPr` of the body's first existing
|
|
3276
3408
|
* run when there is one. Shared by {@link TextFrame.text} and `TableCell.text`.
|
|
3277
|
-
* Does **not** mark any part dirty
|
|
3409
|
+
* Does **not** mark any part dirty: the caller owns the `Part` and must call
|
|
3278
3410
|
* `markDirty()` after.
|
|
3279
3411
|
*/
|
|
3280
3412
|
function setTextBodyText(txBody, value) {
|
|
@@ -3298,7 +3430,7 @@ function setTextBodyText(txBody, value) {
|
|
|
3298
3430
|
* Sibling paragraphs are untouched, which is the whole difference from
|
|
3299
3431
|
* {@link setTextBodyText} and the reason this exists: a SmartArt drawing cache packs several
|
|
3300
3432
|
* nodes' text into one `dsp:txBody`, so collapsing the body there would delete the other
|
|
3301
|
-
* nodes' strings. Does **not** mark any part dirty
|
|
3433
|
+
* nodes' strings. Does **not** mark any part dirty; the caller owns the `Part`.
|
|
3302
3434
|
*/
|
|
3303
3435
|
function setParagraphText(p, value) {
|
|
3304
3436
|
const doc = p.ownerDocument;
|
|
@@ -3316,12 +3448,11 @@ function setParagraphText(p, value) {
|
|
|
3316
3448
|
const endParaRPr = firstChild(p, "a:endParaRPr");
|
|
3317
3449
|
p.insertBefore(run, endParaRPr);
|
|
3318
3450
|
}
|
|
3319
|
-
|
|
3320
|
-
//#region src/read/api/text/paragraph.ts
|
|
3451
|
+
// src/read/api/text/paragraph.ts
|
|
3321
3452
|
/**
|
|
3322
3453
|
* The `Paragraph` read/write proxy (`a:p`).
|
|
3323
3454
|
*
|
|
3324
|
-
* A paragraph owns its `a:pPr`
|
|
3455
|
+
* A paragraph owns its `a:pPr` (alignment, indents, spacing, the bullet) and hands each
|
|
3325
3456
|
* `a:r` to a {@link Run}. It is handed its frame's {@link TextContext}, and builds the one
|
|
3326
3457
|
* {@link InheritedRunProps} its runs share.
|
|
3327
3458
|
*/
|
|
@@ -3335,12 +3466,51 @@ var Paragraph = class {
|
|
|
3335
3466
|
this.context = context;
|
|
3336
3467
|
this.slideLstStyle = slideLstStyle;
|
|
3337
3468
|
}
|
|
3338
|
-
/** The runs (`a:r`) in document order. Fields (`a:fld`)
|
|
3469
|
+
/** The runs (`a:r`) in document order. Fields (`a:fld`) are not runs (see {@link fields}), nor are breaks. */
|
|
3339
3470
|
get runs() {
|
|
3340
3471
|
const inherited = this.#inheritedRunProps();
|
|
3341
3472
|
return getElements(this.element, "a:r").map((element) => new Run(element, this.context, inherited));
|
|
3342
3473
|
}
|
|
3343
3474
|
/**
|
|
3475
|
+
* The OMML equations (`a14:m`) in document order. An equation is neither a run nor part of
|
|
3476
|
+
* {@link text}; each says how many runs precede it, so an inline one keeps its place.
|
|
3477
|
+
*/
|
|
3478
|
+
get equations() {
|
|
3479
|
+
const equations = [];
|
|
3480
|
+
let runIndex = 0;
|
|
3481
|
+
for (let node = this.element.firstChild; node; node = node.nextSibling) {
|
|
3482
|
+
if (node.nodeType !== 1) continue;
|
|
3483
|
+
const element = node;
|
|
3484
|
+
if (element.namespaceURI === OOXML_NS.a && element.localName === "r") runIndex++;
|
|
3485
|
+
if (element.namespaceURI !== OOXML_NS.a14 || element.localName !== "m") continue;
|
|
3486
|
+
const omml = firstChildElement(element);
|
|
3487
|
+
if (omml?.namespaceURI !== OOXML_NS.m) continue;
|
|
3488
|
+
equations.push({
|
|
3489
|
+
omml: serializeXml(omml),
|
|
3490
|
+
inline: omml.localName === "oMath",
|
|
3491
|
+
runIndex
|
|
3492
|
+
});
|
|
3493
|
+
}
|
|
3494
|
+
return equations;
|
|
3495
|
+
}
|
|
3496
|
+
/**
|
|
3497
|
+
* The fields (`a:fld`) in document order, each read like a run; see {@link Field}. Each says how
|
|
3498
|
+
* many runs precede it, so a field between runs keeps its place.
|
|
3499
|
+
*/
|
|
3500
|
+
get fields() {
|
|
3501
|
+
const inherited = this.#inheritedRunProps();
|
|
3502
|
+
const fields = [];
|
|
3503
|
+
let runIndex = 0;
|
|
3504
|
+
for (let node = this.element.firstChild; node; node = node.nextSibling) {
|
|
3505
|
+
if (node.nodeType !== 1) continue;
|
|
3506
|
+
const element = node;
|
|
3507
|
+
if (element.namespaceURI !== OOXML_NS.a) continue;
|
|
3508
|
+
if (element.localName === "r") runIndex++;
|
|
3509
|
+
else if (element.localName === "fld") fields.push(new Field(element, this.context, inherited, runIndex));
|
|
3510
|
+
}
|
|
3511
|
+
return fields;
|
|
3512
|
+
}
|
|
3513
|
+
/**
|
|
3344
3514
|
* What every run in this paragraph inherits when it sets no value of its own, or `null` for text
|
|
3345
3515
|
* that inherits through nothing. Runs in one paragraph share a level and `a:pPr`, so each
|
|
3346
3516
|
* property is resolved at most once per paragraph, and only when a run lacking its own value asks.
|
|
@@ -3364,6 +3534,15 @@ var Paragraph = class {
|
|
|
3364
3534
|
italic: once(resolveInheritedRunItalic)
|
|
3365
3535
|
};
|
|
3366
3536
|
}
|
|
3537
|
+
/**
|
|
3538
|
+
* The language of the paragraph's end mark (`a:endParaRPr/@lang`, e.g. `fr-FR`), or `null` when
|
|
3539
|
+
* unset. It is what text typed at the end of the paragraph, or into an empty one, is spelled in.
|
|
3540
|
+
* The write side states it from the text's `lang` option.
|
|
3541
|
+
*/
|
|
3542
|
+
get endLanguage() {
|
|
3543
|
+
const end = firstChild(this.element, "a:endParaRPr");
|
|
3544
|
+
return end ? attr(end, "lang") : null;
|
|
3545
|
+
}
|
|
3367
3546
|
/** Indent level (`a:pPr/@lvl`), 0 when unset. */
|
|
3368
3547
|
get level() {
|
|
3369
3548
|
const pPr = firstChild(this.element, "a:pPr");
|
|
@@ -3374,8 +3553,7 @@ var Paragraph = class {
|
|
|
3374
3553
|
* `dist` | `thaiDist`), or `null` when unset (inherited from the list style).
|
|
3375
3554
|
*/
|
|
3376
3555
|
get align() {
|
|
3377
|
-
|
|
3378
|
-
return pPr ? attr(pPr, "algn") : null;
|
|
3556
|
+
return alignOf(this.#pPr());
|
|
3379
3557
|
}
|
|
3380
3558
|
/**
|
|
3381
3559
|
* Space before the paragraph in points (`a:pPr/a:spcBef/a:spcPts/@val` is
|
|
@@ -3383,11 +3561,11 @@ var Paragraph = class {
|
|
|
3383
3561
|
* (`a:spcPct`, which has no fixed point value).
|
|
3384
3562
|
*/
|
|
3385
3563
|
get spaceBeforePt() {
|
|
3386
|
-
return this.#
|
|
3564
|
+
return spacingPt(this.#pPr(), "a:spcBef");
|
|
3387
3565
|
}
|
|
3388
3566
|
/** Space after the paragraph in points; see {@link spaceBeforePt} for the percentage caveat. */
|
|
3389
3567
|
get spaceAfterPt() {
|
|
3390
|
-
return this.#
|
|
3568
|
+
return spacingPt(this.#pPr(), "a:spcAft");
|
|
3391
3569
|
}
|
|
3392
3570
|
/**
|
|
3393
3571
|
* The paragraph's line spacing (`a:pPr/a:lnSpc`), as an exact point height
|
|
@@ -3395,37 +3573,112 @@ var Paragraph = class {
|
|
|
3395
3573
|
* `null` when unset (inherited from the list style). See {@link LineSpacing}.
|
|
3396
3574
|
*/
|
|
3397
3575
|
get lineSpacing() {
|
|
3398
|
-
|
|
3399
|
-
const lnSpc = pPr && firstChild(pPr, "a:lnSpc");
|
|
3400
|
-
if (!lnSpc) return null;
|
|
3401
|
-
const pts = firstChild(lnSpc, "a:spcPts");
|
|
3402
|
-
if (pts) {
|
|
3403
|
-
const val = numberValue(attr(pts, "val"));
|
|
3404
|
-
return val === null ? null : {
|
|
3405
|
-
type: "points",
|
|
3406
|
-
valuePt: val / 100
|
|
3407
|
-
};
|
|
3408
|
-
}
|
|
3409
|
-
const pct = firstChild(lnSpc, "a:spcPct");
|
|
3410
|
-
if (pct) {
|
|
3411
|
-
const val = pctPointsAttr(pct, "val");
|
|
3412
|
-
return val === null ? null : {
|
|
3413
|
-
type: "percent",
|
|
3414
|
-
percent: val
|
|
3415
|
-
};
|
|
3416
|
-
}
|
|
3417
|
-
return null;
|
|
3576
|
+
return lineSpacingOf(this.#pPr());
|
|
3418
3577
|
}
|
|
3419
3578
|
/** Left margin of the paragraph in points (`a:pPr/@marL` is EMU; 12700 EMU = 1pt), or `null` when unset. */
|
|
3420
3579
|
get marginLeftPt() {
|
|
3421
|
-
return this.#
|
|
3580
|
+
return emuAttrPt(this.#pPr(), "marL");
|
|
3422
3581
|
}
|
|
3423
3582
|
/** First-line indent in points (`a:pPr/@indent` is EMU; negative for a hanging indent), or `null` when unset. */
|
|
3424
3583
|
get indentPt() {
|
|
3425
|
-
return this.#
|
|
3584
|
+
return emuAttrPt(this.#pPr(), "indent");
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* The alignment the paragraph renders with: its own, else the first its level inherits through
|
|
3588
|
+
* the text body's `a:lstStyle`, the placeholder's layout and master list styles, the master's
|
|
3589
|
+
* `p:txStyles` and the presentation's `p:defaultTextStyle`, the chain {@link Run.resolvedSizePt}
|
|
3590
|
+
* walks. `null` when nothing in the chain states one (PowerPoint then aligns left).
|
|
3591
|
+
*/
|
|
3592
|
+
get resolvedAlign() {
|
|
3593
|
+
return this.#resolved(alignOf);
|
|
3594
|
+
}
|
|
3595
|
+
/** The left margin in points the paragraph renders with, resolved as {@link resolvedAlign} is. */
|
|
3596
|
+
get resolvedMarginLeftPt() {
|
|
3597
|
+
return this.#resolved((pPr) => emuAttrPt(pPr, "marL"));
|
|
3598
|
+
}
|
|
3599
|
+
/** The first-line indent in points the paragraph renders with, resolved as {@link resolvedAlign} is. */
|
|
3600
|
+
get resolvedIndentPt() {
|
|
3601
|
+
return this.#resolved((pPr) => emuAttrPt(pPr, "indent"));
|
|
3602
|
+
}
|
|
3603
|
+
/** The line spacing the paragraph renders with, resolved as {@link resolvedAlign} is. */
|
|
3604
|
+
get resolvedLineSpacing() {
|
|
3605
|
+
return this.#resolved(lineSpacingOf);
|
|
3606
|
+
}
|
|
3607
|
+
/** The space before the paragraph in points, resolved as {@link resolvedAlign} is. */
|
|
3608
|
+
get resolvedSpaceBeforePt() {
|
|
3609
|
+
return this.#resolved((pPr) => spacingPt(pPr, "a:spcBef"));
|
|
3610
|
+
}
|
|
3611
|
+
/** The space after the paragraph in points, resolved as {@link resolvedAlign} is. */
|
|
3612
|
+
get resolvedSpaceAfterPt() {
|
|
3613
|
+
return this.#resolved((pPr) => spacingPt(pPr, "a:spcAft"));
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* The bullet the paragraph renders with: its own {@link bulletDetail}, else the first bullet its
|
|
3617
|
+
* level inherits, resolved as {@link resolvedAlign} is. A picture bullet from an inherited tier
|
|
3618
|
+
* reads with `imagePartName: null`, its image being a relationship of the layout or master.
|
|
3619
|
+
* `null` when nothing in the chain names one.
|
|
3620
|
+
*/
|
|
3621
|
+
get resolvedBulletDetail() {
|
|
3622
|
+
const own = this.bulletDetail;
|
|
3623
|
+
if (own) return own;
|
|
3624
|
+
for (const tier of this.#inheritedLevels()) {
|
|
3625
|
+
const detail = this.#bulletDetailOf(tier, false);
|
|
3626
|
+
if (detail) return detail;
|
|
3627
|
+
}
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
/** The paragraph's own `a:pPr`, or `null`. */
|
|
3631
|
+
#pPr() {
|
|
3632
|
+
return firstChild(this.element, "a:pPr");
|
|
3426
3633
|
}
|
|
3427
3634
|
/**
|
|
3428
|
-
*
|
|
3635
|
+
* The level elements the paragraph inherits paragraph properties from, most specific first: the
|
|
3636
|
+
* text body's `a:lstStyle`, then for a placeholder its layout and master list styles and the
|
|
3637
|
+
* master's `p:txStyles`, then `p:defaultTextStyle`. Empty for text that inherits through nothing.
|
|
3638
|
+
*/
|
|
3639
|
+
#inheritedLevels() {
|
|
3640
|
+
const inherit = this.context.inherit;
|
|
3641
|
+
if (!inherit) return [];
|
|
3642
|
+
const level = this.level;
|
|
3643
|
+
const { ctx } = this.context;
|
|
3644
|
+
const tiers = [lstStyleLevel(this.slideLstStyle, level)];
|
|
3645
|
+
if (inherit.ph) tiers.push(...placeholderInheritedLevels(inherit.ph.type, inherit.ph.idx, level, ctx));
|
|
3646
|
+
tiers.push(lstStyleLevel(ctx.defaultTextStyle ?? null, level));
|
|
3647
|
+
return tiers.filter((tier) => tier !== null);
|
|
3648
|
+
}
|
|
3649
|
+
/** The first value `read` finds in the paragraph's own `a:pPr`, then down {@link inheritedLevels}. */
|
|
3650
|
+
#resolved(read) {
|
|
3651
|
+
const own = read(this.#pPr());
|
|
3652
|
+
if (own !== null) return own;
|
|
3653
|
+
for (const tier of this.#inheritedLevels()) {
|
|
3654
|
+
const value = read(tier);
|
|
3655
|
+
if (value !== null) return value;
|
|
3656
|
+
}
|
|
3657
|
+
return null;
|
|
3658
|
+
}
|
|
3659
|
+
/**
|
|
3660
|
+
* Right-to-left paragraph direction (`a:pPr/@rtl`), or `null` when unset (left to right). The
|
|
3661
|
+
* read counterpart of the write-side `rtlMode` option.
|
|
3662
|
+
*/
|
|
3663
|
+
get rtl() {
|
|
3664
|
+
const pPr = firstChild(this.element, "a:pPr");
|
|
3665
|
+
return pPr ? boolValue(attr(pPr, "rtl")) : null;
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* The paragraph's own tab stops (`a:pPr/a:tabLst/a:tab`) in document order, or `null` when it
|
|
3669
|
+
* states no tab list and inherits one. The read counterpart of the write-side `tabStops` option.
|
|
3670
|
+
*/
|
|
3671
|
+
get tabStops() {
|
|
3672
|
+
const pPr = firstChild(this.element, "a:pPr");
|
|
3673
|
+
const tabLst = pPr && firstChild(pPr, "a:tabLst");
|
|
3674
|
+
if (!tabLst) return null;
|
|
3675
|
+
return getElements(tabLst, "a:tab").map((tab) => ({
|
|
3676
|
+
positionPt: ptFromEmu(numberValue(attr(tab, "pos"))),
|
|
3677
|
+
alignment: attr(tab, "algn") ?? null
|
|
3678
|
+
}));
|
|
3679
|
+
}
|
|
3680
|
+
/**
|
|
3681
|
+
* This paragraph's bullet: its kind, and for a drawn bullet the glyph, the
|
|
3429
3682
|
* numbering start, and the bullet's own font/size/colour. `null` when the
|
|
3430
3683
|
* paragraph names no bullet at all and inherits one from the list style.
|
|
3431
3684
|
*
|
|
@@ -3433,11 +3686,18 @@ var Paragraph = class {
|
|
|
3433
3686
|
* previous `bullet` accessor reported `'none'` / `'char:•'` /
|
|
3434
3687
|
* `'autoNum:arabicPeriod'`, and that shape is ambiguous when the glyph is
|
|
3435
3688
|
* itself a colon; worse, it reads as a bare glyph, which is exactly how the
|
|
3436
|
-
* script converter first consumed it
|
|
3689
|
+
* script converter first consumed it: `'none'.codePointAt(0)` put a literal
|
|
3437
3690
|
* `n` bullet on every converted deck, silently. See {@link BulletDetail}.
|
|
3438
3691
|
*/
|
|
3439
3692
|
get bulletDetail() {
|
|
3440
|
-
|
|
3693
|
+
return this.#bulletDetailOf(this.#pPr(), true);
|
|
3694
|
+
}
|
|
3695
|
+
/**
|
|
3696
|
+
* The bullet a paragraph-properties element names, or `null`.
|
|
3697
|
+
* @param resolveImage - resolve a picture bullet's image through this slide's relationships,
|
|
3698
|
+
* which only holds for the paragraph's own `a:pPr`
|
|
3699
|
+
*/
|
|
3700
|
+
#bulletDetailOf(pPr, resolveImage) {
|
|
3441
3701
|
if (!pPr) return null;
|
|
3442
3702
|
if (firstChild(pPr, "a:buNone")) return { kind: "none" };
|
|
3443
3703
|
const style = this.#bulletStyle(pPr);
|
|
@@ -3457,7 +3717,7 @@ var Paragraph = class {
|
|
|
3457
3717
|
const buBlip = firstChild(pPr, "a:buBlip");
|
|
3458
3718
|
if (buBlip) return {
|
|
3459
3719
|
kind: "picture",
|
|
3460
|
-
imagePartName: this.#bulletImagePartName(buBlip),
|
|
3720
|
+
imagePartName: resolveImage ? this.#bulletImagePartName(buBlip) : null,
|
|
3461
3721
|
...style
|
|
3462
3722
|
};
|
|
3463
3723
|
return null;
|
|
@@ -3489,18 +3749,6 @@ var Paragraph = class {
|
|
|
3489
3749
|
const blip = firstChild(buBlip, "a:blip");
|
|
3490
3750
|
return resolvePartName(blip && attr(blip, "r:embed"), this.context.rels);
|
|
3491
3751
|
}
|
|
3492
|
-
/** Points from a spacing child's `a:spcPts/@val` (hundredths of a point), or `null`. */
|
|
3493
|
-
#spacingPt(qname) {
|
|
3494
|
-
const pPr = firstChild(this.element, "a:pPr");
|
|
3495
|
-
const spc = pPr && firstChild(pPr, qname);
|
|
3496
|
-
const pts = spc && firstChild(spc, "a:spcPts");
|
|
3497
|
-
return ptFromHundredths(pts ? numberValue(attr(pts, "val")) : null);
|
|
3498
|
-
}
|
|
3499
|
-
/** Points from an EMU-valued `a:pPr` attribute (`marL` / `indent`), or `null`. */
|
|
3500
|
-
#emuAttrPt(name) {
|
|
3501
|
-
const pPr = firstChild(this.element, "a:pPr");
|
|
3502
|
-
return ptFromEmu(pPr ? numberValue(attr(pPr, name)) : null);
|
|
3503
|
-
}
|
|
3504
3752
|
/**
|
|
3505
3753
|
* The paragraph's text: run (`a:r`) and field (`a:fld`) text concatenated in
|
|
3506
3754
|
* document order, with each line break (`a:br`) rendered as `\n`.
|
|
@@ -3518,7 +3766,7 @@ var Paragraph = class {
|
|
|
3518
3766
|
/**
|
|
3519
3767
|
* Replace **this paragraph's** content with a single run, preserving the character
|
|
3520
3768
|
* formatting (`a:rPr`) of its first existing run and leaving its `a:pPr` (level,
|
|
3521
|
-
* alignment, bullet) alone. Sibling paragraphs are untouched
|
|
3769
|
+
* alignment, bullet) alone. Sibling paragraphs are untouched. That is the difference from
|
|
3522
3770
|
* {@link TextFrame.text}, which collapses the whole body to one paragraph.
|
|
3523
3771
|
*/
|
|
3524
3772
|
set text(value) {
|
|
@@ -3535,8 +3783,43 @@ var Paragraph = class {
|
|
|
3535
3783
|
}
|
|
3536
3784
|
};
|
|
3537
3785
|
/** A shape's text frame (`p:txBody`): an ordered list of paragraphs. */
|
|
3538
|
-
|
|
3539
|
-
|
|
3786
|
+
/** `@algn` of a paragraph-properties element (`a:pPr` or a list-style level), or `null`. */
|
|
3787
|
+
function alignOf(pPr) {
|
|
3788
|
+
return pPr ? attr(pPr, "algn") : null;
|
|
3789
|
+
}
|
|
3790
|
+
/** Points from a spacing child's `a:spcPts/@val` (hundredths of a point), or `null`. */
|
|
3791
|
+
function spacingPt(pPr, qname) {
|
|
3792
|
+
const spc = pPr && firstChild(pPr, qname);
|
|
3793
|
+
const pts = spc && firstChild(spc, "a:spcPts");
|
|
3794
|
+
return ptFromHundredths(pts ? numberValue(attr(pts, "val")) : null);
|
|
3795
|
+
}
|
|
3796
|
+
/** Points from an EMU-valued paragraph-properties attribute (`marL` / `indent`), or `null`. */
|
|
3797
|
+
function emuAttrPt(pPr, name) {
|
|
3798
|
+
return ptFromEmu(pPr ? numberValue(attr(pPr, name)) : null);
|
|
3799
|
+
}
|
|
3800
|
+
/** The `a:lnSpc` of a paragraph-properties element as a {@link LineSpacing}, or `null`. */
|
|
3801
|
+
function lineSpacingOf(pPr) {
|
|
3802
|
+
const lnSpc = pPr && firstChild(pPr, "a:lnSpc");
|
|
3803
|
+
if (!lnSpc) return null;
|
|
3804
|
+
const pts = firstChild(lnSpc, "a:spcPts");
|
|
3805
|
+
if (pts) {
|
|
3806
|
+
const val = numberValue(attr(pts, "val"));
|
|
3807
|
+
return val === null ? null : {
|
|
3808
|
+
type: "points",
|
|
3809
|
+
valuePt: val / 100
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
const pct = firstChild(lnSpc, "a:spcPct");
|
|
3813
|
+
if (pct) {
|
|
3814
|
+
const val = pctPointsAttr(pct, "val");
|
|
3815
|
+
return val === null ? null : {
|
|
3816
|
+
type: "percent",
|
|
3817
|
+
percent: val
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
return null;
|
|
3821
|
+
}
|
|
3822
|
+
// src/read/api/text/frame.ts
|
|
3540
3823
|
/**
|
|
3541
3824
|
* The `TextFrame` read/write proxy (`p:txBody` / `a:txBody`), and its `a:bodyPr` value types.
|
|
3542
3825
|
*
|
|
@@ -3556,9 +3839,30 @@ var TextFrame = class {
|
|
|
3556
3839
|
return getElements(this.txBody, "a:p").map((element) => new Paragraph(element, this.context, slideLstStyle));
|
|
3557
3840
|
}
|
|
3558
3841
|
/**
|
|
3842
|
+
* The character defaults this frame's own list style states for paragraphs at `level`
|
|
3843
|
+
* (0-based): `a:lstStyle/a:lvlNpPr/a:defRPr`, else `a:defPPr/a:defRPr`. `null` when the frame
|
|
3844
|
+
* has no list style or it states no defaults for that level.
|
|
3845
|
+
*
|
|
3846
|
+
* Only the frame's own tier. What a run inherits through it, and through a placeholder's
|
|
3847
|
+
* layout and master, is the run's `resolved*` reads.
|
|
3848
|
+
*/
|
|
3849
|
+
listStyleDefaults(level = 0) {
|
|
3850
|
+
const defRPr = lstStyleLevelDefRPr(firstChild(this.txBody, "a:lstStyle"), level);
|
|
3851
|
+
if (!defRPr) return null;
|
|
3852
|
+
const fill = firstChild(defRPr, "a:solidFill");
|
|
3853
|
+
const latin = firstChild(defRPr, "a:latin");
|
|
3854
|
+
return {
|
|
3855
|
+
fontSizePt: ptFromHundredths(numberValue(attr(defRPr, "sz"))),
|
|
3856
|
+
fontName: latin && attr(latin, "typeface") || null,
|
|
3857
|
+
bold: boolValue(attr(defRPr, "b")),
|
|
3858
|
+
italic: boolValue(attr(defRPr, "i")),
|
|
3859
|
+
colorRef: fill ? readColorRef(firstChildElement(fill), this.context.ctx) : null
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
/**
|
|
3559
3863
|
* The frame's body properties (`a:bodyPr`: insets, anchor, vertical text), or
|
|
3560
3864
|
* `null` when there is no `a:bodyPr`. Only explicitly-set insets are reported
|
|
3561
|
-
* (a missing side is the PowerPoint default
|
|
3865
|
+
* (a missing side is the PowerPoint default; see {@link BodyProperties}).
|
|
3562
3866
|
*/
|
|
3563
3867
|
get bodyProperties() {
|
|
3564
3868
|
const bodyPr = firstChild(this.txBody, "a:bodyPr");
|
|
@@ -3572,11 +3876,16 @@ var TextFrame = class {
|
|
|
3572
3876
|
inset("rIns", "right");
|
|
3573
3877
|
inset("tIns", "top");
|
|
3574
3878
|
inset("bIns", "bottom");
|
|
3879
|
+
const spcCol = numberValue(attr(bodyPr, "spcCol"));
|
|
3880
|
+
const warp = firstChild(bodyPr, "a:prstTxWarp");
|
|
3575
3881
|
return {
|
|
3576
3882
|
vert: attr(bodyPr, "vert") ?? null,
|
|
3577
3883
|
anchor: attr(bodyPr, "anchor") ?? null,
|
|
3578
3884
|
wrap: attr(bodyPr, "wrap") ?? null,
|
|
3579
|
-
insetsPt
|
|
3885
|
+
insetsPt,
|
|
3886
|
+
columns: numberValue(attr(bodyPr, "numCol")),
|
|
3887
|
+
columnSpacingPt: spcCol === null ? null : emuToPoints(spcCol),
|
|
3888
|
+
textWarp: warp ? attr(warp, "prst") ?? null : null
|
|
3580
3889
|
};
|
|
3581
3890
|
}
|
|
3582
3891
|
/**
|
|
@@ -3585,7 +3894,7 @@ var TextFrame = class {
|
|
|
3585
3894
|
* layout → master placeholder `a:bodyPr`. `null` when nothing in the chain sets
|
|
3586
3895
|
* one (PowerPoint then defaults to top). Unlike {@link bodyProperties}'s
|
|
3587
3896
|
* `anchor` (own attribute only), this surfaces the inherited anchor a
|
|
3588
|
-
* placeholder title relies on
|
|
3897
|
+
* placeholder title relies on: the difference between a top- and
|
|
3589
3898
|
* centre-anchored title that own-attribute reads alone cannot see.
|
|
3590
3899
|
*/
|
|
3591
3900
|
get resolvedAnchor() {
|
|
@@ -3597,8 +3906,8 @@ var TextFrame = class {
|
|
|
3597
3906
|
}
|
|
3598
3907
|
/**
|
|
3599
3908
|
* The frame's vertical-autofit mode (`a:bodyPr` autofit child), or `null` when
|
|
3600
|
-
* there is no `a:bodyPr`. A `bodyPr` with no autofit child
|
|
3601
|
-
* `a:noAutofit
|
|
3909
|
+
* there is no `a:bodyPr`. A `bodyPr` with no autofit child, or an explicit
|
|
3910
|
+
* `a:noAutofit`, reads `'none'`. See {@link AutofitMode}. This is the deep-model
|
|
3602
3911
|
* counterpart of `inspectPptx`'s per-shape `autofit`; unlike the shallow surface
|
|
3603
3912
|
* it hangs off the navigable text frame.
|
|
3604
3913
|
*/
|
|
@@ -3612,8 +3921,8 @@ var TextFrame = class {
|
|
|
3612
3921
|
/**
|
|
3613
3922
|
* The baked shrink scale of a `normAutofit` frame as a percent (62.5 = 62.5%),
|
|
3614
3923
|
* or `null` when the frame has no `a:normAutofit` or bakes no scale. A bare
|
|
3615
|
-
* `<a:normAutofit/>` (write-side `fit: 'shrink'`) carries no scale
|
|
3616
|
-
* computes it on edit and draws at 100% until then
|
|
3924
|
+
* `<a:normAutofit/>` (write-side `fit: 'shrink'`) carries no scale (PowerPoint
|
|
3925
|
+
* computes it on edit and draws at 100% until then), so it too reads `null`; an
|
|
3617
3926
|
* explicit scale comes from `fit: { type: 'shrink', fontScale }`.
|
|
3618
3927
|
*/
|
|
3619
3928
|
get autofitFontScale() {
|
|
@@ -3662,17 +3971,16 @@ var TextFrame = class {
|
|
|
3662
3971
|
this.context.part.markDirty();
|
|
3663
3972
|
}
|
|
3664
3973
|
};
|
|
3665
|
-
|
|
3666
|
-
//#region src/read/api/animation.ts
|
|
3974
|
+
// src/read/api/animation.ts
|
|
3667
3975
|
/**
|
|
3668
3976
|
* Spid-aware, otherwise-opaque handling of a slide's build-animation tree
|
|
3669
3977
|
* (`p:timing` / `p:bldLst`).
|
|
3670
3978
|
*
|
|
3671
|
-
* The `p:timing` subtree is `CT_TimeNodeList
|
|
3979
|
+
* The `p:timing` subtree is `CT_TimeNodeList`, deeply recursive and modeled
|
|
3672
3980
|
* here as opaque DOM (an unmodified slide round-trips it byte-identically for
|
|
3673
3981
|
* free). The only structured data we extract or rewrite is the set of `spid`
|
|
3674
|
-
* references
|
|
3675
|
-
* build list
|
|
3982
|
+
* references (`<p:spTgt spid>` inside the timing tree and `<p:bldP spid>` in the
|
|
3983
|
+
* build list), so animations stay coherent when shape ids change. Three purely
|
|
3676
3984
|
* structural operations: **enumerate**, **remap**, and **prune**. See
|
|
3677
3985
|
* `docs/contributing/design/animations.md` ("spid-aware operations").
|
|
3678
3986
|
*/
|
|
@@ -3686,7 +3994,7 @@ function spidElements(root) {
|
|
|
3686
3994
|
return out;
|
|
3687
3995
|
}
|
|
3688
3996
|
/**
|
|
3689
|
-
* Whether the slide carries build animations
|
|
3997
|
+
* Whether the slide carries build animations: a `<p:bldP>` entry or any timing
|
|
3690
3998
|
* node with a `presetID` (distinguishing animation timing from the media-loop
|
|
3691
3999
|
* timing that has neither).
|
|
3692
4000
|
*/
|
|
@@ -3695,6 +4003,61 @@ function hasAnimations(root) {
|
|
|
3695
4003
|
for (const cTn of root.getElementsByTagNameNS(OOXML_NS.p, "cTn")) if (attr(cTn, "presetID") !== null) return true;
|
|
3696
4004
|
return false;
|
|
3697
4005
|
}
|
|
4006
|
+
const TRIGGERS = {
|
|
4007
|
+
clickEffect: "onClick",
|
|
4008
|
+
withEffect: "withPrevious",
|
|
4009
|
+
afterEffect: "afterPrevious"
|
|
4010
|
+
};
|
|
4011
|
+
/**
|
|
4012
|
+
* The time node enclosing `cTn`: its `p:par` (or `p:seq`) sits in a `p:childTnLst`, whose parent is
|
|
4013
|
+
* the enclosing `p:cTn`. `null` at the top of the tree.
|
|
4014
|
+
*/
|
|
4015
|
+
function parentTimeNode(cTn) {
|
|
4016
|
+
const parent = cTn.parentNode?.parentNode?.parentNode;
|
|
4017
|
+
if (!parent || parent.nodeType !== 1) return null;
|
|
4018
|
+
const element = parent;
|
|
4019
|
+
return element.localName === "cTn" && element.namespaceURI === P_NS ? element : null;
|
|
4020
|
+
}
|
|
4021
|
+
/** A number of milliseconds from a time attribute, or `null` for `indefinite` and anything else. */
|
|
4022
|
+
function millis(value) {
|
|
4023
|
+
if (value === null || !/^\d+$/.test(value)) return null;
|
|
4024
|
+
return Number(value);
|
|
4025
|
+
}
|
|
4026
|
+
/** Every preset effect in the slide's `p:timing`, in document order. See {@link BuildEffect}. */
|
|
4027
|
+
function readBuildEffects(root) {
|
|
4028
|
+
const timing = firstChild(root, "p:timing");
|
|
4029
|
+
if (!timing) return [];
|
|
4030
|
+
const out = [];
|
|
4031
|
+
for (const cTn of timing.getElementsByTagNameNS(P_NS, "cTn")) {
|
|
4032
|
+
const presetClass = attr(cTn, "presetClass");
|
|
4033
|
+
if (presetClass === null) continue;
|
|
4034
|
+
let sequence = "other";
|
|
4035
|
+
for (let node = parentTimeNode(cTn); node; node = parentTimeNode(node)) {
|
|
4036
|
+
const type = attr(node, "nodeType");
|
|
4037
|
+
if (type === "mainSeq") sequence = "main";
|
|
4038
|
+
else if (type === "interactiveSeq") sequence = "interactive";
|
|
4039
|
+
else continue;
|
|
4040
|
+
break;
|
|
4041
|
+
}
|
|
4042
|
+
const stated = [...cTn.getElementsByTagNameNS(P_NS, "cTn")].map((node) => millis(attr(node, "dur"))).filter((dur) => dur !== null);
|
|
4043
|
+
const step = parentTimeNode(cTn);
|
|
4044
|
+
const stCondLst = step && firstChild(step, "p:stCondLst");
|
|
4045
|
+
const cond = stCondLst && firstChild(stCondLst, "p:cond");
|
|
4046
|
+
const spTgt = cTn.getElementsByTagNameNS(P_NS, "spTgt")[0] ?? null;
|
|
4047
|
+
out.push({
|
|
4048
|
+
sequence,
|
|
4049
|
+
presetClass,
|
|
4050
|
+
presetId: numberValue(attr(cTn, "presetID")),
|
|
4051
|
+
presetSubtype: numberValue(attr(cTn, "presetSubtype")),
|
|
4052
|
+
trigger: TRIGGERS[attr(cTn, "nodeType") ?? ""] ?? null,
|
|
4053
|
+
durationMs: stated.length > 0 ? Math.max(...stated) : null,
|
|
4054
|
+
delayMs: cond ? millis(attr(cond, "delay")) : null,
|
|
4055
|
+
shapeId: spTgt ? numberValue(attr(spTgt, "spid")) : null,
|
|
4056
|
+
textRange: spTgt !== null && firstChild(spTgt, "p:txEl") !== null
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
return out;
|
|
4060
|
+
}
|
|
3698
4061
|
/** The sorted, de-duplicated set of `spid`s referenced by the slide's animations. */
|
|
3699
4062
|
function enumerateSpids(root) {
|
|
3700
4063
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3801,7 +4164,7 @@ function pruneSpids(root, spids) {
|
|
|
3801
4164
|
* This is the whole-slide counterpart to {@link pruneSpids} (which drops the
|
|
3802
4165
|
* builds of specific shapes). It only removes staging; it never deletes shapes,
|
|
3803
4166
|
* so a slide that animated alternating states over the same region will show
|
|
3804
|
-
* them all at once after flattening
|
|
4167
|
+
* them all at once after flattening. That is "drop animations", distinct from
|
|
3805
4168
|
* "remove staged/duplicate shapes".
|
|
3806
4169
|
*/
|
|
3807
4170
|
function flattenAnimations(root) {
|
|
@@ -3906,7 +4269,7 @@ function getOrCreateBldLst(timing, doc) {
|
|
|
3906
4269
|
* id on the destination). For each mapped shape: its mainSeq click-group `<p:par>`
|
|
3907
4270
|
* (the whole click step) and `<p:bldP>` are cloned, their `spid`s remapped, their
|
|
3908
4271
|
* `<p:cTn>` ids renumbered to stay collision-free in the destination, and appended
|
|
3909
|
-
* after any existing build
|
|
4272
|
+
* after any existing build, matching how PowerPoint merges a pasted shape's
|
|
3910
4273
|
* animation. Returns `true` when anything was carried (so the caller can
|
|
3911
4274
|
* `markDirty()`), `false` when the source shapes have no animation.
|
|
3912
4275
|
*/
|
|
@@ -3967,22 +4330,21 @@ function targetsOnly(root, spidMap) {
|
|
|
3967
4330
|
const targets = [...root.getElementsByTagNameNS(P_NS, "spTgt")].map((spTgt) => numberValue(attr(spTgt, "spid")));
|
|
3968
4331
|
return targets.length > 0 && targets.every((spid) => spid !== null && spidMap.has(spid));
|
|
3969
4332
|
}
|
|
3970
|
-
|
|
3971
|
-
//#region src/read/api/shapes/base.ts
|
|
4333
|
+
// src/read/api/shapes/base.ts
|
|
3972
4334
|
/**
|
|
3973
4335
|
* The abstract base every shape kind extends.
|
|
3974
4336
|
*
|
|
3975
4337
|
* It owns everything a shape has regardless of kind: non-visual identity (id / name / hidden /
|
|
3976
4338
|
* description / decorative), geometry through the subclass-supplied `a:xfrm`, and the `p:spPr`
|
|
3977
|
-
* surface
|
|
4339
|
+
* surface (fill, line, gradient, effects) resolved against the host's theme. Kind-specific
|
|
3978
4340
|
* reads live on the subclasses in the sibling modules.
|
|
3979
4341
|
*
|
|
3980
|
-
* A proxy holds a back-reference to its owning {@link ShapeHost}
|
|
3981
|
-
* `SlideMaster`
|
|
4342
|
+
* A proxy holds a back-reference to its owning {@link ShapeHost} (a `Slide`, a `SlideLayout` or a
|
|
4343
|
+
* `SlideMaster`), so it can resolve relationships and theme colours, and so a mutation can mark
|
|
3982
4344
|
* that host's part dirty.
|
|
3983
4345
|
*/
|
|
3984
4346
|
const ADEC_NS = "http://schemas.microsoft.com/office/drawing/2017/decorative";
|
|
3985
|
-
/** Common base for every shape in a shape tree
|
|
4347
|
+
/** Common base for every shape in a shape tree: a slide's, a layout's, or a master's. */
|
|
3986
4348
|
var Shape = class {
|
|
3987
4349
|
element;
|
|
3988
4350
|
host;
|
|
@@ -4041,7 +4403,7 @@ var Shape = class {
|
|
|
4041
4403
|
/**
|
|
4042
4404
|
* Whether the shape is explicitly hidden (`p:cNvPr/@hidden="1"`); `false` when
|
|
4043
4405
|
* the attribute is unset. A hidden shape stays in the slide XML but is not
|
|
4044
|
-
* rendered
|
|
4406
|
+
* rendered. Decks use it as a fallback layer (e.g. a duotone-recolour source
|
|
4045
4407
|
* sitting behind the visible icon), so a faithful reader must distinguish it
|
|
4046
4408
|
* from the drawn shapes.
|
|
4047
4409
|
*/
|
|
@@ -4050,6 +4412,29 @@ var Shape = class {
|
|
|
4050
4412
|
return boolValue(cNvPr && attr(cNvPr, "hidden")) === true;
|
|
4051
4413
|
}
|
|
4052
4414
|
/**
|
|
4415
|
+
* The shape's lock flags: the attributes of the locking element under its non-visual drawing
|
|
4416
|
+
* properties (`a:spLocks`, `a:picLocks`, `a:cxnSpLocks`, `a:graphicFrameLocks` or
|
|
4417
|
+
* `a:grpSpLocks`), each as the boolean it states, keyed by attribute name (`noMove`, `noGrp`, …).
|
|
4418
|
+
* `null` when the shape has no locking element. The read counterpart of `objectLock`.
|
|
4419
|
+
*/
|
|
4420
|
+
get locks() {
|
|
4421
|
+
const cNvPr = nonVisualCNvPr(this.element);
|
|
4422
|
+
const nvPr = cNvPr?.parentNode;
|
|
4423
|
+
if (!nvPr) return null;
|
|
4424
|
+
for (const props of childElements(nvPr)) {
|
|
4425
|
+
if (props === cNvPr) continue;
|
|
4426
|
+
const lock = childElements(props).find((child) => child.localName?.endsWith("Locks"));
|
|
4427
|
+
if (!lock) continue;
|
|
4428
|
+
const out = {};
|
|
4429
|
+
for (const attribute of Array.from(lock.attributes)) {
|
|
4430
|
+
const value = boolValue(attribute.value);
|
|
4431
|
+
if (value !== null) out[attribute.localName ?? attribute.name] = value;
|
|
4432
|
+
}
|
|
4433
|
+
return out;
|
|
4434
|
+
}
|
|
4435
|
+
return null;
|
|
4436
|
+
}
|
|
4437
|
+
/**
|
|
4053
4438
|
* The shape's alt-text description (`p:cNvPr/@descr`), or `null` when unset.
|
|
4054
4439
|
* This is the accessibility primitive a screen reader announces; an audit or
|
|
4055
4440
|
* accessible-export tool reads it here. Distinct from {@link name}, which is
|
|
@@ -4083,7 +4468,7 @@ var Shape = class {
|
|
|
4083
4468
|
/**
|
|
4084
4469
|
* Whether the shape is flagged **decorative** (`p:cNvPr/a:extLst/a:ext`
|
|
4085
4470
|
* uri `{C183D7F6-…}` / `adec:decorative@val`), PowerPoint's "Mark as
|
|
4086
|
-
* decorative"
|
|
4471
|
+
* decorative": a purely visual element assistive tech should skip. `false`
|
|
4087
4472
|
* when the extension is absent. A decorative shape typically has no
|
|
4088
4473
|
* {@link description}; the two are alternatives, not companions.
|
|
4089
4474
|
*/
|
|
@@ -4154,8 +4539,8 @@ var Shape = class {
|
|
|
4154
4539
|
}
|
|
4155
4540
|
/**
|
|
4156
4541
|
* Clockwise rotation in degrees (`a:xfrm/@rot` ÷ 60000), or `null` when the
|
|
4157
|
-
* shape has no own transform. A present xfrm with no `@rot` reads as `0`, so
|
|
4158
|
-
*
|
|
4542
|
+
* shape has no own transform. A present xfrm with no `@rot` reads as `0`, so,
|
|
4543
|
+
* mirroring {@link left}/{@link top}/{@link width}/{@link height}, `null`
|
|
4159
4544
|
* ("inherits layout geometry") stays distinct from `0` ("has a transform, not
|
|
4160
4545
|
* rotated"). The value is faithful to the XML and not normalised to a signed
|
|
4161
4546
|
* range, so a `@rot` past 360° (e.g. a negative angle stored as `19216344`)
|
|
@@ -4202,7 +4587,7 @@ var Shape = class {
|
|
|
4202
4587
|
* equal the shape's own transform values.
|
|
4203
4588
|
*
|
|
4204
4589
|
* `null` when the shape (or any enclosing group) has no own transform, or a
|
|
4205
|
-
* group's `a:chExt` is degenerate (zero)
|
|
4590
|
+
* group's `a:chExt` is degenerate (zero). There is then no resolvable frame.
|
|
4206
4591
|
* {@link absoluteFrameFailure} says which of those it was.
|
|
4207
4592
|
*
|
|
4208
4593
|
* A rotated shape's returned `left`/`top` remain PowerPoint's unrotated
|
|
@@ -4217,7 +4602,7 @@ var Shape = class {
|
|
|
4217
4602
|
* Why {@link absoluteFrame} is `null`, or `null` when it resolved.
|
|
4218
4603
|
*
|
|
4219
4604
|
* `absoluteFrame` reports one `null` for three different situations, and only a
|
|
4220
|
-
* caller that wants to *report* the unresolvable shape needs them apart
|
|
4605
|
+
* caller that wants to *report* the unresolvable shape needs them apart: an
|
|
4221
4606
|
* inherited placeholder box is ordinary, a group with no usable `a:xfrm` is not.
|
|
4222
4607
|
* The distinction is not recoverable from the `null`, and re-deriving it means
|
|
4223
4608
|
* walking the same ancestry a second time, so it is named here instead. See
|
|
@@ -4320,13 +4705,13 @@ var Shape = class {
|
|
|
4320
4705
|
this.#applyFill(value === null ? null : { scheme: value });
|
|
4321
4706
|
}
|
|
4322
4707
|
/**
|
|
4323
|
-
* `true` when the shape sets an explicit no-fill (`spPr/a:noFill`)
|
|
4708
|
+
* `true` when the shape sets an explicit no-fill (`spPr/a:noFill`): a
|
|
4324
4709
|
* deliberately transparent surface. The fill-side counterpart of
|
|
4325
4710
|
* {@link lineNoFill}, and the only accessor that separates it from a shape
|
|
4326
4711
|
* carrying no fill child at all (one inheriting through `p:style/a:fillRef`):
|
|
4327
|
-
* every other fill accessor
|
|
4712
|
+
* every other fill accessor ({@link fillColor}, {@link fillSchemeColor},
|
|
4328
4713
|
* {@link resolvedFill}, {@link gradientFill}, {@link patternFill},
|
|
4329
|
-
* {@link pictureFill}
|
|
4714
|
+
* {@link pictureFill}) reports `null` for both. The two paint completely
|
|
4330
4715
|
* differently, so a consumer that cannot tell them apart paints a transparent
|
|
4331
4716
|
* shape in the theme's accent colour.
|
|
4332
4717
|
*/
|
|
@@ -4335,7 +4720,7 @@ var Shape = class {
|
|
|
4335
4720
|
return !!(props && firstChild(props, "a:noFill"));
|
|
4336
4721
|
}
|
|
4337
4722
|
/**
|
|
4338
|
-
* Set an explicit `<a:noFill/>` on the shape
|
|
4723
|
+
* Set an explicit `<a:noFill/>` on the shape: a transparent surface. This is
|
|
4339
4724
|
* distinct from clearing the fill (`fillColor = null`), which removes the
|
|
4340
4725
|
* `a:solidFill` and lets the fill inherit from the shape's style/placeholder.
|
|
4341
4726
|
* Read it back with {@link fillNoFill}.
|
|
@@ -4365,14 +4750,14 @@ var Shape = class {
|
|
|
4365
4750
|
/**
|
|
4366
4751
|
* The line/border dash style (`spPr/a:ln/a:prstDash/@val`), e.g. `'dash'`,
|
|
4367
4752
|
* `'lgDashDot'`, `'sysDot'`, or `null` when the line is solid/unset. A faithful
|
|
4368
|
-
* replica of dashed dividers and dashed card borders needs this
|
|
4753
|
+
* replica of dashed dividers and dashed card borders needs this: it is
|
|
4369
4754
|
* otherwise invisible in {@link lineColor}/{@link lineWidthPt} alone.
|
|
4370
4755
|
*/
|
|
4371
4756
|
get lineDash() {
|
|
4372
4757
|
return this.#lineBasics()?.dash ?? null;
|
|
4373
4758
|
}
|
|
4374
4759
|
/**
|
|
4375
|
-
* `true` when the shape sets an explicit no-line (`spPr/a:ln/a:noFill`)
|
|
4760
|
+
* `true` when the shape sets an explicit no-line (`spPr/a:ln/a:noFill`): a
|
|
4376
4761
|
* deliberately border-less shape. Distinct from simply having no `a:ln`
|
|
4377
4762
|
* (an inherited line), which {@link resolvedLine} cannot tell apart: both
|
|
4378
4763
|
* report `null`. A replica that relies on a shadow instead of a border needs
|
|
@@ -4382,8 +4767,20 @@ var Shape = class {
|
|
|
4382
4767
|
return this.#lineBasics()?.noFill ?? false;
|
|
4383
4768
|
}
|
|
4384
4769
|
/**
|
|
4385
|
-
*
|
|
4386
|
-
*
|
|
4770
|
+
* Whether the shape states an outline of its **own** (`spPr/a:ln`), however little it holds.
|
|
4771
|
+
*
|
|
4772
|
+
* The line accessors all report `null` both for a shape with no `a:ln` and for one whose `a:ln`
|
|
4773
|
+
* is empty, and the two are different statements to a writer: an empty `<a:ln/>` says "take the
|
|
4774
|
+
* whole outline from `p:style` or the placeholder", which is what PowerPoint writes on its own
|
|
4775
|
+
* placeholders, and `addShape` writes it for `line: { type: 'inherit' }`. A copy that has to
|
|
4776
|
+
* reproduce the outline rather than describe it needs to know which one it has.
|
|
4777
|
+
*/
|
|
4778
|
+
get hasOwnLine() {
|
|
4779
|
+
return this.#line() !== null;
|
|
4780
|
+
}
|
|
4781
|
+
/**
|
|
4782
|
+
* The line end cap (`spPr/a:ln/@cap`) as the raw OOXML token, `'flat'`,
|
|
4783
|
+
* `'rnd'` (round) or `'sq'` (square), or `null` when unset (PowerPoint's
|
|
4387
4784
|
* default is `flat`). The write API authors this attribute through
|
|
4388
4785
|
* `ShapeLineProps.cap`, so without the accessor a deck this library produced
|
|
4389
4786
|
* could not be read back without losing it.
|
|
@@ -4398,8 +4795,8 @@ var Shape = class {
|
|
|
4398
4795
|
return ln ? attr(ln, "cap") ?? null : null;
|
|
4399
4796
|
}
|
|
4400
4797
|
/**
|
|
4401
|
-
* The line alignment (`spPr/a:ln/@algn`) as the raw OOXML token
|
|
4402
|
-
* (centred on the shape's outline) or `'in'` (inset, drawn wholly inside it)
|
|
4798
|
+
* The line alignment (`spPr/a:ln/@algn`) as the raw OOXML token, `'ctr'`
|
|
4799
|
+
* (centred on the shape's outline) or `'in'` (inset, drawn wholly inside it),
|
|
4403
4800
|
* or `null` when unset. It shifts a thick outline by half its width, so it
|
|
4404
4801
|
* changes where the border sits relative to the fill.
|
|
4405
4802
|
*/
|
|
@@ -4424,7 +4821,7 @@ var Shape = class {
|
|
|
4424
4821
|
* shape uses preset geometry / none. The faithful, multi-path counterpart of
|
|
4425
4822
|
* {@link presetGeometry}: each `a:path` keeps its own path-unit viewport
|
|
4426
4823
|
* (`w`/`h`) and ordered {@link GeometryCommand}s. Coordinates are raw path-unit
|
|
4427
|
-
* integers, not EMU
|
|
4824
|
+
* integers, not EMU. Pair the path `w`/`h` with the shape's box size to map
|
|
4428
4825
|
* them into slide space.
|
|
4429
4826
|
*
|
|
4430
4827
|
* Not an auto-shape-only property either: a picture clipped to a freeform (what
|
|
@@ -4436,7 +4833,12 @@ var Shape = class {
|
|
|
4436
4833
|
const custGeom = props && firstChild(props, "a:custGeom");
|
|
4437
4834
|
if (!custGeom) return null;
|
|
4438
4835
|
const pathLst = firstChild(custGeom, "a:pathLst");
|
|
4439
|
-
return {
|
|
4836
|
+
return {
|
|
4837
|
+
paths: pathLst ? getElements(pathLst, "a:path").map((p) => readGeometryPath(p)) : [],
|
|
4838
|
+
guides: readGeometryGuides(firstChild(custGeom, "a:gdLst")),
|
|
4839
|
+
adjustHandles: readAdjustHandles(firstChild(custGeom, "a:ahLst")),
|
|
4840
|
+
connectionSites: readConnectionSites(firstChild(custGeom, "a:cxnLst"))
|
|
4841
|
+
};
|
|
4440
4842
|
}
|
|
4441
4843
|
/**
|
|
4442
4844
|
* Preset-geometry adjustment values (`spPr/a:prstGeom/a:avLst/a:gd`) as a
|
|
@@ -4485,7 +4887,7 @@ var Shape = class {
|
|
|
4485
4887
|
* The shape's gradient fill with its geometry (`spPr/a:gradFill`), or `null`
|
|
4486
4888
|
* when the fill is not a gradient. Unlike {@link gradientStops} (stops only),
|
|
4487
4889
|
* this also carries the linear {@link GradientFill.angleDeg} or the
|
|
4488
|
-
* {@link GradientFill.path} shape
|
|
4890
|
+
* {@link GradientFill.path} shape: the geometry a faithful replica needs and
|
|
4489
4891
|
* which the bare stop list omits.
|
|
4490
4892
|
*/
|
|
4491
4893
|
get gradientFill() {
|
|
@@ -4496,8 +4898,8 @@ var Shape = class {
|
|
|
4496
4898
|
* The shape's line/border **gradient** stroke (`spPr/a:ln/a:gradFill`), or
|
|
4497
4899
|
* `null` when the line is a solid, absent, or inherited border (see
|
|
4498
4900
|
* {@link resolvedLine}). The line counterpart of {@link gradientFill}: a
|
|
4499
|
-
* gradient-stroked connector
|
|
4500
|
-
*
|
|
4901
|
+
* gradient-stroked connector (common for faded process arrows in styled decks)
|
|
4902
|
+
* otherwise surfaces only its {@link lineWidthPt}, dropping the colour
|
|
4501
4903
|
* entirely, so a replica cannot reproduce the stroke.
|
|
4502
4904
|
*/
|
|
4503
4905
|
get lineGradient() {
|
|
@@ -4505,6 +4907,15 @@ var Shape = class {
|
|
|
4505
4907
|
return ln ? this.#gradientFillIn(ln) : null;
|
|
4506
4908
|
}
|
|
4507
4909
|
/**
|
|
4910
|
+
* The shape's line/border **pattern** stroke (`spPr/a:ln/a:pattFill`), or `null` when the line
|
|
4911
|
+
* is not a pattern. The line counterpart of {@link patternFill}, and what the write side's
|
|
4912
|
+
* `line.pattern` writes.
|
|
4913
|
+
*/
|
|
4914
|
+
get linePatternFill() {
|
|
4915
|
+
const ln = this.#line();
|
|
4916
|
+
return ln ? readPatternFill(ln, this.host.themeContext()) : null;
|
|
4917
|
+
}
|
|
4918
|
+
/**
|
|
4508
4919
|
* The shape's line/connector arrowheads (`a:ln/a:headEnd` + `a:tailEnd`), or
|
|
4509
4920
|
* `null` when neither end carries one. Essential for replicating connectors,
|
|
4510
4921
|
* whose dot/arrow ends are otherwise invisible in the geometry.
|
|
@@ -4557,7 +4968,7 @@ var Shape = class {
|
|
|
4557
4968
|
/**
|
|
4558
4969
|
* The shape's reflection (`spPr/a:effectLst/a:reflection`), or `null` when it has
|
|
4559
4970
|
* none. Read-only: this library authors no reflection, so a replica should carry
|
|
4560
|
-
* the part rather than regenerate it
|
|
4971
|
+
* the part rather than regenerate it (see {@link Reflection}).
|
|
4561
4972
|
*/
|
|
4562
4973
|
get reflection() {
|
|
4563
4974
|
return readReflection(this.#effectLst());
|
|
@@ -4572,7 +4983,7 @@ var Shape = class {
|
|
|
4572
4983
|
/**
|
|
4573
4984
|
* The shape's pattern (hatch) fill (`spPr/a:pattFill`), or `null` when the fill
|
|
4574
4985
|
* is not a pattern. Surfaces the {@link PatternFill.preset} name and both
|
|
4575
|
-
* colours resolved against the host's theme
|
|
4986
|
+
* colours resolved against the host's theme: the pattern counterpart of
|
|
4576
4987
|
* {@link resolvedFill}, which reports `null` for a non-solid fill and so drops a
|
|
4577
4988
|
* hatched surface entirely.
|
|
4578
4989
|
*/
|
|
@@ -4583,8 +4994,8 @@ var Shape = class {
|
|
|
4583
4994
|
/**
|
|
4584
4995
|
* The shape's picture (image) fill (`spPr/a:blipFill`), or `null` when the fill
|
|
4585
4996
|
* is not a picture. The image-fill counterpart of {@link patternFill}: a shape
|
|
4586
|
-
* whose *surface* is an image is not a {@link Picture}
|
|
4587
|
-
* a blip fill
|
|
4997
|
+
* whose *surface* is an image is not a {@link Picture} (it is an autoShape with
|
|
4998
|
+
* a blip fill), and {@link resolvedFill} reports `null` for one, so without this
|
|
4588
4999
|
* an image-filled shape reads as unfilled. Carries the embedded image
|
|
4589
5000
|
* ({@link PictureFill.relId}/{@link PictureFill.partName}) plus the stretch/tile
|
|
4590
5001
|
* geometry.
|
|
@@ -4593,18 +5004,18 @@ var Shape = class {
|
|
|
4593
5004
|
const props = this.properties();
|
|
4594
5005
|
return props ? readPictureFill(props, this.host.relationships) : null;
|
|
4595
5006
|
}
|
|
4596
|
-
/** The shape's effect list (`spPr/a:effectLst`), or `null
|
|
5007
|
+
/** The shape's effect list (`spPr/a:effectLst`), or `null`: what `shapes/effects.ts` decodes. */
|
|
4597
5008
|
#effectLst() {
|
|
4598
5009
|
const props = this.properties();
|
|
4599
5010
|
return props ? firstChild(props, "a:effectLst") : null;
|
|
4600
5011
|
}
|
|
4601
5012
|
/**
|
|
4602
5013
|
* The shape's solid fill resolved against the host's theme
|
|
4603
|
-
* ({@link Slide.themeContext}) to a literal hex
|
|
5014
|
+
* ({@link Slide.themeContext}) to a literal hex: the resolved counterpart of
|
|
4604
5015
|
* {@link fillColor}/{@link fillSchemeColor}, which report the raw reference.
|
|
4605
5016
|
* `null` when the shape has no `a:solidFill` (a gradient/none/inherited fill)
|
|
4606
5017
|
* or the colour cannot be made literal. The returned {@link ResolvedColor}
|
|
4607
|
-
* carries the base `hex` and raw transforms, and `effectiveHex
|
|
5018
|
+
* carries the base `hex` and raw transforms, and `effectiveHex`, the base with
|
|
4608
5019
|
* its colour transforms (`lumMod`/`shade`/…) applied (read that for the final
|
|
4609
5020
|
* rendered colour).
|
|
4610
5021
|
*
|
|
@@ -4620,7 +5031,7 @@ var Shape = class {
|
|
|
4620
5031
|
}
|
|
4621
5032
|
/**
|
|
4622
5033
|
* The shape's line/border solid fill resolved against the host's theme to a
|
|
4623
|
-
* literal hex
|
|
5034
|
+
* literal hex: the resolved counterpart of {@link lineColor}/{@link lineSchemeColor}.
|
|
4624
5035
|
* `null` when the shape has no `a:ln/a:solidFill` or it cannot be made literal.
|
|
4625
5036
|
* Like {@link resolvedFill}, the result carries `effectiveHex` (the base colour
|
|
4626
5037
|
* with its transforms applied) for the final rendered colour.
|
|
@@ -4708,11 +5119,20 @@ var Shape = class {
|
|
|
4708
5119
|
}
|
|
4709
5120
|
/**
|
|
4710
5121
|
* This shape's placeholder identity (`p:ph` `type`/`idx`), or `null` when it is
|
|
4711
|
-
* not a placeholder.
|
|
4712
|
-
*
|
|
5122
|
+
* not a placeholder. `idx` defaults to `'0'` when the attribute is absent, as
|
|
5123
|
+
* PowerPoint does. A text placeholder is a `p:sp`; a filled picture, table or chart
|
|
5124
|
+
* placeholder is a `p:pic` or `p:graphicFrame` carrying the same `p:ph`, which is what
|
|
5125
|
+
* inserting a picture into a picture placeholder produces. This used to be `null` for
|
|
5126
|
+
* every shape but a `p:sp` (#111). Use {@link Slide.placeholder} to find a text
|
|
5127
|
+
* placeholder by type.
|
|
4713
5128
|
*/
|
|
4714
5129
|
get placeholder() {
|
|
4715
|
-
|
|
5130
|
+
const nvPr = nvPrOf(this.element);
|
|
5131
|
+
const ph = nvPr && firstChild(nvPr, "p:ph");
|
|
5132
|
+
return ph ? {
|
|
5133
|
+
type: attr(ph, "type"),
|
|
5134
|
+
idx: attr(ph, "idx") ?? "0"
|
|
5135
|
+
} : null;
|
|
4716
5136
|
}
|
|
4717
5137
|
/**
|
|
4718
5138
|
* Remove this shape from its parent (the host's shape tree, or an enclosing
|
|
@@ -4739,10 +5159,74 @@ var Shape = class {
|
|
|
4739
5159
|
return this.element;
|
|
4740
5160
|
}
|
|
4741
5161
|
};
|
|
4742
|
-
|
|
4743
|
-
|
|
5162
|
+
/** An `ST_AdjCoordinate` attribute: a number in EMU, a guide name, or `null` when absent. */
|
|
5163
|
+
function adjCoordinate(element, name) {
|
|
5164
|
+
const value = attr(element, name);
|
|
5165
|
+
if (value === null || value === "") return null;
|
|
5166
|
+
const number = numberValue(value);
|
|
5167
|
+
return number === null ? value : number;
|
|
5168
|
+
}
|
|
5169
|
+
/** An `ST_AdjAngle` attribute in degrees, or `null` when absent or a guide name. */
|
|
5170
|
+
function adjAngleDeg(element, name) {
|
|
5171
|
+
const number = numberValue(attr(element, name));
|
|
5172
|
+
return number === null ? null : number / 6e4;
|
|
5173
|
+
}
|
|
5174
|
+
/** A custom geometry's `a:gdLst` as {@link CustomGeometryGuide}s. */
|
|
5175
|
+
function readGeometryGuides(gdLst) {
|
|
5176
|
+
if (!gdLst) return [];
|
|
5177
|
+
return getElements(gdLst, "a:gd").map((gd) => ({
|
|
5178
|
+
name: attr(gd, "name") ?? "",
|
|
5179
|
+
formula: attr(gd, "fmla") ?? ""
|
|
5180
|
+
}));
|
|
5181
|
+
}
|
|
5182
|
+
/** A custom geometry's `a:ahLst` as {@link CustomGeometryAdjustHandle}s, XY and polar in document order. */
|
|
5183
|
+
function readAdjustHandles(ahLst) {
|
|
5184
|
+
if (!ahLst) return [];
|
|
5185
|
+
const handles = [];
|
|
5186
|
+
for (const handle of childElements(ahLst)) {
|
|
5187
|
+
const pos = firstChild(handle, "a:pos");
|
|
5188
|
+
const x = pos ? adjCoordinate(pos, "x") : null;
|
|
5189
|
+
const y = pos ? adjCoordinate(pos, "y") : null;
|
|
5190
|
+
if (handle.localName === "ahXY") handles.push({
|
|
5191
|
+
kind: "xy",
|
|
5192
|
+
x,
|
|
5193
|
+
y,
|
|
5194
|
+
gdRefX: attr(handle, "gdRefX"),
|
|
5195
|
+
minX: adjCoordinate(handle, "minX"),
|
|
5196
|
+
maxX: adjCoordinate(handle, "maxX"),
|
|
5197
|
+
gdRefY: attr(handle, "gdRefY"),
|
|
5198
|
+
minY: adjCoordinate(handle, "minY"),
|
|
5199
|
+
maxY: adjCoordinate(handle, "maxY")
|
|
5200
|
+
});
|
|
5201
|
+
else if (handle.localName === "ahPolar") handles.push({
|
|
5202
|
+
kind: "polar",
|
|
5203
|
+
x,
|
|
5204
|
+
y,
|
|
5205
|
+
gdRefR: attr(handle, "gdRefR"),
|
|
5206
|
+
minR: adjCoordinate(handle, "minR"),
|
|
5207
|
+
maxR: adjCoordinate(handle, "maxR"),
|
|
5208
|
+
gdRefAng: attr(handle, "gdRefAng"),
|
|
5209
|
+
minAngDeg: adjAngleDeg(handle, "minAng"),
|
|
5210
|
+
maxAngDeg: adjAngleDeg(handle, "maxAng")
|
|
5211
|
+
});
|
|
5212
|
+
}
|
|
5213
|
+
return handles;
|
|
5214
|
+
}
|
|
5215
|
+
/** A custom geometry's `a:cxnLst` as {@link CustomGeometryConnectionSite}s. */
|
|
5216
|
+
function readConnectionSites(cxnLst) {
|
|
5217
|
+
if (!cxnLst) return [];
|
|
5218
|
+
return getElements(cxnLst, "a:cxn").map((cxn) => {
|
|
5219
|
+
const pos = firstChild(cxn, "a:pos");
|
|
5220
|
+
return {
|
|
5221
|
+
angleDeg: adjAngleDeg(cxn, "ang"),
|
|
5222
|
+
x: pos ? adjCoordinate(pos, "x") : null,
|
|
5223
|
+
y: pos ? adjCoordinate(pos, "y") : null
|
|
5224
|
+
};
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5227
|
+
// src/read/api/shapes/autoshape.ts
|
|
4744
5228
|
/**
|
|
4745
|
-
* An auto shape, text box, or placeholder (`p:sp`)
|
|
5229
|
+
* An auto shape, text box, or placeholder (`p:sp`): the only shape kind that holds text.
|
|
4746
5230
|
*/
|
|
4747
5231
|
/** An auto shape, text box, or placeholder (`p:sp`). The only kind that holds text. */
|
|
4748
5232
|
var AutoShape = class extends Shape {
|
|
@@ -4765,22 +5249,8 @@ var AutoShape = class extends Shape {
|
|
|
4765
5249
|
inherit
|
|
4766
5250
|
});
|
|
4767
5251
|
}
|
|
4768
|
-
/**
|
|
4769
|
-
* This shape's placeholder identity (`p:ph` `type`/`idx`), or `null` when it is
|
|
4770
|
-
* not a placeholder. `idx` defaults to `'0'` when the attribute is absent, as
|
|
4771
|
-
* PowerPoint does. Use {@link Slide.placeholder} to find a placeholder by type.
|
|
4772
|
-
*/
|
|
4773
|
-
get placeholder() {
|
|
4774
|
-
const nvPr = nvPrOf(this.element);
|
|
4775
|
-
const ph = nvPr && firstChild(nvPr, "p:ph");
|
|
4776
|
-
return ph ? {
|
|
4777
|
-
type: attr(ph, "type"),
|
|
4778
|
-
idx: attr(ph, "idx") ?? "0"
|
|
4779
|
-
} : null;
|
|
4780
|
-
}
|
|
4781
5252
|
};
|
|
4782
|
-
|
|
4783
|
-
//#region src/read/api/shapes/picture.ts
|
|
5253
|
+
// src/read/api/shapes/picture.ts
|
|
4784
5254
|
/**
|
|
4785
5255
|
* A picture (`p:pic`).
|
|
4786
5256
|
*
|
|
@@ -4809,6 +5279,34 @@ function recolorColorOf(color, ctx) {
|
|
|
4809
5279
|
if (!color || color.namespaceURI !== OOXML_NS.a) return null;
|
|
4810
5280
|
return readColorRef(color, ctx);
|
|
4811
5281
|
}
|
|
5282
|
+
/** One `a:blip` child as a {@link Recolor}, or `null` when it is not one of the five recognised effects. */
|
|
5283
|
+
function recolorOf(child, ctx) {
|
|
5284
|
+
switch (child.localName) {
|
|
5285
|
+
case "duotone": return {
|
|
5286
|
+
kind: "duotone",
|
|
5287
|
+
stops: childElements(child).map((color) => recolorColorOf(color, ctx)).filter((c) => c !== null)
|
|
5288
|
+
};
|
|
5289
|
+
case "clrChange": {
|
|
5290
|
+
const from = firstChild(child, "a:clrFrom");
|
|
5291
|
+
const to = firstChild(child, "a:clrTo");
|
|
5292
|
+
return {
|
|
5293
|
+
kind: "clrChange",
|
|
5294
|
+
from: from ? recolorColorOf(firstChildElement(from), ctx) : null,
|
|
5295
|
+
to: to ? recolorColorOf(firstChildElement(to), ctx) : null
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
case "grayscl": return { kind: "grayscale" };
|
|
5299
|
+
case "biLevel": return {
|
|
5300
|
+
kind: "biLevel",
|
|
5301
|
+
threshold: pctAttr(child, "thresh")
|
|
5302
|
+
};
|
|
5303
|
+
case "alphaModFix": return {
|
|
5304
|
+
kind: "alphaModFix",
|
|
5305
|
+
amount: pctAttr(child, "amt") ?? 1
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
return null;
|
|
5309
|
+
}
|
|
4812
5310
|
/** A picture (`p:pic`). */
|
|
4813
5311
|
var Picture = class extends Shape {
|
|
4814
5312
|
shapeType = "picture";
|
|
@@ -4848,7 +5346,7 @@ var Picture = class extends Shape {
|
|
|
4848
5346
|
* Microsoft SVG blip extension (`a:blip/a:extLst/a:ext/asvg:svgBlip/@r:embed`),
|
|
4849
5347
|
* or `null` when the picture has no SVG. PowerPoint usually pairs this with a
|
|
4850
5348
|
* raster fallback in `a:blip/@r:embed` ({@link imageRelId}), but some exporters
|
|
4851
|
-
* emit an SVG-only blip where `imageRelId` is absent and only this resolves
|
|
5349
|
+
* emit an SVG-only blip where `imageRelId` is absent and only this resolves,
|
|
4852
5350
|
* so a reader that wants the real drawn art must consult both.
|
|
4853
5351
|
*/
|
|
4854
5352
|
get svgRelId() {
|
|
@@ -4861,14 +5359,14 @@ var Picture = class extends Shape {
|
|
|
4861
5359
|
}
|
|
4862
5360
|
/**
|
|
4863
5361
|
* Which drawable media this picture carries:
|
|
4864
|
-
* - `'raster'
|
|
4865
|
-
* - `'svg'
|
|
5362
|
+
* - `'raster'`: only a raster blip (`a:blip/@r:embed`);
|
|
5363
|
+
* - `'svg'`: only a vector blip (`asvg:svgBlip/@r:embed`, no raster). This
|
|
4866
5364
|
* is what PowerPoint's *Insert → Icons* and a plain SVG insert produce;
|
|
4867
|
-
* - `'both'
|
|
4868
|
-
* - `'none'
|
|
5365
|
+
* - `'both'`: a raster fallback *and* an SVG (PowerPoint's usual pairing);
|
|
5366
|
+
* - `'none'`: a `p:pic` with no embedded blip at all (e.g. a linked image).
|
|
4869
5367
|
*
|
|
4870
|
-
* Lets a caller distinguish an SVG-only picture
|
|
4871
|
-
* is legitimately `null`
|
|
5368
|
+
* Lets a caller distinguish an SVG-only picture (where {@link imagePartName}
|
|
5369
|
+
* is legitimately `null`) from a genuinely empty one, without two null checks.
|
|
4872
5370
|
*/
|
|
4873
5371
|
get mediaKind() {
|
|
4874
5372
|
const hasRaster = this.imageRelId != null;
|
|
@@ -4880,7 +5378,7 @@ var Picture = class extends Shape {
|
|
|
4880
5378
|
}
|
|
4881
5379
|
/**
|
|
4882
5380
|
* Absolute partname of whichever part actually carries this picture's drawn
|
|
4883
|
-
* data
|
|
5381
|
+
* data (the raster part when present, otherwise the SVG part), or `null`
|
|
4884
5382
|
* when the picture embeds neither. Use this when you just want "the bytes
|
|
4885
5383
|
* this picture shows"; prefer {@link imagePartName} / {@link svgPartName}
|
|
4886
5384
|
* (and {@link mediaKind}) when you need to know *which* kind it is. An
|
|
@@ -4891,8 +5389,52 @@ var Picture = class extends Shape {
|
|
|
4891
5389
|
return this.imagePartName ?? this.svgPartName;
|
|
4892
5390
|
}
|
|
4893
5391
|
/**
|
|
5392
|
+
* The audio or video this picture plays (`p:nvPr/a:audioFile` or `a:videoFile`), or `null` for a
|
|
5393
|
+
* still picture. Its blip ({@link imagePartName}) is then the poster frame. See {@link PictureMedia}.
|
|
5394
|
+
*/
|
|
5395
|
+
get media() {
|
|
5396
|
+
const nvPr = firstChild(this.element, "p:nvPicPr");
|
|
5397
|
+
const props = nvPr && firstChild(nvPr, "p:nvPr");
|
|
5398
|
+
if (!props) return null;
|
|
5399
|
+
const file = firstChild(props, "a:videoFile") ?? firstChild(props, "a:audioFile");
|
|
5400
|
+
if (!file) return null;
|
|
5401
|
+
const rels = this.host.relationships;
|
|
5402
|
+
const embedded = descendantsByTag(props, OOXML_NS.p14, "media").map((media) => resolvePartName(attr(media, "r:embed"), rels)).find((name) => name !== null);
|
|
5403
|
+
const linkId = attr(file, "r:link");
|
|
5404
|
+
const partName = embedded ?? resolvePartName(linkId, rels);
|
|
5405
|
+
const linkRel = linkId ? rels.get(linkId) : void 0;
|
|
5406
|
+
return {
|
|
5407
|
+
kind: file.localName === "audioFile" ? "audio" : "video",
|
|
5408
|
+
partName,
|
|
5409
|
+
contentType: partName === null ? null : this.host.opc.part(partName)?.contentType ?? null,
|
|
5410
|
+
link: partName === null && linkRel?.targetMode === "External" ? linkRel.target : null,
|
|
5411
|
+
repeatCount: this.#repeatCount()
|
|
5412
|
+
};
|
|
5413
|
+
}
|
|
5414
|
+
/**
|
|
5415
|
+
* The `repeatCount` of the media node the host's `p:timing` aims at this picture, as
|
|
5416
|
+
* {@link PictureMedia.repeatCount} reports it.
|
|
5417
|
+
*/
|
|
5418
|
+
#repeatCount() {
|
|
5419
|
+
const id = this.id;
|
|
5420
|
+
const root = this.element.ownerDocument?.documentElement;
|
|
5421
|
+
if (id === null || !root) return null;
|
|
5422
|
+
for (const node of descendantsByTag(root, OOXML_NS.p, "cMediaNode")) {
|
|
5423
|
+
const target = firstChild(node, "p:tgtEl");
|
|
5424
|
+
const spTgt = target && firstChild(target, "p:spTgt");
|
|
5425
|
+
if (!spTgt || attr(spTgt, "spid") !== String(id)) continue;
|
|
5426
|
+
const cTn = firstChild(node, "p:cTn");
|
|
5427
|
+
const value = cTn && attr(cTn, "repeatCount");
|
|
5428
|
+
if (value === null) return null;
|
|
5429
|
+
if (value === "indefinite") return "indefinite";
|
|
5430
|
+
const count = Number(value);
|
|
5431
|
+
return Number.isFinite(count) && count > 0 ? count / 1e3 : null;
|
|
5432
|
+
}
|
|
5433
|
+
return null;
|
|
5434
|
+
}
|
|
5435
|
+
/**
|
|
4894
5436
|
* The picture's crop as fractions of the *source image*, read from
|
|
4895
|
-
* `p:blipFill/a:srcRect`
|
|
5437
|
+
* `p:blipFill/a:srcRect` as `{ left, top, right, bottom }`, each the amount
|
|
4896
5438
|
* trimmed off that edge (so `0.1` = 10 % cropped away, an uncropped edge is
|
|
4897
5439
|
* `0`). `null` when there is no `a:srcRect` at all; an explicit
|
|
4898
5440
|
* `{0,0,0,0}` crop still reports zeros, since its presence is meaningful.
|
|
@@ -4910,44 +5452,32 @@ var Picture = class extends Shape {
|
|
|
4910
5452
|
* `null` when the blip carries none. Recognises the effects a faithful reader
|
|
4911
5453
|
* needs to reproduce a recoloured image: `a:duotone` (the two-stop icon-tint
|
|
4912
5454
|
* trick), `a:clrChange`, `a:grayscl`, `a:biLevel`, and `a:alphaModFix`; the
|
|
4913
|
-
* first such effect in document order wins. Colours mirror the
|
|
5455
|
+
* first such effect in document order wins, and {@link recolors} lists them all. Colours mirror the
|
|
4914
5456
|
* {@link GradientStop} split (`color`/`schemeColor`/`presetColor`) so theme
|
|
4915
5457
|
* tokens resolve through {@link Slide.themeContext}. `threshold`/`amount` are
|
|
4916
5458
|
* 0–1 fractions. {@link hidden} (the duotone fallback-layer trick) reports the
|
|
4917
5459
|
* *visibility* of a recolour source; this reports the *tint* itself.
|
|
4918
5460
|
*/
|
|
4919
5461
|
get recolor() {
|
|
5462
|
+
return this.recolors[0] ?? null;
|
|
5463
|
+
}
|
|
5464
|
+
/**
|
|
5465
|
+
* Every blip effect {@link recolor} recognises, in document order: the first is
|
|
5466
|
+
* {@link recolor}. They combine (a picture can be faded with `a:alphaModFix` and
|
|
5467
|
+
* recoloured too, which is how the write API emits `transparency` beside a
|
|
5468
|
+
* recolour), and {@link recolor} alone reported only the first of them.
|
|
5469
|
+
*/
|
|
5470
|
+
get recolors() {
|
|
4920
5471
|
const blip = this.#blip();
|
|
4921
|
-
if (!blip) return
|
|
5472
|
+
if (!blip) return [];
|
|
4922
5473
|
const ctx = this.host.themeContext();
|
|
5474
|
+
const out = [];
|
|
4923
5475
|
for (const child of childElements(blip)) {
|
|
4924
5476
|
if (child.namespaceURI !== OOXML_NS.a) continue;
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
kind: "duotone",
|
|
4928
|
-
stops: childElements(child).map((color) => recolorColorOf(color, ctx)).filter((c) => c !== null)
|
|
4929
|
-
};
|
|
4930
|
-
case "clrChange": {
|
|
4931
|
-
const from = firstChild(child, "a:clrFrom");
|
|
4932
|
-
const to = firstChild(child, "a:clrTo");
|
|
4933
|
-
return {
|
|
4934
|
-
kind: "clrChange",
|
|
4935
|
-
from: from ? recolorColorOf(firstChildElement(from), ctx) : null,
|
|
4936
|
-
to: to ? recolorColorOf(firstChildElement(to), ctx) : null
|
|
4937
|
-
};
|
|
4938
|
-
}
|
|
4939
|
-
case "grayscl": return { kind: "grayscale" };
|
|
4940
|
-
case "biLevel": return {
|
|
4941
|
-
kind: "biLevel",
|
|
4942
|
-
threshold: pctAttr(child, "thresh")
|
|
4943
|
-
};
|
|
4944
|
-
case "alphaModFix": return {
|
|
4945
|
-
kind: "alphaModFix",
|
|
4946
|
-
amount: pctAttr(child, "amt") ?? 1
|
|
4947
|
-
};
|
|
4948
|
-
}
|
|
5477
|
+
const effect = recolorOf(child, ctx);
|
|
5478
|
+
if (effect) out.push(effect);
|
|
4949
5479
|
}
|
|
4950
|
-
return
|
|
5480
|
+
return out;
|
|
4951
5481
|
}
|
|
4952
5482
|
/**
|
|
4953
5483
|
* The `p:blipFill/a:blip` this picture draws, or `null` when it has none. Three getters and
|
|
@@ -4983,7 +5513,7 @@ var Picture = class extends Shape {
|
|
|
4983
5513
|
*
|
|
4984
5514
|
* `fit` controls the picture's `a:srcRect` crop against its current frame
|
|
4985
5515
|
* extent (`a:xfrm/a:ext`):
|
|
4986
|
-
* - omitted (default): geometry and crop are left untouched
|
|
5516
|
+
* - omitted (default): geometry and crop are left untouched; the caller owns
|
|
4987
5517
|
* sizing. Note an inherited `a:srcRect` was tuned to the *previous* image's
|
|
4988
5518
|
* aspect ratio, so swapping in an image of a different ratio reuses a crop
|
|
4989
5519
|
* that no longer fits and the result looks stretched; pass `fit` to refit.
|
|
@@ -5050,8 +5580,7 @@ var Picture = class extends Shape {
|
|
|
5050
5580
|
return getOrAddChild(getOrAddChild(this.element, "p:blipFill", PIC_BLIPFILL_AFTER), "a:blip", BLIPFILL_BLIP_AFTER);
|
|
5051
5581
|
}
|
|
5052
5582
|
};
|
|
5053
|
-
|
|
5054
|
-
//#region src/read/api/shapes/connector.ts
|
|
5583
|
+
// src/read/api/shapes/connector.ts
|
|
5055
5584
|
/**
|
|
5056
5585
|
* A connector / line (`p:cxnSp`).
|
|
5057
5586
|
*/
|
|
@@ -5090,22 +5619,21 @@ var Connector = class extends Shape {
|
|
|
5090
5619
|
};
|
|
5091
5620
|
}
|
|
5092
5621
|
};
|
|
5093
|
-
|
|
5094
|
-
//#region src/read/oxml/point-cache.ts
|
|
5622
|
+
// src/read/oxml/point-cache.ts
|
|
5095
5623
|
/**
|
|
5096
5624
|
* The one place a chart point cache is turned into an idx-ordered array.
|
|
5097
5625
|
*
|
|
5098
5626
|
* Both chart families cache their category/value data the same way: a run of indexed point
|
|
5099
5627
|
* elements plus a declared count, which the reader widens back into a dense array so
|
|
5100
5628
|
* `series.values[3]` means the point at `idx="3"` and a gap reads as `null`. Classic `c:`
|
|
5101
|
-
* charts and 2016 `cx:` charts spell it differently
|
|
5102
|
-
* against `cx:lvl/@ptCount` and the point's own text
|
|
5629
|
+
* charts and 2016 `cx:` charts spell it differently (`c:ptCount/@val` and a `c:v` child
|
|
5630
|
+
* against `cx:lvl/@ptCount` and the point's own text), and nothing else about the two decodes
|
|
5103
5631
|
* differs, so the shape parameterizes over exactly those two differences.
|
|
5104
5632
|
*
|
|
5105
5633
|
* **Why neither number out of the file is trusted.** `@ptCount` is `xsd:unsignedInt`, so
|
|
5106
5634
|
* `4294967295` is schema-valid, and `new Array(n).fill(null)` at that size is not a slow path
|
|
5107
5635
|
* but a process kill: V8 answers `FATAL ERROR: invalid table size` and the host dies with no
|
|
5108
|
-
* exception to catch. That was reachable through the public read API
|
|
5636
|
+
* exception to catch. That was reachable through the public read API: `Presentation.load()`
|
|
5109
5637
|
* on a deck with an edited `c:ptCount` returned normally and the first `chart.series` access
|
|
5110
5638
|
* killed the process. A single `<c:pt idx="900000000"/>` does the same thing on its own.
|
|
5111
5639
|
* Reading a deck is the one part of this library whose input comes from somewhere other than
|
|
@@ -5113,8 +5641,8 @@ var Connector = class extends Shape {
|
|
|
5113
5641
|
*
|
|
5114
5642
|
* The array is sized by the points that are really there, not by the declared count. The cost
|
|
5115
5643
|
* of that is a *trailing* gap: a cache declaring four points but carrying three (the fourth
|
|
5116
|
-
* cell blank) reads three long rather than four. No value is lost
|
|
5117
|
-
* ones that would have been `null`
|
|
5644
|
+
* cell blank) reads three long rather than four. No value is lost (the dropped slots are the
|
|
5645
|
+
* ones that would have been `null`), and the disagreement is warned about either way.
|
|
5118
5646
|
*/
|
|
5119
5647
|
/**
|
|
5120
5648
|
* The highest point index this reader will honour.
|
|
@@ -5123,7 +5651,7 @@ var Connector = class extends Shape {
|
|
|
5123
5651
|
* a point index at or above that cannot describe data any producer could have charted. Using
|
|
5124
5652
|
* the sheet bound rather than a round number keeps the ceiling something a reader can check:
|
|
5125
5653
|
* below it every real cache fits, above it the file is wrong by construction. It also caps the
|
|
5126
|
-
* worst-case allocation at about 8 MB of `null
|
|
5654
|
+
* worst-case allocation at about 8 MB of `null`, survivable and reported, instead of an
|
|
5127
5655
|
* unbounded one that takes the process with it.
|
|
5128
5656
|
*/
|
|
5129
5657
|
const MAX_POINT_INDEX = 1048576;
|
|
@@ -5163,13 +5691,38 @@ function readIndexedPoints(pts, declaredCount, valueOf, label) {
|
|
|
5163
5691
|
}
|
|
5164
5692
|
return points;
|
|
5165
5693
|
}
|
|
5166
|
-
|
|
5167
|
-
//#region src/read/api/chart.ts
|
|
5694
|
+
// src/read/api/chart.ts
|
|
5168
5695
|
/** A chart graphic-frame's chart, backed by its chart part. */
|
|
5169
5696
|
var Chart = class {
|
|
5170
5697
|
part;
|
|
5171
|
-
|
|
5698
|
+
rels;
|
|
5699
|
+
constructor(part, rels = null) {
|
|
5172
5700
|
this.part = part;
|
|
5701
|
+
this.rels = rels;
|
|
5702
|
+
}
|
|
5703
|
+
/** The chart area's fill and border (`c:chartSpace/c:spPr`), or `null` when it states none. */
|
|
5704
|
+
get chartArea() {
|
|
5705
|
+
const root = this.part.dom.documentElement;
|
|
5706
|
+
const spPr = root ? firstChild(root, "c:spPr") : null;
|
|
5707
|
+
return spPr ? readAreaFormat(spPr, null, this.rels) : null;
|
|
5708
|
+
}
|
|
5709
|
+
/** Whether the chart area's corners are rounded (`c:roundedCorners`), or `null` when unstated. */
|
|
5710
|
+
get roundedCorners() {
|
|
5711
|
+
const root = this.part.dom.documentElement;
|
|
5712
|
+
const element = root ? firstChild(root, "c:roundedCorners") : null;
|
|
5713
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
5714
|
+
}
|
|
5715
|
+
/** The plot area's fill, border and manual layout, or `null` when the chart has no plot area. */
|
|
5716
|
+
get plotArea() {
|
|
5717
|
+
const plotArea = this.#plotArea();
|
|
5718
|
+
if (!plotArea) return null;
|
|
5719
|
+
return readAreaFormat(firstChild(plotArea, "c:spPr"), firstChild(plotArea, "c:layout"), this.rels);
|
|
5720
|
+
}
|
|
5721
|
+
/** The title's manual layout (`c:title/c:layout`), or `null` when it is placed automatically. */
|
|
5722
|
+
get titleLayout() {
|
|
5723
|
+
const chart = this.#chart();
|
|
5724
|
+
const title = chart && firstChild(chart, "c:title");
|
|
5725
|
+
return readManualLayout(title && firstChild(title, "c:layout"));
|
|
5173
5726
|
}
|
|
5174
5727
|
/** Partname of the chart part. */
|
|
5175
5728
|
get partName() {
|
|
@@ -5224,9 +5777,28 @@ var Chart = class {
|
|
|
5224
5777
|
const overlay = firstChild(legend, "c:overlay");
|
|
5225
5778
|
return {
|
|
5226
5779
|
position: pos ? attr(pos, "val") : null,
|
|
5227
|
-
overlay: overlay ? boolValue(attr(overlay, "val")) : null
|
|
5780
|
+
overlay: overlay ? boolValue(attr(overlay, "val")) : null,
|
|
5781
|
+
textStyle: readTextStyle(firstChild(legend, "c:txPr")),
|
|
5782
|
+
layout: readManualLayout(firstChild(legend, "c:layout"))
|
|
5228
5783
|
};
|
|
5229
5784
|
}
|
|
5785
|
+
/** The chart title's formatting (`c:title/c:tx/c:rich`), or `null` when the chart has no rich title. */
|
|
5786
|
+
get titleStyle() {
|
|
5787
|
+
const chart = this.#chart();
|
|
5788
|
+
return readTitleStyle(chart && firstChild(chart, "c:title"));
|
|
5789
|
+
}
|
|
5790
|
+
/**
|
|
5791
|
+
* The language the chart's text is tagged with: `c:lang`, which PowerPoint writes, else the first
|
|
5792
|
+
* paragraph end's `@lang` in the part, where `addChart` writes its `lang`. `null` when neither says.
|
|
5793
|
+
*/
|
|
5794
|
+
get language() {
|
|
5795
|
+
const root = this.part.dom.documentElement;
|
|
5796
|
+
if (!root) return null;
|
|
5797
|
+
const lang = firstChild(root, "c:lang");
|
|
5798
|
+
if (lang) return attr(lang, "val");
|
|
5799
|
+
const end = descendantsByTag(root, OOXML_NS.a, "endParaRPr").find((element) => attr(element, "lang"));
|
|
5800
|
+
return end ? attr(end, "lang") : null;
|
|
5801
|
+
}
|
|
5230
5802
|
/**
|
|
5231
5803
|
* The chart-level data-label settings, read from the first plot group's
|
|
5232
5804
|
* aggregate `c:dLbls` (the group-wide block after the series). `null` when the
|
|
@@ -5241,6 +5813,72 @@ var Chart = class {
|
|
|
5241
5813
|
const group = this.#chartGroups()[0];
|
|
5242
5814
|
return readDataLabels(group ? firstChild(group, "c:dLbls") : null);
|
|
5243
5815
|
}
|
|
5816
|
+
/** The data table under the plot (`c:plotArea/c:dTable`), or `null` when the chart draws none. */
|
|
5817
|
+
get dataTable() {
|
|
5818
|
+
const plotArea = this.#plotArea();
|
|
5819
|
+
const table = plotArea && firstChild(plotArea, "c:dTable");
|
|
5820
|
+
if (!table) return null;
|
|
5821
|
+
const flag = (qname) => {
|
|
5822
|
+
const element = firstChild(table, qname);
|
|
5823
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
5824
|
+
};
|
|
5825
|
+
return {
|
|
5826
|
+
showHorizontalBorder: flag("c:showHorzBorder"),
|
|
5827
|
+
showVerticalBorder: flag("c:showVertBorder"),
|
|
5828
|
+
showOutline: flag("c:showOutline"),
|
|
5829
|
+
showKeys: flag("c:showKeys"),
|
|
5830
|
+
fontSizePt: readTextSizePt(firstChild(table, "c:txPr"))
|
|
5831
|
+
};
|
|
5832
|
+
}
|
|
5833
|
+
/**
|
|
5834
|
+
* The key/value annotations `addChart`'s `metadata` option writes: the pptx-ts extension on
|
|
5835
|
+
* `c:chartSpace/c:extLst` (uri `{094A432E-1F6C-499B-95B8-B57DC9536949}`). `null` when the chart
|
|
5836
|
+
* carries no such extension; an extension with no item reads as an empty map.
|
|
5837
|
+
*/
|
|
5838
|
+
get metadata() {
|
|
5839
|
+
const root = this.part.dom.documentElement;
|
|
5840
|
+
const extLst = root ? firstChild(root, "c:extLst") : null;
|
|
5841
|
+
const ext = extLst ? getElements(extLst, "c:ext").find((element) => attr(element, "uri") === CHART_METADATA_EXT_URI) : void 0;
|
|
5842
|
+
const payload = ext ? childElements(ext).find((element) => element.namespaceURI === "http://ts-pptx.com/schema/chart/metadata" && element.localName === "metadata") : void 0;
|
|
5843
|
+
if (!payload) return null;
|
|
5844
|
+
const out = {};
|
|
5845
|
+
for (const item of childElements(payload)) {
|
|
5846
|
+
if (item.namespaceURI !== "http://ts-pptx.com/schema/chart/metadata" || item.localName !== "item") continue;
|
|
5847
|
+
const key = attr(item, "key");
|
|
5848
|
+
if (key) out[key] = attr(item, "value") ?? "";
|
|
5849
|
+
}
|
|
5850
|
+
return out;
|
|
5851
|
+
}
|
|
5852
|
+
/**
|
|
5853
|
+
* The plot groups (`c:barChart`, `c:lineChart`, …) of the plot area, in document order, each
|
|
5854
|
+
* with what it sets on all its series and the series it plots. A combo chart has several.
|
|
5855
|
+
*/
|
|
5856
|
+
get plotGroups() {
|
|
5857
|
+
return this.#chartGroups().map((group) => new ChartPlotGroup(group, this.part));
|
|
5858
|
+
}
|
|
5859
|
+
/** The 3-D view (`c:chart/c:view3D`), or `null` when the chart states none. */
|
|
5860
|
+
get view3D() {
|
|
5861
|
+
const chart = this.#chart();
|
|
5862
|
+
const view = chart && firstChild(chart, "c:view3D");
|
|
5863
|
+
if (!view) return null;
|
|
5864
|
+
const number = (qname) => {
|
|
5865
|
+
const element = firstChild(view, qname);
|
|
5866
|
+
return element ? numberValue(attr(element, "val")) : null;
|
|
5867
|
+
};
|
|
5868
|
+
const rAngAx = firstChild(view, "c:rAngAx");
|
|
5869
|
+
return {
|
|
5870
|
+
rotX: number("c:rotX"),
|
|
5871
|
+
rotY: number("c:rotY"),
|
|
5872
|
+
perspective: number("c:perspective"),
|
|
5873
|
+
rightAngleAxes: rAngAx ? boolValue(attr(rAngAx, "val")) : null
|
|
5874
|
+
};
|
|
5875
|
+
}
|
|
5876
|
+
/** How blank cells plot (`c:chart/c:dispBlanksAs`): `gap`, `zero` or `span`, or `null` when unstated. */
|
|
5877
|
+
get displayBlanksAs() {
|
|
5878
|
+
const chart = this.#chart();
|
|
5879
|
+
const element = chart && firstChild(chart, "c:dispBlanksAs");
|
|
5880
|
+
return element ? attr(element, "val") : null;
|
|
5881
|
+
}
|
|
5244
5882
|
/** The data series (`c:ser`) across all chart groups, in document order. */
|
|
5245
5883
|
get series() {
|
|
5246
5884
|
return this.#chartGroups().flatMap((group) => getElements(group, "c:ser")).map((ser) => new ChartSeries(ser, this.part));
|
|
@@ -5288,6 +5926,106 @@ var Chart = class {
|
|
|
5288
5926
|
}
|
|
5289
5927
|
};
|
|
5290
5928
|
/**
|
|
5929
|
+
* One plot group of a chart's plot area (`c:barChart`, `c:lineChart`, `c:pieChart`, …): the series
|
|
5930
|
+
* it plots and what it sets on all of them. Each accessor reads `null` (or `false` for an element
|
|
5931
|
+
* that is either there or not) where the group does not state it, including on a plot type whose
|
|
5932
|
+
* schema has no such element.
|
|
5933
|
+
*/
|
|
5934
|
+
var ChartPlotGroup = class {
|
|
5935
|
+
group;
|
|
5936
|
+
part;
|
|
5937
|
+
constructor(group, part) {
|
|
5938
|
+
this.group = group;
|
|
5939
|
+
this.part = part;
|
|
5940
|
+
}
|
|
5941
|
+
/** The plot type, the element's local name less `Chart`: `bar`, `line`, `bar3D`, `doughnut`, … */
|
|
5942
|
+
get type() {
|
|
5943
|
+
return (this.group.localName ?? "").replace(/Chart$/, "");
|
|
5944
|
+
}
|
|
5945
|
+
/** The series this group plots (`c:ser`), in document order. */
|
|
5946
|
+
get series() {
|
|
5947
|
+
return getElements(this.group, "c:ser").map((ser) => new ChartSeries(ser, this.part));
|
|
5948
|
+
}
|
|
5949
|
+
/**
|
|
5950
|
+
* The ids of the axes this group plots against (`c:axId`), as {@link ChartAxis.id} reads them: a
|
|
5951
|
+
* combo's group on the secondary axes names the secondary pair. Empty for a pie or a doughnut.
|
|
5952
|
+
*/
|
|
5953
|
+
get axisIds() {
|
|
5954
|
+
return childElements(this.group).filter((child) => child.localName === "axId").map((axId) => numberValue(attr(axId, "val"))).filter((id) => id !== null);
|
|
5955
|
+
}
|
|
5956
|
+
/** A bar group's direction (`c:barDir`): `bar` (horizontal) or `col` (vertical). */
|
|
5957
|
+
get barDirection() {
|
|
5958
|
+
return this.#value("c:barDir");
|
|
5959
|
+
}
|
|
5960
|
+
/** How the series stack (`c:grouping`): `clustered`, `stacked`, `percentStacked` or `standard`. */
|
|
5961
|
+
get grouping() {
|
|
5962
|
+
return this.#value("c:grouping");
|
|
5963
|
+
}
|
|
5964
|
+
/** The gap between bar clusters as a percentage of a bar's width (`c:gapWidth`). */
|
|
5965
|
+
get gapWidthPct() {
|
|
5966
|
+
return this.#number("c:gapWidth");
|
|
5967
|
+
}
|
|
5968
|
+
/** How far bars in a cluster overlap, -100 to 100 percent (`c:overlap`). */
|
|
5969
|
+
get overlapPct() {
|
|
5970
|
+
return this.#number("c:overlap");
|
|
5971
|
+
}
|
|
5972
|
+
/** Whether stacked bars are joined by series lines (`c:serLines`). */
|
|
5973
|
+
get seriesLines() {
|
|
5974
|
+
return firstChild(this.group, "c:serLines") !== null;
|
|
5975
|
+
}
|
|
5976
|
+
/** The series lines' stroke (`c:serLines/c:spPr/a:ln`), or `null` when they state none. */
|
|
5977
|
+
get seriesLinesLine() {
|
|
5978
|
+
return spPrLine(firstChild(this.group, "c:serLines"));
|
|
5979
|
+
}
|
|
5980
|
+
/** The depth gap of a 3-D bar group, as a percentage (`c:gapDepth`). */
|
|
5981
|
+
get gapDepthPct() {
|
|
5982
|
+
return this.#number("c:gapDepth");
|
|
5983
|
+
}
|
|
5984
|
+
/** The group-wide 3-D bar shape (`c:shape`); a series can state its own ({@link ChartSeries.shape}). */
|
|
5985
|
+
get shape() {
|
|
5986
|
+
return this.#value("c:shape");
|
|
5987
|
+
}
|
|
5988
|
+
/** The angle of a pie's first slice, 0 to 360 degrees (`c:firstSliceAng`). */
|
|
5989
|
+
get firstSliceAngle() {
|
|
5990
|
+
return this.#number("c:firstSliceAng");
|
|
5991
|
+
}
|
|
5992
|
+
/** A doughnut's hole, 10 to 90 percent of its size (`c:holeSize`). */
|
|
5993
|
+
get holeSizePct() {
|
|
5994
|
+
return this.#number("c:holeSize");
|
|
5995
|
+
}
|
|
5996
|
+
/** A radar's style (`c:radarStyle`): `standard`, `marker` or `filled`. */
|
|
5997
|
+
get radarStyle() {
|
|
5998
|
+
return this.#value("c:radarStyle");
|
|
5999
|
+
}
|
|
6000
|
+
/** Whether a surface is drawn as a wireframe (`c:wireframe`). */
|
|
6001
|
+
get wireframe() {
|
|
6002
|
+
const element = firstChild(this.group, "c:wireframe");
|
|
6003
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
6004
|
+
}
|
|
6005
|
+
/** Whether a line or stock group draws up/down bars (`c:upDownBars`). */
|
|
6006
|
+
get upDownBars() {
|
|
6007
|
+
return firstChild(this.group, "c:upDownBars") !== null;
|
|
6008
|
+
}
|
|
6009
|
+
/** Escape hatch: the underlying plot-group element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
|
|
6010
|
+
get element_() {
|
|
6011
|
+
return this.group;
|
|
6012
|
+
}
|
|
6013
|
+
/** Mark the owning chart part dirty so `save()` reserializes it. Call after mutating {@link element_}. */
|
|
6014
|
+
markDirty() {
|
|
6015
|
+
this.part.markDirty();
|
|
6016
|
+
}
|
|
6017
|
+
/** The `@val` of a named child, or `null`. */
|
|
6018
|
+
#value(qname) {
|
|
6019
|
+
const element = firstChild(this.group, qname);
|
|
6020
|
+
return element ? attr(element, "val") : null;
|
|
6021
|
+
}
|
|
6022
|
+
/** The `@val` of a named child as a number, or `null`. */
|
|
6023
|
+
#number(qname) {
|
|
6024
|
+
const element = firstChild(this.group, qname);
|
|
6025
|
+
return element ? numberValue(attr(element, "val")) : null;
|
|
6026
|
+
}
|
|
6027
|
+
};
|
|
6028
|
+
/**
|
|
5291
6029
|
* One axis (`c:catAx`/`c:valAx`/`c:dateAx`/`c:serAx`) of a chart's plot area.
|
|
5292
6030
|
* All four share the scaling/delete/position/tickmark structure; `kind`
|
|
5293
6031
|
* distinguishes them.
|
|
@@ -5385,6 +6123,81 @@ var ChartAxis = class {
|
|
|
5385
6123
|
const el = firstChild(this.ax, "c:minorUnit");
|
|
5386
6124
|
return el ? numberValue(attr(el, "val")) : null;
|
|
5387
6125
|
}
|
|
6126
|
+
/** The tick labels' text formatting and rotation (`c:txPr`), or `null` when the axis states none. */
|
|
6127
|
+
get labelStyle() {
|
|
6128
|
+
return readTextStyle(firstChild(this.ax, "c:txPr"));
|
|
6129
|
+
}
|
|
6130
|
+
/** The axis title's formatting (`c:title/c:tx/c:rich`), or `null` when it has no rich title. */
|
|
6131
|
+
get titleStyle() {
|
|
6132
|
+
return readTitleStyle(firstChild(this.ax, "c:title"));
|
|
6133
|
+
}
|
|
6134
|
+
/** Where this axis crosses the other one, as a value on it (`c:crossesAt`), or `null`. */
|
|
6135
|
+
get crossesAt() {
|
|
6136
|
+
const element = firstChild(this.ax, "c:crossesAt");
|
|
6137
|
+
return element ? numberValue(attr(element, "val")) : null;
|
|
6138
|
+
}
|
|
6139
|
+
/** Where this axis crosses the other one, as a rule (`c:crosses`: `autoZero`, `max`, `min`), or `null`. */
|
|
6140
|
+
get crosses() {
|
|
6141
|
+
const element = firstChild(this.ax, "c:crosses");
|
|
6142
|
+
return element ? attr(element, "val") : null;
|
|
6143
|
+
}
|
|
6144
|
+
/** Whether a value axis crosses between categories or on them (`c:crossBetween`: `between`, `midCat`). */
|
|
6145
|
+
get crossBetween() {
|
|
6146
|
+
const element = firstChild(this.ax, "c:crossBetween");
|
|
6147
|
+
return element ? attr(element, "val") : null;
|
|
6148
|
+
}
|
|
6149
|
+
/** A value axis' display units (`c:dispUnits`), or `null` when it shows plain values. */
|
|
6150
|
+
get displayUnits() {
|
|
6151
|
+
const units = firstChild(this.ax, "c:dispUnits");
|
|
6152
|
+
if (!units) return null;
|
|
6153
|
+
const builtIn = firstChild(units, "c:builtInUnit");
|
|
6154
|
+
const custom = firstChild(units, "c:custUnit");
|
|
6155
|
+
return {
|
|
6156
|
+
builtIn: builtIn ? attr(builtIn, "val") : null,
|
|
6157
|
+
custom: custom ? numberValue(attr(custom, "val")) : null,
|
|
6158
|
+
showLabel: firstChild(units, "c:dispUnitsLbl") !== null
|
|
6159
|
+
};
|
|
6160
|
+
}
|
|
6161
|
+
/** How many categories pass between two labels (`c:tickLblSkip`), or `null` when automatic. */
|
|
6162
|
+
get labelSkip() {
|
|
6163
|
+
const element = firstChild(this.ax, "c:tickLblSkip");
|
|
6164
|
+
return element ? numberValue(attr(element, "val")) : null;
|
|
6165
|
+
}
|
|
6166
|
+
/** Whether a category axis flattens a multi-level category list (`c:noMultiLvlLbl`), or `null` when unstated. */
|
|
6167
|
+
get noMultiLevelLabels() {
|
|
6168
|
+
const element = firstChild(this.ax, "c:noMultiLvlLbl");
|
|
6169
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
6170
|
+
}
|
|
6171
|
+
/** A date axis' base unit (`c:baseTimeUnit`: `days`, `months`, `years`), or `null`. */
|
|
6172
|
+
get baseTimeUnit() {
|
|
6173
|
+
return this.#value("c:baseTimeUnit");
|
|
6174
|
+
}
|
|
6175
|
+
/** A date axis' major unit scale (`c:majorTimeUnit`), or `null`. */
|
|
6176
|
+
get majorTimeUnit() {
|
|
6177
|
+
return this.#value("c:majorTimeUnit");
|
|
6178
|
+
}
|
|
6179
|
+
/** A date axis' minor unit scale (`c:minorTimeUnit`), or `null`. */
|
|
6180
|
+
get minorTimeUnit() {
|
|
6181
|
+
return this.#value("c:minorTimeUnit");
|
|
6182
|
+
}
|
|
6183
|
+
/** The axis line's own stroke (`c:spPr/a:ln`), or `null` when it states none. */
|
|
6184
|
+
get line() {
|
|
6185
|
+
const spPr = firstChild(this.ax, "c:spPr");
|
|
6186
|
+
const ln = spPr && firstChild(spPr, "a:ln");
|
|
6187
|
+
return ln ? readChartLine(ln) : null;
|
|
6188
|
+
}
|
|
6189
|
+
/** The major gridlines' stroke (`c:majorGridlines/c:spPr/a:ln`), or `null` when they state none. */
|
|
6190
|
+
get majorGridlinesLine() {
|
|
6191
|
+
const gridlines = firstChild(this.ax, "c:majorGridlines");
|
|
6192
|
+
const spPr = gridlines && firstChild(gridlines, "c:spPr");
|
|
6193
|
+
const ln = spPr && firstChild(spPr, "a:ln");
|
|
6194
|
+
return ln ? readChartLine(ln) : null;
|
|
6195
|
+
}
|
|
6196
|
+
/** The `@val` of a named child, or `null`. */
|
|
6197
|
+
#value(qname) {
|
|
6198
|
+
const element = firstChild(this.ax, qname);
|
|
6199
|
+
return element ? attr(element, "val") : null;
|
|
6200
|
+
}
|
|
5388
6201
|
/** Escape hatch: the underlying axis element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
|
|
5389
6202
|
get element_() {
|
|
5390
6203
|
return this.ax;
|
|
@@ -5416,20 +6229,14 @@ var ChartSeries = class {
|
|
|
5416
6229
|
return spPr ? readSolidFill(spPr) : null;
|
|
5417
6230
|
}
|
|
5418
6231
|
/**
|
|
5419
|
-
* Series line/stroke (`c:spPr/a:ln`)
|
|
6232
|
+
* Series line/stroke (`c:spPr/a:ln`): width, dash, colour. `null` when the
|
|
5420
6233
|
* series `c:spPr` has no `a:ln` (bar/area fills carry no line by default).
|
|
5421
6234
|
*/
|
|
5422
6235
|
get line() {
|
|
5423
6236
|
const spPr = firstChild(this.ser, "c:spPr");
|
|
5424
6237
|
const ln = spPr && firstChild(spPr, "a:ln");
|
|
5425
6238
|
if (!ln) return null;
|
|
5426
|
-
|
|
5427
|
-
return {
|
|
5428
|
-
widthPt,
|
|
5429
|
-
dash,
|
|
5430
|
-
colorRef,
|
|
5431
|
-
noFill
|
|
5432
|
-
};
|
|
6239
|
+
return readChartLine(ln);
|
|
5433
6240
|
}
|
|
5434
6241
|
/** Series index (`c:idx/@val`), or `null` if absent. */
|
|
5435
6242
|
get index() {
|
|
@@ -5478,6 +6285,16 @@ var ChartSeries = class {
|
|
|
5478
6285
|
get yValues() {
|
|
5479
6286
|
return this.#numbers("c:yVal");
|
|
5480
6287
|
}
|
|
6288
|
+
/**
|
|
6289
|
+
* The number format the series' values are cached in (`c:numCache/c:formatCode` under `c:val`, or
|
|
6290
|
+
* under `c:yVal` on a scatter or bubble series), or `null` when the cache states none. PowerPoint
|
|
6291
|
+
* paints a source-linked data label and a data table in it.
|
|
6292
|
+
*/
|
|
6293
|
+
get valueFormatCode() {
|
|
6294
|
+
const cache = findCache(firstChild(this.ser, "c:val") ?? firstChild(this.ser, "c:yVal"));
|
|
6295
|
+
const formatCode = cache && firstChild(cache, "c:formatCode");
|
|
6296
|
+
return formatCode ? formatCode.textContent ?? null : null;
|
|
6297
|
+
}
|
|
5481
6298
|
/** Cached bubble sizes of a bubble series (`c:bubbleSize`); non-numeric or missing points are `null`. */
|
|
5482
6299
|
get bubbleSizes() {
|
|
5483
6300
|
return this.#numbers("c:bubbleSize");
|
|
@@ -5510,6 +6327,113 @@ var ChartSeries = class {
|
|
|
5510
6327
|
get dataLabels() {
|
|
5511
6328
|
return readDataLabels(firstChild(this.ser, "c:dLbls"));
|
|
5512
6329
|
}
|
|
6330
|
+
/**
|
|
6331
|
+
* The series' labels that state their own text (`c:ser/c:dLbls/c:dLbl` with `c:tx/c:rich`), in
|
|
6332
|
+
* document order. A label that only formats or deletes its point has no text and is left out.
|
|
6333
|
+
*/
|
|
6334
|
+
get customLabels() {
|
|
6335
|
+
const dLbls = firstChild(this.ser, "c:dLbls");
|
|
6336
|
+
if (!dLbls) return [];
|
|
6337
|
+
const labels = [];
|
|
6338
|
+
for (const dLbl of childElements(dLbls)) {
|
|
6339
|
+
if (dLbl.localName !== "dLbl") continue;
|
|
6340
|
+
const idx = firstChild(dLbl, "c:idx");
|
|
6341
|
+
const index = idx ? numberValue(attr(idx, "val")) : null;
|
|
6342
|
+
const tx = firstChild(dLbl, "c:tx");
|
|
6343
|
+
const rich = tx && firstChild(tx, "c:rich");
|
|
6344
|
+
if (index === null || !rich) continue;
|
|
6345
|
+
const runs = [];
|
|
6346
|
+
for (const paragraph of childElements(rich)) {
|
|
6347
|
+
if (paragraph.localName !== "p") continue;
|
|
6348
|
+
for (const run of childElements(paragraph)) {
|
|
6349
|
+
if (run.localName !== "r" && run.localName !== "fld") continue;
|
|
6350
|
+
const text = firstChild(run, "a:t")?.textContent ?? "";
|
|
6351
|
+
runs.push({
|
|
6352
|
+
text,
|
|
6353
|
+
field: run.localName === "fld" ? attr(run, "type") : null
|
|
6354
|
+
});
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
labels.push({
|
|
6358
|
+
index,
|
|
6359
|
+
runs
|
|
6360
|
+
});
|
|
6361
|
+
}
|
|
6362
|
+
return labels;
|
|
6363
|
+
}
|
|
6364
|
+
/** The series' gradient fill (`c:spPr/a:gradFill`), or `null`. */
|
|
6365
|
+
get gradientFill() {
|
|
6366
|
+
const spPr = firstChild(this.ser, "c:spPr");
|
|
6367
|
+
return spPr ? readGradientFill(spPr, NO_THEME) : null;
|
|
6368
|
+
}
|
|
6369
|
+
/** The series' pattern fill (`c:spPr/a:pattFill`), or `null`. */
|
|
6370
|
+
get patternFill() {
|
|
6371
|
+
const spPr = firstChild(this.ser, "c:spPr");
|
|
6372
|
+
return spPr ? readPatternFill(spPr, NO_THEME) : null;
|
|
6373
|
+
}
|
|
6374
|
+
/** The series' outer shadow (`c:spPr/a:effectLst/a:outerShdw`), or `null`. */
|
|
6375
|
+
get shadow() {
|
|
6376
|
+
const spPr = firstChild(this.ser, "c:spPr");
|
|
6377
|
+
return readOuterShadow(spPr && firstChild(spPr, "a:effectLst"), NO_THEME);
|
|
6378
|
+
}
|
|
6379
|
+
/** The series' inner shadow (`c:spPr/a:effectLst/a:innerShdw`), or `null`. */
|
|
6380
|
+
get innerShadow() {
|
|
6381
|
+
const spPr = firstChild(this.ser, "c:spPr");
|
|
6382
|
+
return readInnerShadow(spPr && firstChild(spPr, "a:effectLst"), NO_THEME);
|
|
6383
|
+
}
|
|
6384
|
+
/** Whether a line or scatter series is smoothed (`c:smooth`), or `null` when unstated. */
|
|
6385
|
+
get smooth() {
|
|
6386
|
+
const element = firstChild(this.ser, "c:smooth");
|
|
6387
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
6388
|
+
}
|
|
6389
|
+
/** The series' data-point marker (`c:marker`), or `null` when it states none. */
|
|
6390
|
+
get marker() {
|
|
6391
|
+
const marker = firstChild(this.ser, "c:marker");
|
|
6392
|
+
if (!marker) return null;
|
|
6393
|
+
const symbol = firstChild(marker, "c:symbol");
|
|
6394
|
+
const size = firstChild(marker, "c:size");
|
|
6395
|
+
const spPr = firstChild(marker, "c:spPr");
|
|
6396
|
+
const ln = spPr && firstChild(spPr, "a:ln");
|
|
6397
|
+
return {
|
|
6398
|
+
symbol: symbol ? attr(symbol, "val") : null,
|
|
6399
|
+
size: size ? numberValue(attr(size, "val")) : null,
|
|
6400
|
+
fill: spPr ? readSolidFill(spPr) : null,
|
|
6401
|
+
line: ln ? readChartLine(ln) : null
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
/**
|
|
6405
|
+
* The points this series formats on its own (`c:dPt`), in document order: a bar's per-point
|
|
6406
|
+
* colours, a pie's slice colours. A `c:dPt` with no `c:idx` is left out.
|
|
6407
|
+
*/
|
|
6408
|
+
get dataPoints() {
|
|
6409
|
+
const points = [];
|
|
6410
|
+
for (const dPt of childElements(this.ser)) {
|
|
6411
|
+
if (dPt.localName !== "dPt") continue;
|
|
6412
|
+
const idx = firstChild(dPt, "c:idx");
|
|
6413
|
+
const index = idx ? numberValue(attr(idx, "val")) : null;
|
|
6414
|
+
if (index === null) continue;
|
|
6415
|
+
const invert = firstChild(dPt, "c:invertIfNegative");
|
|
6416
|
+
const spPr = firstChild(dPt, "c:spPr");
|
|
6417
|
+
points.push({
|
|
6418
|
+
index,
|
|
6419
|
+
invertIfNegative: invert ? boolValue(attr(invert, "val")) : null,
|
|
6420
|
+
fill: spPr ? readSolidFill(spPr) : null,
|
|
6421
|
+
patternFill: spPr ? readPatternFill(spPr, NO_THEME) : null,
|
|
6422
|
+
line: spPrLine(dPt)
|
|
6423
|
+
});
|
|
6424
|
+
}
|
|
6425
|
+
return points;
|
|
6426
|
+
}
|
|
6427
|
+
/** Whether a bar series paints a negative bar inverted (`c:invertIfNegative`), or `null` when unstated. */
|
|
6428
|
+
get invertIfNegative() {
|
|
6429
|
+
const element = firstChild(this.ser, "c:invertIfNegative");
|
|
6430
|
+
return element ? boolValue(attr(element, "val")) : null;
|
|
6431
|
+
}
|
|
6432
|
+
/** A 3-D bar series' own shape (`c:shape`: `box`, `cylinder`, `cone`, …), or `null` when unstated. */
|
|
6433
|
+
get shape() {
|
|
6434
|
+
const element = firstChild(this.ser, "c:shape");
|
|
6435
|
+
return element ? attr(element, "val") : null;
|
|
6436
|
+
}
|
|
5513
6437
|
/** Escape hatch: the underlying `c:ser` element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
|
|
5514
6438
|
get element_() {
|
|
5515
6439
|
return this.ser;
|
|
@@ -5524,6 +6448,34 @@ var ChartSeries = class {
|
|
|
5524
6448
|
return readPoints(container && findCache(container)).map(numberValue);
|
|
5525
6449
|
}
|
|
5526
6450
|
};
|
|
6451
|
+
/** The size, in points, a chart text block (`c:txPr`) sets on its first paragraph's `a:defRPr`. */
|
|
6452
|
+
function readTextSizePt(txPr) {
|
|
6453
|
+
const paragraph = txPr && firstChild(txPr, "a:p");
|
|
6454
|
+
const pPr = paragraph && firstChild(paragraph, "a:pPr");
|
|
6455
|
+
const defRPr = pPr && firstChild(pPr, "a:defRPr");
|
|
6456
|
+
const size = defRPr ? numberValue(attr(defRPr, "sz")) : null;
|
|
6457
|
+
return size === null ? null : size / 100;
|
|
6458
|
+
}
|
|
6459
|
+
/**
|
|
6460
|
+
* A chart stroke (`a:ln`): {@link readLineBasics}' width, dash, colour and no-fill, then the cap and
|
|
6461
|
+
* the colour's `a:alpha`, which a chart part read without a theme leaves unresolved otherwise.
|
|
6462
|
+
*/
|
|
6463
|
+
function readChartLine(ln) {
|
|
6464
|
+
const { widthPt, dash, colorRef, noFill } = readLineBasics(ln, null);
|
|
6465
|
+
const solidFill = firstChild(ln, "a:solidFill");
|
|
6466
|
+
const color = solidFill && firstChildElement(solidFill);
|
|
6467
|
+
const alpha = color && firstChild(color, "a:alpha");
|
|
6468
|
+
const alphaValue = alpha ? numberValue(attr(alpha, "val")) : null;
|
|
6469
|
+
return {
|
|
6470
|
+
widthPt,
|
|
6471
|
+
dash,
|
|
6472
|
+
colorRef,
|
|
6473
|
+
noFill,
|
|
6474
|
+
cap: attr(ln, "cap") ?? null,
|
|
6475
|
+
alpha: alphaValue === null ? null : alphaValue / 1e5,
|
|
6476
|
+
colorTransformed: color ? childElements(color).some((child) => child.localName !== "alpha") : false
|
|
6477
|
+
};
|
|
6478
|
+
}
|
|
5527
6479
|
/** Read a `c:dLbls` block's show flags, position and number format, or `null` when there is no block. */
|
|
5528
6480
|
function readDataLabels(dLbls) {
|
|
5529
6481
|
if (!dLbls) return null;
|
|
@@ -5541,9 +6493,59 @@ function readDataLabels(dLbls) {
|
|
|
5541
6493
|
showBubbleSize: flag("c:showBubbleSize"),
|
|
5542
6494
|
showLeaderLines: flag("c:showLeaderLines"),
|
|
5543
6495
|
position: pos ? attr(pos, "val") : null,
|
|
5544
|
-
numberFormat: readNumberFormat(dLbls)
|
|
6496
|
+
numberFormat: readNumberFormat(dLbls),
|
|
6497
|
+
textStyle: readTextStyle(firstChild(dLbls, "c:txPr")),
|
|
6498
|
+
fill: spPrFill(dLbls),
|
|
6499
|
+
leaderLines: spPrLine(firstChild(dLbls, "c:leaderLines"))
|
|
5545
6500
|
};
|
|
5546
6501
|
}
|
|
6502
|
+
/** The size, in 1/60000 of a degree, of the `@rot` PowerPoint writes for an upright default. */
|
|
6503
|
+
const MAX_TEXT_ROTATION = 54e5;
|
|
6504
|
+
/** Read a chart text block, a `c:txPr` or a title's `c:rich`; see {@link ChartTextStyle}. */
|
|
6505
|
+
function readTextStyle(block) {
|
|
6506
|
+
if (!block) return null;
|
|
6507
|
+
const bodyPr = firstChild(block, "a:bodyPr");
|
|
6508
|
+
const paragraph = firstChild(block, "a:p");
|
|
6509
|
+
const pPr = paragraph && firstChild(paragraph, "a:pPr");
|
|
6510
|
+
const defRPr = pPr && firstChild(pPr, "a:defRPr");
|
|
6511
|
+
const run = paragraph && firstChild(paragraph, "a:r");
|
|
6512
|
+
const props = [run && firstChild(run, "a:rPr"), defRPr].filter((element) => element !== null);
|
|
6513
|
+
const first = (name) => {
|
|
6514
|
+
for (const element of props) {
|
|
6515
|
+
const value = attr(element, name);
|
|
6516
|
+
if (value !== null) return value;
|
|
6517
|
+
}
|
|
6518
|
+
return null;
|
|
6519
|
+
};
|
|
6520
|
+
const child = (qname) => {
|
|
6521
|
+
for (const element of props) {
|
|
6522
|
+
const found = firstChild(element, qname);
|
|
6523
|
+
if (found) return found;
|
|
6524
|
+
}
|
|
6525
|
+
return null;
|
|
6526
|
+
};
|
|
6527
|
+
const solidFill = child("a:solidFill");
|
|
6528
|
+
const colorElement = solidFill && firstChildElement(solidFill);
|
|
6529
|
+
const latin = child("a:latin");
|
|
6530
|
+
const size = numberValue(first("sz"));
|
|
6531
|
+
const rot = bodyPr ? numberValue(attr(bodyPr, "rot")) : null;
|
|
6532
|
+
return {
|
|
6533
|
+
sizePt: size === null ? null : size / 100,
|
|
6534
|
+
bold: boolValue(first("b")),
|
|
6535
|
+
italic: boolValue(first("i")),
|
|
6536
|
+
underline: first("u"),
|
|
6537
|
+
color: colorElement ? readColorRef(colorElement, null) : null,
|
|
6538
|
+
colorTransformed: colorElement ? childElements(colorElement).some((transform) => transform.localName !== "alpha") : false,
|
|
6539
|
+
fontName: latin ? attr(latin, "typeface") : null,
|
|
6540
|
+
rotationDeg: rot === null || Math.abs(rot) > MAX_TEXT_ROTATION ? null : rot / 6e4,
|
|
6541
|
+
align: pPr ? attr(pPr, "algn") : null
|
|
6542
|
+
};
|
|
6543
|
+
}
|
|
6544
|
+
/** A `c:title`'s formatting, off its `c:tx/c:rich`. */
|
|
6545
|
+
function readTitleStyle(title) {
|
|
6546
|
+
const tx = title && firstChild(title, "c:tx");
|
|
6547
|
+
return readTextStyle(tx && firstChild(tx, "c:rich"));
|
|
6548
|
+
}
|
|
5547
6549
|
/**
|
|
5548
6550
|
* Read a `c:multiLvlStrCache` into its levels, in document order.
|
|
5549
6551
|
*
|
|
@@ -5590,11 +6592,79 @@ function readSolidFill(container) {
|
|
|
5590
6592
|
const solidFill = firstChild(container, "a:solidFill");
|
|
5591
6593
|
const noFill = !!firstChild(container, "a:noFill");
|
|
5592
6594
|
if (!solidFill && !noFill) return null;
|
|
6595
|
+
const color = solidFill ? firstChildElement(solidFill) : null;
|
|
6596
|
+
return {
|
|
6597
|
+
colorRef: readColorRef(color, null),
|
|
6598
|
+
noFill,
|
|
6599
|
+
alpha: colorAlpha(color)
|
|
6600
|
+
};
|
|
6601
|
+
}
|
|
6602
|
+
/** The `a:alpha` of a colour element, 0 to 1, or `null`. */
|
|
6603
|
+
function colorAlpha(color) {
|
|
6604
|
+
const alpha = color && firstChild(color, "a:alpha");
|
|
6605
|
+
const value = alpha ? numberValue(attr(alpha, "val")) : null;
|
|
6606
|
+
return value === null ? null : value / 1e5;
|
|
6607
|
+
}
|
|
6608
|
+
/**
|
|
6609
|
+
* The colour context a chart part's fills resolve against: none. A literal resolves with its
|
|
6610
|
+
* transforms; a scheme colour does not, since the part is read without its slide's theme.
|
|
6611
|
+
*/
|
|
6612
|
+
const NO_THEME = {
|
|
6613
|
+
clrMap: /* @__PURE__ */ new Map(),
|
|
6614
|
+
clrScheme: /* @__PURE__ */ new Map()
|
|
6615
|
+
};
|
|
6616
|
+
/** The solid fill of an element's `c:spPr`, or `null`. */
|
|
6617
|
+
function spPrFill(parent) {
|
|
6618
|
+
const spPr = parent && firstChild(parent, "c:spPr");
|
|
6619
|
+
return spPr ? readSolidFill(spPr) : null;
|
|
6620
|
+
}
|
|
6621
|
+
/** The stroke of an element's `c:spPr/a:ln`, or `null`. */
|
|
6622
|
+
function spPrLine(parent) {
|
|
6623
|
+
const spPr = parent && firstChild(parent, "c:spPr");
|
|
6624
|
+
const ln = spPr && firstChild(spPr, "a:ln");
|
|
6625
|
+
return ln ? readChartLine(ln) : null;
|
|
6626
|
+
}
|
|
6627
|
+
/** A `c:layout`'s manual layout, or `null` when it has none. */
|
|
6628
|
+
function readManualLayout(layout) {
|
|
6629
|
+
const manual = layout && firstChild(layout, "c:manualLayout");
|
|
6630
|
+
if (!manual) return null;
|
|
6631
|
+
const value = (qname) => {
|
|
6632
|
+
const element = firstChild(manual, qname);
|
|
6633
|
+
return element ? attr(element, "val") : null;
|
|
6634
|
+
};
|
|
6635
|
+
const number = (qname) => numberValue(value(qname));
|
|
6636
|
+
return {
|
|
6637
|
+
target: value("c:layoutTarget"),
|
|
6638
|
+
xMode: value("c:xMode"),
|
|
6639
|
+
yMode: value("c:yMode"),
|
|
6640
|
+
x: number("c:x"),
|
|
6641
|
+
y: number("c:y"),
|
|
6642
|
+
w: number("c:w"),
|
|
6643
|
+
h: number("c:h")
|
|
6644
|
+
};
|
|
6645
|
+
}
|
|
6646
|
+
/** A chart surface's `c:spPr` and `c:layout` as a {@link ChartAreaFormat}. */
|
|
6647
|
+
function readAreaFormat(spPr, layout, rels) {
|
|
6648
|
+
const solidFill = spPr && firstChild(spPr, "a:solidFill");
|
|
6649
|
+
const color = solidFill && firstChildElement(solidFill);
|
|
6650
|
+
const ln = spPr && firstChild(spPr, "a:ln");
|
|
5593
6651
|
return {
|
|
5594
|
-
|
|
5595
|
-
|
|
6652
|
+
fillNoFill: spPr ? firstChild(spPr, "a:noFill") !== null : false,
|
|
6653
|
+
hasOwnFill: hasFillChoice(spPr),
|
|
6654
|
+
fillColor: colorValueOf(color, "srgbClr"),
|
|
6655
|
+
fillSchemeColor: colorValueOf(color, "schemeClr"),
|
|
6656
|
+
resolvedFill: color && color.localName === "srgbClr" ? resolveColorElement(color, NO_THEME) : null,
|
|
6657
|
+
gradientFill: spPr ? readGradientFill(spPr, NO_THEME) : null,
|
|
6658
|
+
patternFill: spPr ? readPatternFill(spPr, NO_THEME) : null,
|
|
6659
|
+
pictureFill: spPr ? readPictureFill(spPr, rels) : null,
|
|
6660
|
+
border: ln ? readChartLine(ln) : null,
|
|
6661
|
+
layout: readManualLayout(layout)
|
|
5596
6662
|
};
|
|
5597
6663
|
}
|
|
6664
|
+
/** The `@val` of a colour element of the given local name, or `null`. */
|
|
6665
|
+
function colorValueOf(color, localName) {
|
|
6666
|
+
return color && color.localName === localName ? attr(color, "val") : null;
|
|
6667
|
+
}
|
|
5598
6668
|
/** Resolve a `c:cat`/`c:val`/`c:tx` container to its cache element (`c:numCache`/`c:strCache`/literal). */
|
|
5599
6669
|
function findCache(container) {
|
|
5600
6670
|
if (!container) return null;
|
|
@@ -5633,8 +6703,7 @@ function readPoints(cache) {
|
|
|
5633
6703
|
const ptCount = firstChild(cache, "c:ptCount");
|
|
5634
6704
|
return readIndexedPoints(getElements(cache, "c:pt"), ptCount ? numberValue(attr(ptCount, "val")) : null, (pt) => firstChild(pt, "c:v")?.textContent ?? null, "c:ptCount");
|
|
5635
6705
|
}
|
|
5636
|
-
|
|
5637
|
-
//#region src/read/api/chartex.ts
|
|
6706
|
+
// src/read/api/chartex.ts
|
|
5638
6707
|
/** A chartEx graphic-frame's chart, backed by its `cx:chartSpace` part. */
|
|
5639
6708
|
var ChartEx = class {
|
|
5640
6709
|
part;
|
|
@@ -5646,7 +6715,7 @@ var ChartEx = class {
|
|
|
5646
6715
|
return this.part.partName;
|
|
5647
6716
|
}
|
|
5648
6717
|
/**
|
|
5649
|
-
* The `cx:series/@layoutId` tokens in plot-area document order
|
|
6718
|
+
* The `cx:series/@layoutId` tokens in plot-area document order: the raw chartEx
|
|
5650
6719
|
* layout names (`waterfall`, `funnel`, `treemap`, `sunburst`, `boxWhisker`,
|
|
5651
6720
|
* `regionMap`, or `clusteredColumn`/`paretoLine` for histogram & pareto). This is
|
|
5652
6721
|
* the OOXML truth: a histogram and a pareto both surface as `clusteredColumn`, so
|
|
@@ -5775,7 +6844,7 @@ var ChartExSeries = class {
|
|
|
5775
6844
|
return readLevelPoints(numDim && firstChild(numDim, "cx:lvl")).map(numberValue);
|
|
5776
6845
|
}
|
|
5777
6846
|
/**
|
|
5778
|
-
* Cached category labels (`cx:strDim`'s leaf `cx:lvl
|
|
6847
|
+
* Cached category labels (`cx:strDim`'s leaf `cx:lvl`, the first level, which
|
|
5779
6848
|
* the writer emits leaf-first). Empty when the data block carries no `cx:strDim`.
|
|
5780
6849
|
*/
|
|
5781
6850
|
get categories() {
|
|
@@ -5866,16 +6935,15 @@ function readLevelPoints(lvl) {
|
|
|
5866
6935
|
if (!lvl) return [];
|
|
5867
6936
|
return readIndexedPoints(getElements(lvl, "cx:pt"), numberValue(attr(lvl, "ptCount")), (pt) => pt.textContent ?? null, "cx:lvl/@ptCount");
|
|
5868
6937
|
}
|
|
5869
|
-
|
|
5870
|
-
//#region src/read/api/diagram.ts
|
|
6938
|
+
// src/read/api/diagram.ts
|
|
5871
6939
|
/**
|
|
5872
6940
|
* Read proxies for a SmartArt diagram (`dgm:dataModel`) hosted in a `p:graphicFrame`.
|
|
5873
6941
|
*
|
|
5874
6942
|
* SmartArt is a four-part construct, and the frame itself holds none of it: the
|
|
5875
6943
|
* `a:graphicData` carries only a `dgm:relIds` with four relationship ids pointing at the
|
|
5876
6944
|
* data, layout, quick-style and colours parts under `/ppt/diagrams/`. The **data** part is
|
|
5877
|
-
* the one with content in it
|
|
5878
|
-
* connections that give the points their tree
|
|
6945
|
+
* the one with content in it (a flat `dgm:ptLst` of points plus a `dgm:cxnLst` of
|
|
6946
|
+
* connections that give the points their tree), and it is what this surface decodes. The
|
|
5879
6947
|
* other three are generated presets (`urn:microsoft.com/office/officeart/…`) describing how
|
|
5880
6948
|
* PowerPoint *draws* the graph rather than what it says; they surface as parts, not getters.
|
|
5881
6949
|
*
|
|
@@ -5908,14 +6976,14 @@ function readLevelPoints(lvl) {
|
|
|
5908
6976
|
*
|
|
5909
6977
|
* **A point's text is stored twice.** The data model is what PowerPoint reads, but a deck
|
|
5910
6978
|
* also carries a *drawing* part (`dsp:drawing`) holding a copy of every drawn string, and
|
|
5911
|
-
* every renderer without a SmartArt layout engine
|
|
5912
|
-
*
|
|
6979
|
+
* every renderer without a SmartArt layout engine (LibreOffice, Google Slides, thumbnailers)
|
|
6980
|
+
* paints that copy and nothing else. {@link DiagramPoint.drawnShape} is the link between
|
|
5913
6981
|
* the two, and {@link DiagramPoint.text}'s setter is what keeps them in step.
|
|
5914
6982
|
*
|
|
5915
6983
|
* Authoring a SmartArt graphic from nothing is out of scope: the layout part is a
|
|
5916
6984
|
* constraint-solver program PowerPoint executes, and the presentation tree it generates is
|
|
5917
6985
|
* not derivable from the user's content. Editing the text of one that already exists is
|
|
5918
|
-
* supported
|
|
6986
|
+
* supported; see {@link DiagramPoint.text}.
|
|
5919
6987
|
*/
|
|
5920
6988
|
const POINT_TYPES = [
|
|
5921
6989
|
"node",
|
|
@@ -5937,7 +7005,7 @@ const DRAWING_EXT_URI = OOXML_NS.dsp;
|
|
|
5937
7005
|
* The node → drawn-shape index of one diagram: the `presOf` edges of the data model and the
|
|
5938
7006
|
* `dsp:sp` elements of the drawing part, both keyed for lookup.
|
|
5939
7007
|
*
|
|
5940
|
-
* Built once per {@link Diagram}, and only when a point is actually asked what draws it
|
|
7008
|
+
* Built once per {@link Diagram}, and only when a point is actually asked what draws it. The
|
|
5941
7009
|
* drawing part is a second XML DOM and a consumer reading node text should not pay to parse
|
|
5942
7010
|
* it. The consequence of that memo: a consumer who *adds* or *removes* shapes in the drawing
|
|
5943
7011
|
* part through the escape hatches must re-reach the diagram to see them here.
|
|
@@ -5980,8 +7048,8 @@ var DrawingIndex = class {
|
|
|
5980
7048
|
* Resolve one authored point to the `pres` point that presents it and to the drawn
|
|
5981
7049
|
* paragraph carrying its text.
|
|
5982
7050
|
*
|
|
5983
|
-
* A point commonly has **several** `presOf` arms
|
|
5984
|
-
* connector beneath it are two presentations of one node
|
|
7051
|
+
* A point commonly has **several** `presOf` arms (an org-chart node's box and the
|
|
7052
|
+
* connector beneath it are two presentations of one node), so the arm is chosen by what it
|
|
5985
7053
|
* reaches rather than by its `@srcOrd`: the first, in `@srcOrd` order, whose `pres` point
|
|
5986
7054
|
* draws a `dsp:sp` that has a `dsp:txBody`. That selector produced a unique hit for all 90
|
|
5987
7055
|
* authored points measured across five layout families, and it degrades to "no arm" rather
|
|
@@ -6052,7 +7120,7 @@ var DiagramPoint = class {
|
|
|
6052
7120
|
return idAttr(this.pt, "cxnId");
|
|
6053
7121
|
}
|
|
6054
7122
|
/**
|
|
6055
|
-
* Whether this point is an unfilled prompt (`dgm:prSet/@phldr`)
|
|
7123
|
+
* Whether this point is an unfilled prompt (`dgm:prSet/@phldr`): a node PowerPoint draws
|
|
6056
7124
|
* with a grey placeholder string and treats as empty. Excluded from {@link Diagram.text}:
|
|
6057
7125
|
* a layout that stores its prompt in `dgm:t` would otherwise extract as content.
|
|
6058
7126
|
*/
|
|
@@ -6071,7 +7139,7 @@ var DiagramPoint = class {
|
|
|
6071
7139
|
* `resolved*` getters all apply.
|
|
6072
7140
|
*
|
|
6073
7141
|
* **Writing through this edits the data model only**, and leaves the drawing cache
|
|
6074
|
-
* holding the old string
|
|
7142
|
+
* holding the old string, which is what PowerPoint-less renderers draw. It is the escape
|
|
6075
7143
|
* hatch for run-level formatting, so it does exactly what it is told and nothing more;
|
|
6076
7144
|
* {@link text}'s setter is the path that mirrors.
|
|
6077
7145
|
*/
|
|
@@ -6086,7 +7154,7 @@ var DiagramPoint = class {
|
|
|
6086
7154
|
}
|
|
6087
7155
|
/**
|
|
6088
7156
|
* The generated `pres` point that presents this one (`dgm:cxn[@type="presOf"]/@destId`),
|
|
6089
|
-
* or `null` when nothing presents it
|
|
7157
|
+
* or `null` when nothing presents it (the normal state of an unlabelled transition point).
|
|
6090
7158
|
*
|
|
6091
7159
|
* Where a point has several `presOf` arms the one named here is the arm that draws text;
|
|
6092
7160
|
* see {@link drawnShape}, which resolves both.
|
|
@@ -6100,7 +7168,7 @@ var DiagramPoint = class {
|
|
|
6100
7168
|
*
|
|
6101
7169
|
* The mapping runs through the `pres` point rather than directly: a `dsp:sp/@modelId` is
|
|
6102
7170
|
* always a `pres` point's id, so a lookup keyed on the authored point's own id finds
|
|
6103
|
-
* nothing at all. It is many-to-one in both directions
|
|
7171
|
+
* nothing at all. It is many-to-one in both directions: one shape draws several points
|
|
6104
7172
|
* (`@destOrd` says which paragraph is whose, and it can contradict document order), and
|
|
6105
7173
|
* one point has several presentations, of which at most one holds text.
|
|
6106
7174
|
*
|
|
@@ -6134,7 +7202,7 @@ var DiagramPoint = class {
|
|
|
6134
7202
|
* Two things it deliberately does not do. It does **not** recompute geometry: the drawn
|
|
6135
7203
|
* shape keeps its cached position and size, so a much longer string overflows its box in
|
|
6136
7204
|
* those same renderers until PowerPoint re-lays the diagram out. And it does not *create*
|
|
6137
|
-
* a place to draw text
|
|
7205
|
+
* a place to draw text. A transition point whose layout has no label slot has no
|
|
6138
7206
|
* `dsp:sp`, and PowerPoint strips text put on one, so the honest outcome is the
|
|
6139
7207
|
* diagnostic. Like every other `text` setter here it collapses the point to one run,
|
|
6140
7208
|
* keeping the first run's `a:rPr`; use {@link textFrame} for run-level work.
|
|
@@ -6168,7 +7236,7 @@ var DiagramPoint = class {
|
|
|
6168
7236
|
}
|
|
6169
7237
|
};
|
|
6170
7238
|
/**
|
|
6171
|
-
* One user-authored point in its place in the diagram's tree
|
|
7239
|
+
* One user-authored point in its place in the diagram's tree: a `node` or an `asst`, never a
|
|
6172
7240
|
* generated `pres` point or a transition label. See {@link Diagram.nodes}.
|
|
6173
7241
|
*/
|
|
6174
7242
|
var DiagramNode = class {
|
|
@@ -6207,7 +7275,7 @@ var Diagram = class {
|
|
|
6207
7275
|
return this.part.partName;
|
|
6208
7276
|
}
|
|
6209
7277
|
/**
|
|
6210
|
-
* Every point (`dgm:ptLst/dgm:pt`) in document order, unfiltered
|
|
7278
|
+
* Every point (`dgm:ptLst/dgm:pt`) in document order, unfiltered, with generated `pres`
|
|
6211
7279
|
* points and the `doc` root included. Filter on {@link DiagramPoint.type} for the user's
|
|
6212
7280
|
* nodes; {@link text} is the shortcut for the common case, {@link nodes} the structured one.
|
|
6213
7281
|
*
|
|
@@ -6224,8 +7292,8 @@ var Diagram = class {
|
|
|
6224
7292
|
return getElements(ptLst, "dgm:pt").map((pt) => new DiagramPoint(pt, this.part, this.themeContext, relationships, drawing));
|
|
6225
7293
|
}
|
|
6226
7294
|
/**
|
|
6227
|
-
* The authored tree
|
|
6228
|
-
* order
|
|
7295
|
+
* The authored tree (every `node` and `asst` point in its place, roots in `@srcOrd`
|
|
7296
|
+
* order) as a view over {@link points} and {@link connections} rather than a replacement
|
|
6229
7297
|
* for either.
|
|
6230
7298
|
*
|
|
6231
7299
|
* A root is a content point whose `parOf` parent is not itself content, which in a
|
|
@@ -6286,10 +7354,12 @@ var Diagram = class {
|
|
|
6286
7354
|
return tree;
|
|
6287
7355
|
}
|
|
6288
7356
|
/**
|
|
6289
|
-
* The point with this `@modelId`, or `null
|
|
6290
|
-
* since a `DiagramConnection` names its ends by id and nothing else resolves them.
|
|
7357
|
+
* The point with this `@modelId`, or `null`. This is the lookup every connection getter implies,
|
|
7358
|
+
* since a `DiagramConnection` names its ends by id and nothing else resolves them. Takes
|
|
7359
|
+
* `null`, as a connection end with no id reads, and misses on it.
|
|
6291
7360
|
*/
|
|
6292
7361
|
point(modelId) {
|
|
7362
|
+
if (modelId === null) return null;
|
|
6293
7363
|
return this.points.find((candidate) => candidate.modelId === modelId) ?? null;
|
|
6294
7364
|
}
|
|
6295
7365
|
/** Every edge (`dgm:cxnLst/dgm:cxn`) in document order. */
|
|
@@ -6313,7 +7383,7 @@ var Diagram = class {
|
|
|
6313
7383
|
});
|
|
6314
7384
|
}
|
|
6315
7385
|
/**
|
|
6316
|
-
* The diagram's text in document order, blocks joined by `\n
|
|
7386
|
+
* The diagram's text in document order, blocks joined by `\n`: the counterpart to
|
|
6317
7387
|
* `TextFrame.text` for a construct that has no single text body.
|
|
6318
7388
|
*
|
|
6319
7389
|
* Contributing points are the ones a user can type into: everything except the generated
|
|
@@ -6385,7 +7455,7 @@ var Diagram = class {
|
|
|
6385
7455
|
return new PackageReadError("diagram/parent-edge-cycle", `Diagram ${this.part.partName} has a parOf cycle reaching point ${modelId}, so its points do not form a tree`);
|
|
6386
7456
|
}
|
|
6387
7457
|
/**
|
|
6388
|
-
* The `doc` point's `dgm:prSet`, which carries all three preset ids
|
|
7458
|
+
* The `doc` point's `dgm:prSet`, which carries all three preset ids, resolved once
|
|
6389
7459
|
* rather than once per getter, since each lookup is a scan of the whole `ptLst`.
|
|
6390
7460
|
* `undefined` means "not looked up yet"; `null` means "looked up, not there".
|
|
6391
7461
|
*/
|
|
@@ -6401,7 +7471,7 @@ var Diagram = class {
|
|
|
6401
7471
|
return prSet ? attr(prSet, name) : null;
|
|
6402
7472
|
}
|
|
6403
7473
|
/**
|
|
6404
|
-
* The drawing index, built on first use and kept
|
|
7474
|
+
* The drawing index, built on first use and kept. See {@link DrawingIndex} for what that
|
|
6405
7475
|
* memo costs a consumer who mutates the drawing part's shape tree by hand.
|
|
6406
7476
|
*/
|
|
6407
7477
|
#drawingIndex() {
|
|
@@ -6421,15 +7491,14 @@ function idAttr(element, name) {
|
|
|
6421
7491
|
const raw = attr(element, name);
|
|
6422
7492
|
return raw === null || raw === "" || raw === "0" ? null : raw;
|
|
6423
7493
|
}
|
|
6424
|
-
|
|
6425
|
-
//#region src/read/api/table-structure.ts
|
|
7494
|
+
// src/read/api/table-structure.ts
|
|
6426
7495
|
/**
|
|
6427
7496
|
* Structural edits to an existing `a:tbl`: adding and removing rows and columns, and
|
|
6428
7497
|
* merging and unmerging cells.
|
|
6429
7498
|
*
|
|
6430
7499
|
* The one fact that makes this tractable is that a stored table's grid is already
|
|
6431
|
-
* **rectangular**. Unlike the write path
|
|
6432
|
-
* build the merge grid itself (`gen/slide/objects/table.ts` STEP 3)
|
|
7500
|
+
* **rectangular**. Unlike the write path, which receives lopsided row arrays and has to
|
|
7501
|
+
* build the merge grid itself (`gen/slide/objects/table.ts` STEP 3), every `a:tr` in a
|
|
6433
7502
|
* conformant `a:tbl` holds exactly one `a:tc` per grid column, with the covered half of each
|
|
6434
7503
|
* span present and flagged. So "the cell at (r, c)" is simply the c-th `a:tc` of the r-th
|
|
6435
7504
|
* `a:tr`, and an edit's whole job is keeping that rectangle true.
|
|
@@ -6463,7 +7532,7 @@ function rowsOf(tbl) {
|
|
|
6463
7532
|
function cellsOf(tr) {
|
|
6464
7533
|
return getElements(tr, "a:tc");
|
|
6465
7534
|
}
|
|
6466
|
-
/** The table's `a:tblGrid`, or a thrown error
|
|
7535
|
+
/** The table's `a:tblGrid`, or a thrown error: `CT_Table` requires one. */
|
|
6467
7536
|
function gridOf(tbl) {
|
|
6468
7537
|
const grid = firstChild(tbl, "a:tblGrid");
|
|
6469
7538
|
if (!grid) throw new InvalidOptionError("table/column-index-out-of-range", "This table has no a:tblGrid, so its columns cannot be edited");
|
|
@@ -6486,7 +7555,7 @@ function isVMerge(tc) {
|
|
|
6486
7555
|
return boolAttr(tc, "vMerge") === true;
|
|
6487
7556
|
}
|
|
6488
7557
|
/**
|
|
6489
|
-
* Set a span attribute, removing it when the span collapses to 1
|
|
7558
|
+
* Set a span attribute, removing it when the span collapses to 1: the schema default, and
|
|
6490
7559
|
* what PowerPoint itself writes for an unmerged cell.
|
|
6491
7560
|
*/
|
|
6492
7561
|
function setSpan(tc, name, value) {
|
|
@@ -6711,8 +7780,8 @@ function removeColumn(tbl, index) {
|
|
|
6711
7780
|
/**
|
|
6712
7781
|
* Merge the rectangle `(row1, col1)`–`(row2, col2)` into one cell.
|
|
6713
7782
|
*
|
|
6714
|
-
* The top-left cell becomes the origin; every other cell in the rectangle becomes a covered cell
|
|
6715
|
-
*
|
|
7783
|
+
* The top-left cell becomes the origin; every other cell in the rectangle becomes a covered cell:
|
|
7784
|
+
* flagged, given the spans PowerPoint repeats on a region's first row and column, and emptied,
|
|
6716
7785
|
* since a covered cell is never rendered.
|
|
6717
7786
|
*
|
|
6718
7787
|
* **The covered cells' text moves into the origin**, as PowerPoint's own Merge Cells does: each
|
|
@@ -6823,8 +7892,7 @@ function emptyCellText(tc) {
|
|
|
6823
7892
|
for (const p of getElements(txBody, "a:p")) txBody.removeChild(p);
|
|
6824
7893
|
txBody.appendChild(createElement(ownerDocumentOf(txBody), "a:p"));
|
|
6825
7894
|
}
|
|
6826
|
-
|
|
6827
|
-
//#region src/read/api/table-style-resolve.ts
|
|
7895
|
+
// src/read/api/table-style-resolve.ts
|
|
6828
7896
|
/**
|
|
6829
7897
|
* Look up the `a:tblStyle` whose `@styleId` equals `styleId` in the deck's single
|
|
6830
7898
|
* `tableStyles.xml`, or `null` when there is no such part or no matching style
|
|
@@ -6884,7 +7952,7 @@ function cellStyleParts(flags, row, col, rowCount, colCount) {
|
|
|
6884
7952
|
/**
|
|
6885
7953
|
* The fill a single `a:tcStyle` contributes. `fall` means the part defines no fill
|
|
6886
7954
|
* (only borders, or nothing) so resolution continues to the next-lower part;
|
|
6887
|
-
* `stop` means the part settles the fill
|
|
7955
|
+
* `stop` means the part settles the fill, with either a resolved solid colour or `null`
|
|
6888
7956
|
* for an explicit `a:noFill` / a non-solid style fill (a gradient has no single hex).
|
|
6889
7957
|
*/
|
|
6890
7958
|
function tcStyleFill(tcStyle, ctx) {
|
|
@@ -6980,29 +8048,28 @@ function resolveTableCellTextStyle(style, flags, row, col, rowCount, colCount, c
|
|
|
6980
8048
|
}
|
|
6981
8049
|
return out;
|
|
6982
8050
|
}
|
|
6983
|
-
|
|
6984
|
-
//#region src/read/api/table-edit.ts
|
|
8051
|
+
// src/read/api/table-edit.ts
|
|
6985
8052
|
/**
|
|
6986
8053
|
* Ordered mutation of a table cell's `a:tcPr`, and the cell-scoped schema facts every such
|
|
6987
8054
|
* edit needs.
|
|
6988
8055
|
*
|
|
6989
8056
|
* `CT_TableCellProperties` declares its children as a **sequence**, so an append-only
|
|
6990
|
-
* implementation produces an out-of-order `a:tcPr
|
|
8057
|
+
* implementation produces an out-of-order `a:tcPr`, which PowerPoint reports as a corrupt
|
|
6991
8058
|
* file rather than as a bad edit. Every insertion therefore goes through
|
|
6992
|
-
* {@link
|
|
8059
|
+
* {@link insertInOrder} with the new element's *successors*, which is
|
|
6993
8060
|
* what `TCPR_AFTER` tabulates; it and the sequence it derives from live in
|
|
6994
8061
|
* `src/ooxml/sequence.ts` alongside the other complexType orderings.
|
|
6995
8062
|
*
|
|
6996
8063
|
* The attribute values are the other half. A value outside its `ST_` union is equally fatal
|
|
6997
|
-
* and equally silent, so each is checked against the enum before it is written
|
|
8064
|
+
* and equally silent, so each is checked against the enum before it is written: the same
|
|
6998
8065
|
* validate-at-the-boundary rule the write path follows, with the difference that here there
|
|
6999
8066
|
* is a caller to throw at rather than a deck to degrade (see `ooxml/check-enum.ts`).
|
|
7000
8067
|
*/
|
|
7001
|
-
/** `ST_TextAnchoringType
|
|
8068
|
+
/** `ST_TextAnchoringType`, for `a:tcPr/@anchor`. */
|
|
7002
8069
|
const ANCHOR_VALUES = TEXT_ANCHORS;
|
|
7003
|
-
/** `ST_TextVerticalType
|
|
8070
|
+
/** `ST_TextVerticalType`, for `a:tcPr/@vert`. */
|
|
7004
8071
|
const VERT_VALUES = TEXT_VERTICAL;
|
|
7005
|
-
/** `ST_TextHorzOverflowType
|
|
8072
|
+
/** `ST_TextHorzOverflowType`, for `a:tcPr/@horzOverflow`. */
|
|
7006
8073
|
const HORZ_OVERFLOW_VALUES = TEXT_HORZ_OVERFLOW;
|
|
7007
8074
|
/** The four edges plus the two diagonals, keyed as {@link TableCellEdge} names them. */
|
|
7008
8075
|
const EDGE_QNAMES = {
|
|
@@ -7022,8 +8089,7 @@ const EDGE_QNAMES = {
|
|
|
7022
8089
|
function insertTcPrChild(tcPr, qname, node) {
|
|
7023
8090
|
insertInOrder(tcPr, node, TCPR_AFTER[qname] ?? []);
|
|
7024
8091
|
}
|
|
7025
|
-
|
|
7026
|
-
//#region src/read/api/table-cell.ts
|
|
8092
|
+
// src/read/api/table-cell.ts
|
|
7027
8093
|
/** One table cell (`a:tc`). */
|
|
7028
8094
|
var TableCell = class {
|
|
7029
8095
|
tc;
|
|
@@ -7114,7 +8180,7 @@ var TableCell = class {
|
|
|
7114
8180
|
* The cell's `a:tcPr`, creating it when absent.
|
|
7115
8181
|
*
|
|
7116
8182
|
* `CT_TableCell` sequences `a:txBody` then `a:tcPr` then `a:extLst`, so a newly created
|
|
7117
|
-
* one is inserted before `a:extLst
|
|
8183
|
+
* one is inserted before `a:extLst`; appending it blindly would put it after and make
|
|
7118
8184
|
* the part invalid. Every setter below goes through here for that reason.
|
|
7119
8185
|
*/
|
|
7120
8186
|
#getOrAddTcPr() {
|
|
@@ -7137,7 +8203,7 @@ var TableCell = class {
|
|
|
7137
8203
|
this.#setTcPrAttr("vert", value, VERT_VALUES, "table/invalid-cell-vert");
|
|
7138
8204
|
}
|
|
7139
8205
|
/**
|
|
7140
|
-
* Set (or clear) `a:tcPr/@horzOverflow
|
|
8206
|
+
* Set (or clear) `a:tcPr/@horzOverflow`: what a single glyph too wide for the cell does.
|
|
7141
8207
|
* `null` removes the attribute; so does `'clip'` in effect, since it is the schema
|
|
7142
8208
|
* default, but the attribute is still written as asked.
|
|
7143
8209
|
* @throws {InvalidOptionError} when the value is outside `ST_TextHorzOverflowType`
|
|
@@ -7161,7 +8227,7 @@ var TableCell = class {
|
|
|
7161
8227
|
*
|
|
7162
8228
|
* Partial: only the sides named are touched, so `{ left: 0 }` flushes the text left and
|
|
7163
8229
|
* leaves the other three alone. A side given as `null` has its attribute removed, which
|
|
7164
|
-
* returns it to the schema default (91440 EMU left/right, 45720 top/bottom)
|
|
8230
|
+
* returns it to the schema default (91440 EMU left/right, 45720 top/bottom), not to
|
|
7165
8231
|
* zero. Pass `{}` to change nothing.
|
|
7166
8232
|
* @throws {InvalidOptionError} when a value is not a finite number
|
|
7167
8233
|
*/
|
|
@@ -7181,7 +8247,7 @@ var TableCell = class {
|
|
|
7181
8247
|
this.part.markDirty();
|
|
7182
8248
|
}
|
|
7183
8249
|
/**
|
|
7184
|
-
* Set (or clear) one of the cell's six borders
|
|
8250
|
+
* Set (or clear) one of the cell's six borders: the four edges and the two diagonals.
|
|
7185
8251
|
*
|
|
7186
8252
|
* `null` removes the element entirely, which is "inherit", and is a different thing from
|
|
7187
8253
|
* `{ noFill: true }`, which writes an explicit no-border and suppresses whatever would
|
|
@@ -7234,7 +8300,7 @@ var TableCell = class {
|
|
|
7234
8300
|
* `null` removes the `a:solidFill` and lets the cell inherit from the table style again.
|
|
7235
8301
|
* That is not the same as {@link noFill}, which writes an explicit `a:noFill` and so
|
|
7236
8302
|
* suppresses the inherited shading. Any competing fill choice (`a:gradFill`,
|
|
7237
|
-
* `a:blipFill`, …) is dropped first
|
|
8303
|
+
* `a:blipFill`, …) is dropped first, since `EG_FillProperties` admits one.
|
|
7238
8304
|
* @throws {InvalidOptionError} when `color` is not a 6-digit hex string
|
|
7239
8305
|
*/
|
|
7240
8306
|
setFillColor(color) {
|
|
@@ -7249,7 +8315,7 @@ var TableCell = class {
|
|
|
7249
8315
|
this.#applyFill(token === null ? null : { scheme: token });
|
|
7250
8316
|
}
|
|
7251
8317
|
/**
|
|
7252
|
-
* Write an explicit `<a:noFill/>` on the cell
|
|
8318
|
+
* Write an explicit `<a:noFill/>` on the cell: a transparent cell that shows the table
|
|
7253
8319
|
* background (or the slide) through. Distinct from `setFillColor(null)`, which removes
|
|
7254
8320
|
* the fill and lets the table style's shading apply again.
|
|
7255
8321
|
*/
|
|
@@ -7275,23 +8341,23 @@ var TableCell = class {
|
|
|
7275
8341
|
}
|
|
7276
8342
|
/**
|
|
7277
8343
|
* The cell's solid fill resolved against the table's theme colour context to a
|
|
7278
|
-
* literal hex
|
|
8344
|
+
* literal hex. It is the table-cell counterpart of
|
|
7279
8345
|
* {@link AutoShape.resolvedFill}. The cell's own
|
|
7280
8346
|
* `a:tcPr/a:solidFill` wins; when the cell defines none, this falls back to the
|
|
7281
8347
|
* table **style** graph (the `firstRow`/banded/`wholeTbl` shading the
|
|
7282
|
-
* `a:tableStyleId` supplies
|
|
8348
|
+
* `a:tableStyleId` supplies; see {@link Table.resolvedStyle}), so a styled cell
|
|
7283
8349
|
* with an empty `a:tcPr` reports the colour PowerPoint actually renders rather
|
|
7284
8350
|
* than `null`.
|
|
7285
8351
|
*
|
|
7286
8352
|
* A cell that carries *some other* fill choice (`a:blipFill`/`a:gradFill`/
|
|
7287
8353
|
* `a:pattFill`/`a:noFill`) overrides the style graph in PowerPoint, so this
|
|
7288
|
-
* reports `null` for one rather than falling through to the inherited shading
|
|
8354
|
+
* reports `null` for one rather than falling through to the inherited shading,
|
|
7289
8355
|
* the same guard {@link AutoShape.resolvedFill} applies to
|
|
7290
8356
|
* the style matrix. Read {@link pictureFill} for an image-filled cell. Also
|
|
7291
8357
|
* `null` when neither source yields a solid colour (an
|
|
7292
8358
|
* unmapped token, an explicit style `a:noFill`). The returned
|
|
7293
8359
|
* {@link ResolvedColor} carries `effectiveHex` (the base colour with its
|
|
7294
|
-
* `lumMod`/`lumOff`/… transforms applied)
|
|
8360
|
+
* `lumMod`/`lumOff`/… transforms applied); read that for the final colour.
|
|
7295
8361
|
*/
|
|
7296
8362
|
get resolvedFill() {
|
|
7297
8363
|
if (this.hasOwnFill) return resolveSolidFillColor(this.#tcPr(), this.themeContext);
|
|
@@ -7302,7 +8368,7 @@ var TableCell = class {
|
|
|
7302
8368
|
* child), as opposed to inheriting one from the table style's header/banding rules.
|
|
7303
8369
|
*
|
|
7304
8370
|
* This is the flag that disambiguates {@link resolvedFill}, which deliberately reports
|
|
7305
|
-
* either
|
|
8371
|
+
* either: it answers "what colour does this cell render as", and both sources are valid
|
|
7306
8372
|
* answers to that. Anything that has to reproduce the cell rather than describe it needs
|
|
7307
8373
|
* to know which: baking an inherited banding colour into a copy makes the copy look right
|
|
7308
8374
|
* until someone changes its table style, and then nothing moves. `false` for a cell whose
|
|
@@ -7312,20 +8378,20 @@ var TableCell = class {
|
|
|
7312
8378
|
return hasFillChoice(this.#tcPr());
|
|
7313
8379
|
}
|
|
7314
8380
|
/**
|
|
7315
|
-
* `true` when the cell sets an explicit no-fill (`a:tcPr/a:noFill`)
|
|
8381
|
+
* `true` when the cell sets an explicit no-fill (`a:tcPr/a:noFill`): a deliberately
|
|
7316
8382
|
* transparent cell showing the table background (or the slide) through. The cell-side
|
|
7317
8383
|
* counterpart of {@link AutoShape.fillNoFill}, and what
|
|
7318
8384
|
* {@link noFill} writes.
|
|
7319
8385
|
*
|
|
7320
8386
|
* {@link hasOwnFill} is not this question: it is `true` for *any* `EG_FillProperties`
|
|
7321
8387
|
* child, so on its own it cannot separate a suppressed fill from a gradient or an image
|
|
7322
|
-
* one
|
|
8388
|
+
* one, and every colour accessor ({@link resolvedFill}, {@link fillSchemeColor})
|
|
7323
8389
|
* reports `null` for a no-fill cell exactly as it does for a
|
|
7324
8390
|
* cell that inherits its shading from the table style. Deriving it as "has a fill of its
|
|
7325
8391
|
* own, and no accessor recognises it" instead of reading it has two failure modes: it
|
|
7326
8392
|
* folds `a:grpFill` in with `a:noFill`, and its meaning changes silently the day a
|
|
7327
|
-
* further fill kind gets an accessor. The two paint completely differently
|
|
7328
|
-
* inherited banding colour versus nothing at all
|
|
8393
|
+
* further fill kind gets an accessor. The two paint completely differently (an
|
|
8394
|
+
* inherited banding colour versus nothing at all), so a consumer that cannot tell them
|
|
7329
8395
|
* apart paints a transparent cell in the style's shading.
|
|
7330
8396
|
*/
|
|
7331
8397
|
get fillNoFill() {
|
|
@@ -7383,7 +8449,7 @@ var TableCell = class {
|
|
|
7383
8449
|
* `null` when the cell defines none. Each present edge decodes to a
|
|
7384
8450
|
* {@link CellBorder} (width / dash / resolved colour + its raw token and transform
|
|
7385
8451
|
* list / suppressed flag);
|
|
7386
|
-
* absent edges are `null`. Cell borders are the biggest visible table gap
|
|
8452
|
+
* absent edges are `null`. Cell borders are the biggest visible table gap: a
|
|
7387
8453
|
* replica built only from geometry and fill draws every cell edge-to-edge with
|
|
7388
8454
|
* no rule, so this surfaces the per-side stroke the writer's `border` option emits.
|
|
7389
8455
|
*/
|
|
@@ -7393,7 +8459,10 @@ var TableCell = class {
|
|
|
7393
8459
|
const decode = (qname) => {
|
|
7394
8460
|
const ln = firstChild(tcPr, qname);
|
|
7395
8461
|
if (!ln) return null;
|
|
7396
|
-
return
|
|
8462
|
+
return {
|
|
8463
|
+
...readLineBasics(ln, this.themeContext),
|
|
8464
|
+
cap: attr(ln, "cap") ?? null
|
|
8465
|
+
};
|
|
7397
8466
|
};
|
|
7398
8467
|
const borders = {
|
|
7399
8468
|
left: decode("a:lnL"),
|
|
@@ -7423,7 +8492,7 @@ var TableCell = class {
|
|
|
7423
8492
|
}
|
|
7424
8493
|
/**
|
|
7425
8494
|
* Whether the cell's whole text **block** is centred horizontally within it
|
|
7426
|
-
* (`a:tcPr/@anchorCtr`). `false` when unset
|
|
8495
|
+
* (`a:tcPr/@anchorCtr`). `false` when unset; that is the schema default, and it is
|
|
7427
8496
|
* what PowerPoint writes nothing for.
|
|
7428
8497
|
*
|
|
7429
8498
|
* Distinct from a paragraph's `align`, which decides where each *line* sits inside the
|
|
@@ -7467,12 +8536,12 @@ var TableCell = class {
|
|
|
7467
8536
|
*
|
|
7468
8537
|
* This is what {@link headerIds} references: a data cell names the header cells that
|
|
7469
8538
|
* govern it by their `@id`, which is how a complex table tells a screen reader what a
|
|
7470
|
-
* value means. Unrelated to the shape-level `p:cNvPr/@id
|
|
8539
|
+
* value means. Unrelated to the shape-level `p:cNvPr/@id`: it is an `xs:string` scoped
|
|
7471
8540
|
* to the table, not a slide-wide numeric id.
|
|
7472
8541
|
*
|
|
7473
8542
|
* **PowerPoint does not write this and strips it on save**, so it will read `null` on
|
|
7474
8543
|
* any deck PowerPoint has saved, however it was produced. It is surfaced for decks from
|
|
7475
|
-
* other producers. There is deliberately no write-API counterpart
|
|
8544
|
+
* other producers. There is deliberately no write-API counterpart; see
|
|
7476
8545
|
* `test/read/fixtures/authoring/probe-table-cell-a11y-and-3d.ps1` for the measurement.
|
|
7477
8546
|
*/
|
|
7478
8547
|
get id() {
|
|
@@ -7481,7 +8550,7 @@ var TableCell = class {
|
|
|
7481
8550
|
/**
|
|
7482
8551
|
* The `@id`s of the header cells that govern this cell
|
|
7483
8552
|
* (`a:tcPr/a:headers/a:header/@val`), in document order. Empty when the cell declares no
|
|
7484
|
-
* header association
|
|
8553
|
+
* header association, which is the common case, since only a complex table needs one.
|
|
7485
8554
|
*
|
|
7486
8555
|
* Carries the same caveat as {@link id}: PowerPoint strips `a:headers` on save, so this
|
|
7487
8556
|
* is empty on any PowerPoint-saved deck. `Table.firstRowHeader` (`a:tblPr/@firstRow`) is
|
|
@@ -7496,7 +8565,7 @@ var TableCell = class {
|
|
|
7496
8565
|
/**
|
|
7497
8566
|
* How the cell treats a glyph too wide for its text width (`a:tcPr/@horzOverflow`):
|
|
7498
8567
|
* `'clip'` cuts it at the cell edge, `'overflow'` lets it draw past. `null` when unset
|
|
7499
|
-
* (PowerPoint clips). Not a wrap flag
|
|
8568
|
+
* (PowerPoint clips). Not a wrap flag: cell text always wraps to the column width.
|
|
7500
8569
|
*/
|
|
7501
8570
|
get horzOverflow() {
|
|
7502
8571
|
const tcPr = this.#tcPr();
|
|
@@ -7542,8 +8611,7 @@ var TableCell = class {
|
|
|
7542
8611
|
this.part.markDirty();
|
|
7543
8612
|
}
|
|
7544
8613
|
};
|
|
7545
|
-
|
|
7546
|
-
//#region src/read/api/table-row.ts
|
|
8614
|
+
// src/read/api/table-row.ts
|
|
7547
8615
|
/** One table row (`a:tr`). */
|
|
7548
8616
|
var TableRow = class {
|
|
7549
8617
|
tr;
|
|
@@ -7577,8 +8645,7 @@ var TableRow = class {
|
|
|
7577
8645
|
this.part.markDirty();
|
|
7578
8646
|
}
|
|
7579
8647
|
};
|
|
7580
|
-
|
|
7581
|
-
//#region src/read/api/table.ts
|
|
8648
|
+
// src/read/api/table.ts
|
|
7582
8649
|
/** A table: a grid of rows and cells inside a graphic frame. */
|
|
7583
8650
|
var Table = class {
|
|
7584
8651
|
tbl;
|
|
@@ -7603,7 +8670,7 @@ var Table = class {
|
|
|
7603
8670
|
* from the deck's `tableStyles.xml`, or `null` when the table names no
|
|
7604
8671
|
* `a:tableStyleId`, the deck has no `tableStyles.xml`, or the id is a built-in
|
|
7605
8672
|
* `[MS-OE376]` style the deck does not materialise. This is what supplies the
|
|
7606
|
-
* banded-row / header shading a cell with no own fill inherits
|
|
8673
|
+
* banded-row / header shading a cell with no own fill inherits. Read the
|
|
7607
8674
|
* resolved per-cell colour off {@link TableCell.resolvedFill}.
|
|
7608
8675
|
*
|
|
7609
8676
|
* Resolved afresh on every read, not through the per-instance memo the cells share, so an
|
|
@@ -7671,7 +8738,7 @@ var Table = class {
|
|
|
7671
8738
|
* `{5940675A-B579-460E-94D1-54222C63F5DA}`, or `null` when the table carries no
|
|
7672
8739
|
* `a:tableStyleId`. This is the reference into `ppt/tableStyles.xml` (or a
|
|
7673
8740
|
* built-in `[MS-OE376]` style) that supplies the banded-row / header shading the
|
|
7674
|
-
* `firstRow`/`bandRow` flags activate
|
|
8741
|
+
* `firstRow`/`bandRow` flags activate. Without it a replica loses the whole
|
|
7675
8742
|
* table style, so it is the read counterpart of the writer's `tableStyle` option.
|
|
7676
8743
|
*/
|
|
7677
8744
|
get styleId() {
|
|
@@ -7686,7 +8753,7 @@ var Table = class {
|
|
|
7686
8753
|
* This is the table-level counterpart of {@link TableCell.resolvedFill}, and it is a
|
|
7687
8754
|
* genuinely different thing from a cell fill: a `a:tblPr` fill sits *behind* the grid, so
|
|
7688
8755
|
* a cell with no fill of its own shows it through. Reports `null` for a non-solid choice
|
|
7689
|
-
* (`a:blipFill`/`a:gradFill`/`a:pattFill`/`a:noFill`)
|
|
8756
|
+
* (`a:blipFill`/`a:gradFill`/`a:pattFill`/`a:noFill`); read {@link pictureFill} for an
|
|
7690
8757
|
* image background.
|
|
7691
8758
|
*/
|
|
7692
8759
|
get resolvedFill() {
|
|
@@ -7764,6 +8831,13 @@ var Table = class {
|
|
|
7764
8831
|
return this.#tblPrFlag("lastCol");
|
|
7765
8832
|
}
|
|
7766
8833
|
/**
|
|
8834
|
+
* Whether the table reads right to left (`a:tblPr/@rtl`), from `addTable`'s `rtl`: its columns
|
|
8835
|
+
* run from the right edge, so column 0 is the rightmost.
|
|
8836
|
+
*/
|
|
8837
|
+
get rtl() {
|
|
8838
|
+
return this.#tblPrFlag("rtl");
|
|
8839
|
+
}
|
|
8840
|
+
/**
|
|
7767
8841
|
* The cell at `(rowIndex, columnIndex)` (both zero-based), or `null` when out
|
|
7768
8842
|
* of range. Column index counts `a:tc` elements in the row, so a cell that
|
|
7769
8843
|
* spans columns (`gridSpan`) occupies a single index here.
|
|
@@ -7793,7 +8867,7 @@ var Table = class {
|
|
|
7793
8867
|
* Remove the row at `index`, with its content.
|
|
7794
8868
|
*
|
|
7795
8869
|
* A cell in the row that *continues* a vertical merge shortens that merge. A cell that
|
|
7796
|
-
* *starts* one hands the region to its first continuation, which becomes the new origin
|
|
8870
|
+
* *starts* one hands the region to its first continuation, which becomes the new origin:
|
|
7797
8871
|
* the merged region survives one row shorter, and only the removed row's own text is lost.
|
|
7798
8872
|
* @throws {InvalidOptionError} when `index` is out of range
|
|
7799
8873
|
*/
|
|
@@ -7832,7 +8906,7 @@ var Table = class {
|
|
|
7832
8906
|
* never rendered.
|
|
7833
8907
|
*
|
|
7834
8908
|
* A rectangle that cuts through an existing merge is **rejected**, not silently widened
|
|
7835
|
-
* to fit
|
|
8909
|
+
* to fit. The caller asked for a specific region, and quietly producing a different one
|
|
7836
8910
|
* is how a layout ends up wrong with nothing to point at. Unmerge first.
|
|
7837
8911
|
* @throws {InvalidOptionError} when an index is out of range, the range covers one cell,
|
|
7838
8912
|
* or it partially overlaps an existing merge
|
|
@@ -7864,10 +8938,9 @@ var Table = class {
|
|
|
7864
8938
|
return tblPr ? boolAttr(tblPr, name) === true : false;
|
|
7865
8939
|
}
|
|
7866
8940
|
};
|
|
7867
|
-
|
|
7868
|
-
//#region src/read/api/shapes/graphic-frame.ts
|
|
8941
|
+
// src/read/api/shapes/graphic-frame.ts
|
|
7869
8942
|
/**
|
|
7870
|
-
* A graphic frame (`p:graphicFrame`)
|
|
8943
|
+
* A graphic frame (`p:graphicFrame`): the host element for a table, a chart, a chartEx
|
|
7871
8944
|
* chart, or a SmartArt diagram.
|
|
7872
8945
|
*
|
|
7873
8946
|
* Which one it holds is told by the `a:graphicData/@uri`, so the accessors below each check
|
|
@@ -7876,7 +8949,7 @@ var Table = class {
|
|
|
7876
8949
|
* without it a consumer cannot tell "this frame holds content I cannot reach" from "this
|
|
7877
8950
|
* frame holds a chart with nothing in it", since every predicate answers `false` for both.
|
|
7878
8951
|
*/
|
|
7879
|
-
/** A graphic frame (`p:graphicFrame`)
|
|
8952
|
+
/** A graphic frame (`p:graphicFrame`), the host for tables, charts and SmartArt diagrams. */
|
|
7880
8953
|
var GraphicFrame = class extends Shape {
|
|
7881
8954
|
shapeType = "graphicFrame";
|
|
7882
8955
|
xfrm() {
|
|
@@ -7911,7 +8984,7 @@ var GraphicFrame = class extends Shape {
|
|
|
7911
8984
|
return this.#graphicDataUri() === OOXML_NS.dgm;
|
|
7912
8985
|
}
|
|
7913
8986
|
/**
|
|
7914
|
-
* The `a:graphicData/@uri` verbatim
|
|
8987
|
+
* The `a:graphicData/@uri` verbatim: the namespace naming what this frame hosts, or
|
|
7915
8988
|
* `null` when the frame carries no `a:graphicData`.
|
|
7916
8989
|
*
|
|
7917
8990
|
* Every `has*` predicate is a comparison against this, so a frame whose URI matches none
|
|
@@ -7938,7 +9011,7 @@ var GraphicFrame = class extends Shape {
|
|
|
7938
9011
|
const relId = chartRef && attr(chartRef, "r:id");
|
|
7939
9012
|
if (!relId) return null;
|
|
7940
9013
|
const part = relPart(this.host.opc, this.host.relationships, relId);
|
|
7941
|
-
return part ? new Chart(part) : null;
|
|
9014
|
+
return part ? new Chart(part, this.host.opc.relationshipsFor(part.partName)) : null;
|
|
7942
9015
|
}
|
|
7943
9016
|
/**
|
|
7944
9017
|
* The hosted chartEx chart (waterfall/funnel/treemap/…), or `null` when this
|
|
@@ -7978,25 +9051,24 @@ var GraphicFrame = class extends Shape {
|
|
|
7978
9051
|
return graphicData ? attr(graphicData, "uri") : null;
|
|
7979
9052
|
}
|
|
7980
9053
|
};
|
|
7981
|
-
|
|
7982
|
-
//#region src/read/api/shapes.ts
|
|
9054
|
+
// src/read/api/shapes.ts
|
|
7983
9055
|
/**
|
|
7984
9056
|
* Read-model proxies for the shapes in a `p:spTree`.
|
|
7985
9057
|
*
|
|
7986
9058
|
* The `p:spTree` holds five shape kinds; each wraps its element and exposes
|
|
7987
9059
|
* non-visual identity (id/name), geometry (left/top/width/height in EMU), and
|
|
7988
9060
|
* kind-specific reads. Proxies hold a back-reference to the owning
|
|
7989
|
-
* {@link ShapeHost}
|
|
9061
|
+
* {@link ShapeHost} (the slide, layout, or master whose part carries the tree),
|
|
7990
9062
|
* so pictures can resolve their image relationship and so edits can mark that
|
|
7991
9063
|
* part dirty.
|
|
7992
9064
|
*
|
|
7993
9065
|
* This module is the entry point for that model: the `Shape` base and the four leaf kinds live in
|
|
7994
|
-
* `./shapes/`, and only the dispatch that turns an element into a proxy
|
|
7995
|
-
* recurses back through it
|
|
9066
|
+
* `./shapes/`, and only the dispatch that turns an element into a proxy (plus `GroupShape`, which
|
|
9067
|
+
* recurses back through it) is here. Everything the read API surfaces is re-exported below, so
|
|
7996
9068
|
* `pptx-ts/read` and its consumers keep importing shapes from one place.
|
|
7997
9069
|
*/
|
|
7998
9070
|
/**
|
|
7999
|
-
* A group shape (`p:grpSp`)
|
|
9071
|
+
* A group shape (`p:grpSp`), which contains nested shapes.
|
|
8000
9072
|
*
|
|
8001
9073
|
* It is the one kind defined here rather than under `./shapes/`: its `shapes` getter recurses
|
|
8002
9074
|
* through `buildShapes` below, so putting it in a sibling module would make the dispatch and the
|
|
@@ -8030,7 +9102,7 @@ var GroupShape = class extends Shape {
|
|
|
8030
9102
|
* element is incomplete.
|
|
8031
9103
|
*
|
|
8032
9104
|
* Most consumers should not need this: {@link Shape.absoluteFrame} already
|
|
8033
|
-
* composes the whole ancestor chain
|
|
9105
|
+
* composes the whole ancestor chain (child space, group flips and rotations),
|
|
8034
9106
|
* and doing that arithmetic per consumer is how a shape in a nested group ends
|
|
8035
9107
|
* up subtly displaced. It is here for a *replica* consumer rather than a
|
|
8036
9108
|
* *paint* one: rebuilding this group and its children as OOXML needs the
|
|
@@ -8053,7 +9125,7 @@ var GroupShape = class extends Shape {
|
|
|
8053
9125
|
return buildShapes(this.element, this.host);
|
|
8054
9126
|
}
|
|
8055
9127
|
};
|
|
8056
|
-
/** Narrow a shape to an {@link AutoShape} (`p:sp
|
|
9128
|
+
/** Narrow a shape to an {@link AutoShape} (`p:sp`: auto shape, text box, or placeholder). */
|
|
8057
9129
|
function isAutoShape(shape) {
|
|
8058
9130
|
return shape.shapeType === "autoShape";
|
|
8059
9131
|
}
|
|
@@ -8065,7 +9137,7 @@ function isPicture(shape) {
|
|
|
8065
9137
|
function isConnector(shape) {
|
|
8066
9138
|
return shape.shapeType === "connector";
|
|
8067
9139
|
}
|
|
8068
|
-
/** Narrow a shape to a {@link GraphicFrame} (`p:graphicFrame
|
|
9140
|
+
/** Narrow a shape to a {@link GraphicFrame} (`p:graphicFrame`, the table/chart host). */
|
|
8069
9141
|
function isGraphicFrame(shape) {
|
|
8070
9142
|
return shape.shapeType === "graphicFrame";
|
|
8071
9143
|
}
|
|
@@ -8093,7 +9165,7 @@ function wrapShapeElement(element, host) {
|
|
|
8093
9165
|
* The richer of an `mc:AlternateContent`'s two branches: the first `mc:Choice`
|
|
8094
9166
|
* that carries a shape element (what PowerPoint renders when it understands the
|
|
8095
9167
|
* required feature), falling back to `mc:Fallback`. PowerPoint wraps a shape this
|
|
8096
|
-
* way when its full form needs a feature namespace a plain consumer lacks
|
|
9168
|
+
* way when its full form needs a feature namespace a plain consumer lacks: a
|
|
8097
9169
|
* chartEx chart's `p:graphicFrame`, a zoom frame, an inline-math shape. Returns
|
|
8098
9170
|
* the wrapped inner shape, or `null` when neither branch holds one.
|
|
8099
9171
|
*/
|
|
@@ -8130,11 +9202,13 @@ function buildShapes(parent, host) {
|
|
|
8130
9202
|
/**
|
|
8131
9203
|
* The first shape in `shapes` with the given drawing id (`p:cNvPr/@id`), walked
|
|
8132
9204
|
* pre-order so a group is visited before its children. Backs
|
|
8133
|
-
* {@link ShapeHost.shapeByIdDeep} on all three hosts
|
|
9205
|
+
* {@link ShapeHost.shapeByIdDeep} on all three hosts. Drawing ids are unique
|
|
8134
9206
|
* within a part, so the first match is the only match and the walk order just
|
|
8135
|
-
* fixes a deterministic one.
|
|
9207
|
+
* fixes a deterministic one. A `null` id, as a shape's `id` reads when it has
|
|
9208
|
+
* no `@id`, is a miss, not a match on the first shape that lacks one too.
|
|
8136
9209
|
*/
|
|
8137
9210
|
function findShapeByIdDeep(shapes, id) {
|
|
9211
|
+
if (id === null) return void 0;
|
|
8138
9212
|
for (const shape of shapes) {
|
|
8139
9213
|
if (shape.id === id) return shape;
|
|
8140
9214
|
if (shape instanceof GroupShape) {
|
|
@@ -8143,7 +9217,6 @@ function findShapeByIdDeep(shapes, id) {
|
|
|
8143
9217
|
}
|
|
8144
9218
|
}
|
|
8145
9219
|
}
|
|
8146
|
-
|
|
8147
|
-
export { placeholderInheritedColor as $, relsPartNameFor as $t, flattenAnimations as A, CSLD_AFTER_BG as At, readColorRef as B, SLIDE_AFTER_TRANSITION as Bt, ChartSeries as C, styleRefFill as Ct, Shape as D, presetColorHex as Dt, AutoShape as E, themeElementsOfRoot as Et, Paragraph as F, PRESENTATION_AFTER_SLD_ID_LST as Ft, resolveSlideColorContext as G, SPPR_LN_AFTER as Gt, resolveLayoutColorContext as H, SPPR_AFTER_XFRM as Ht, Run as I, PRESENTATION_AFTER_SLD_MASTER_ID_LST as It, checkPositiveEmu as J, TXBODY_AFTER_BODYPR as Jt, resolveSlideThemeParts as K, SPPR_SCENE3D_AFTER as Kt, readPatternFill as L, RPR_FILL_AFTER as Lt, pruneSpids as M, LN_SLOTS as Mt, remapSpids as N, PRESENTATION_AFTER_EMBEDDED_FONT_LST as Nt, carryShapeAnimations as O, hasFillChoice as Ot, TextFrame as P, PRESENTATION_AFTER_NOTES_MASTER_ID_LST as Pt, placeholderInheritedAnchor as Q, relativePartName as Qt, readPictureFill as R, RUN_AFTER_RPR as Rt, ChartAxis as S, resolveSchemeToken as St, Picture as T, substitutePhClr as Tt, resolveMasterColorContext as U, SPPR_EFFECT_AFTER as Ut, resolveColorElement as V, SLIDE_MASTER_AFTER_SLD_LAYOUT_ID_LST as Vt, resolveNotesColorContext as W, SPPR_FILL_AFTER as Wt, RUN_PROP_NAMES as X, partNameExtension as Xt, LST_STYLE_LEVELS as Y, TXBODY_AFTER_LSTSTYLE as Yt, lstStyleLevelDefRPr as Z, partNameToZipPath as Zt, DiagramPoint as _, parseXml as _n, isA as _t, isConnector as a, boolAttr as an, slideDefinesProp as at, ChartExSeries as b, serializeXml as bn, replaceColorElement as bt, isPicture as c, descendantsByTag as cn, carriedDecorations as ct, Table as d, getElements as dn, nextDrawingId as dt, resolveRelativePartName as en, placeholderInheritedListStyles as et, TableRow as f, getOrAddChild as fn, nthShapeChild as ft, DiagramNode as g, ownerDocumentOf as gn, fmtEntry as gt, Diagram as h, numberValue as hn, spTreeOf as ht, isAutoShape as i, attr as in, slideDefinesColor as it, hasAnimations as j, EMBEDDED_FONT_ENTRY_AFTER as jt, enumerateSpids as k, applyColorTransforms as kt, wrapShapeElement as l, firstChild as ln, collectElements as lt, resolveTableCellTextStyle as m, numberAttr as mn, reassignDrawingIds as mt, buildShapes as n, singleRelPart as nn, placeholderInheritedXfrm as nt, isGraphicFrame as o, childElements as on, cSldName as ot, TableCell as p, insertInOrder as pn, nvPrOf as pt, checkFiniteEmu as q, SPPR_SP3D_AFTER as qt, findShapeByIdDeep as r, zipPathToPartName as rn, placeholderOf as rt, isGroupShape as s, createElement as sn, cSldOf as st, GroupShape as t, resolveSingleRel as tn, placeholderInheritedRunProps as tt, GraphicFrame as u, firstChildElement as un, firstShapeChild as ut, ChartEx as v, removeAttr as vn, parseClrMap as vt, Connector as w, styleRefLine as wt, Chart as x, setAttr as xn, resolveColor as xt, ChartExAxis as y, removeChildrenByQName as yn, parseClrScheme as yt, readGradientFill as z, SHAPE_AFTER_SPPR as zt };
|
|
9220
|
+
export { LST_STYLE_LEVELS as $, TXBODY_AFTER_LSTSTYLE as $t, enumerateSpids as A, themeElementsOfRoot as At, composeGroupFrame as B, PRESENTATION_AFTER_SLD_MASTER_ID_LST as Bt, ChartPlotGroup as C, removeChildrenByQName as Cn, parseClrScheme as Ct, AutoShape as D, styleRefFill as Dt, Picture as E, resolveSchemeToken as Et, remapSpids as F, EMBEDDED_FONT_ENTRY_AFTER as Ft, resolveColorElement as G, SLIDE_MASTER_AFTER_SLD_LAYOUT_ID_LST as Gt, readGradientFill as H, RUN_AFTER_RPR as Ht, TextFrame as I, LN_SLOTS as It, resolveNotesColorContext as J, SPPR_FILL_AFTER as Jt, resolveLayoutColorContext as K, SPPR_AFTER_XFRM as Kt, Paragraph as L, PRESENTATION_AFTER_EMBEDDED_FONT_LST as Lt, hasAnimations as M, hasFillChoice as Mt, pruneSpids as N, applyColorTransforms as Nt, Shape as O, styleRefLine as Ot, readBuildEffects as P, CSLD_AFTER_BG as Pt, checkPositiveEmu as Q, TXBODY_AFTER_BODYPR as Qt, Field as R, PRESENTATION_AFTER_NOTES_MASTER_ID_LST as Rt, ChartAxis as S, removeAttr as Sn, parseClrMap as St, Connector as T, setAttr as Tn, resolveColor as Tt, readPictureFill as U, SHAPE_AFTER_SPPR as Ut, readPatternFill as V, RPR_FILL_AFTER as Vt, readColorRef as W, SLIDE_AFTER_TRANSITION as Wt, resolveSlideThemeParts as X, SPPR_SCENE3D_AFTER as Xt, resolveSlideColorContext as Y, SPPR_LN_AFTER as Yt, checkFiniteEmu as Z, SPPR_SP3D_AFTER as Zt, DiagramPoint as _, insertInOrder as _n, nvPrOf as _t, isConnector as a, resolveSingleRel as an, placeholderInheritedRunProps as at, ChartExSeries as b, ownerDocumentOf as bn, fmtEntry as bt, isPicture as c, attr as cn, slideDefinesColor as ct, Table as d, createElement as dn, cSldOf as dt, partNameExtension as en, RUN_PROP_NAMES as et, TableRow as f, descendantsByTag as fn, carriedDecorations as ft, DiagramNode as g, getOrAddChild as gn, nthShapeChild as gt, Diagram as h, getElements as hn, nextDrawingId as ht, isAutoShape as i, resolveRelativePartName as in, placeholderInheritedListStyles as it, flattenAnimations as j, presetColorHex as jt, carryShapeAnimations as k, substitutePhClr as kt, wrapShapeElement as l, boolAttr as ln, slideDefinesProp as lt, resolveTableCellTextStyle as m, firstChildElement as mn, firstShapeChild as mt, buildShapes as n, relativePartName as nn, placeholderInheritedAnchor as nt, isGraphicFrame as o, singleRelPart as on, placeholderInheritedXfrm as ot, TableCell as p, firstChild as pn, collectElements as pt, resolveMasterColorContext as q, SPPR_EFFECT_AFTER as qt, findShapeByIdDeep as r, relsPartNameFor as rn, placeholderInheritedColor as rt, isGroupShape as s, zipPathToPartName as sn, placeholderOf as st, GroupShape as t, partNameToZipPath as tn, lstStyleLevelDefRPr as tt, GraphicFrame as u, childElements as un, cSldName as ut, ChartEx as v, numberAttr as vn, reassignDrawingIds as vt, ChartSeries as w, serializeXml as wn, replaceColorElement as wt, Chart as x, parseXml as xn, isA as xt, ChartExAxis as y, numberValue as yn, spTreeOf as yt, Run as z, PRESENTATION_AFTER_SLD_ID_LST as zt };
|
|
8148
9221
|
|
|
8149
|
-
//# sourceMappingURL=shapes-
|
|
9222
|
+
//# sourceMappingURL=shapes-CAPOjU0Q.js.map
|