@shbernal/ts-pptx 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/{autopage-BYlTzX-l.js → autopage-DkjYZUFD.js} +3 -3
  2. package/dist/{autopage-BYlTzX-l.js.map → autopage-DkjYZUFD.js.map} +1 -1
  3. package/dist/browser.d.ts +5 -5
  4. package/dist/browser.js +4 -4
  5. package/dist/browser.js.map +1 -1
  6. package/dist/{diagnostics-B0OmTOoz.d.ts → diagnostics-Du_s75MT.d.ts} +20 -5
  7. package/dist/diagnostics-Du_s75MT.d.ts.map +1 -0
  8. package/dist/{errors-fB1KDJKQ.d.ts → errors-PflMPWBn.d.ts} +3 -3
  9. package/dist/{errors-fB1KDJKQ.d.ts.map → errors-PflMPWBn.d.ts.map} +1 -1
  10. package/dist/{fit-TjpNbMru.js → fit-BKs0zxMI.js} +3 -3
  11. package/dist/{fit-TjpNbMru.js.map → fit-BKs0zxMI.js.map} +1 -1
  12. package/dist/{html-dom-yUSdCExA.js → html-dom-B0I0QSbU.js} +4 -4
  13. package/dist/{html-dom-yUSdCExA.js.map → html-dom-B0I0QSbU.js.map} +1 -1
  14. package/dist/html.d.ts +3 -3
  15. package/dist/html.js +1 -1
  16. package/dist/index.d.ts +5 -5
  17. package/dist/index.js +3 -3
  18. package/dist/inspect.d.ts +28 -4
  19. package/dist/inspect.d.ts.map +1 -1
  20. package/dist/inspect.js +42 -11
  21. package/dist/inspect.js.map +1 -1
  22. package/dist/math.d.ts +1 -1
  23. package/dist/measure.d.ts +2 -2
  24. package/dist/measure.js +1 -1
  25. package/dist/{namespaces-Sy_m7Nhd.js → namespaces-BVnNu7sg.js} +77 -3
  26. package/dist/namespaces-BVnNu7sg.js.map +1 -0
  27. package/dist/node.d.ts +5 -5
  28. package/dist/node.js +3 -3
  29. package/dist/{notes-bWApshah.js → notes-RLCwdlTE.js} +289 -313
  30. package/dist/{notes-bWApshah.js.map → notes-RLCwdlTE.js.map} +1 -1
  31. package/dist/{pres-Dybf7ZUl.d.ts → pres-C5__xkDh.d.ts} +2 -2
  32. package/dist/{pres-Dybf7ZUl.d.ts.map → pres-C5__xkDh.d.ts.map} +1 -1
  33. package/dist/{presentation-3rAKKRfm.js → presentation-Cp7vzlAw.js} +566 -519
  34. package/dist/presentation-Cp7vzlAw.js.map +1 -0
  35. package/dist/{presentation-types-D7cgT-eG.d.ts → presentation-types-DP_8jog7.d.ts} +514 -276
  36. package/dist/presentation-types-DP_8jog7.d.ts.map +1 -0
  37. package/dist/read.d.ts +31 -48
  38. package/dist/read.d.ts.map +1 -1
  39. package/dist/read.js +3 -3
  40. package/dist/script.d.ts +2 -2
  41. package/dist/script.d.ts.map +1 -1
  42. package/dist/script.js +84 -75
  43. package/dist/script.js.map +1 -1
  44. package/dist/{shapes-Dyw5bxwO.js → shapes-BemroU-z.js} +1010 -306
  45. package/dist/shapes-BemroU-z.js.map +1 -0
  46. package/dist/{slide-B_aInw3N.d.ts → slide-js0QpRV-.d.ts} +1278 -1254
  47. package/dist/slide-js0QpRV-.d.ts.map +1 -0
  48. package/dist/{text-DSN3anEh.js → text-CyetbF6p.js} +2039 -1417
  49. package/dist/text-CyetbF6p.js.map +1 -0
  50. package/dist/units-1kRzYShk.js +231 -0
  51. package/dist/units-1kRzYShk.js.map +1 -0
  52. package/dist/units-internal-Dg95w_YD.js +753 -0
  53. package/dist/units-internal-Dg95w_YD.js.map +1 -0
  54. package/dist/zip.d.ts +1 -1
  55. package/package.json +2 -1
  56. package/dist/diagnostics-B0OmTOoz.d.ts.map +0 -1
  57. package/dist/diagnostics-CPVADSL1.js +0 -90
  58. package/dist/diagnostics-CPVADSL1.js.map +0 -1
  59. package/dist/namespaces-Sy_m7Nhd.js.map +0 -1
  60. package/dist/presentation-3rAKKRfm.js.map +0 -1
  61. package/dist/presentation-types-D7cgT-eG.d.ts.map +0 -1
  62. package/dist/shapes-Dyw5bxwO.js.map +0 -1
  63. package/dist/slide-B_aInw3N.d.ts.map +0 -1
  64. package/dist/text-DSN3anEh.js.map +0 -1
  65. package/dist/units-internal-DNvK2I6o.js +0 -896
  66. package/dist/units-internal-DNvK2I6o.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { r as warn } from "./diagnostics-CPVADSL1.js";
1
+ import { i as EMU_PER_POINT, s as PERCENT_SCALE, t as ANGLE_UNITS_PER_DEGREE, x as warn } from "./units-1kRzYShk.js";
2
2
  import { i as PackageReadError, n as InvalidOptionError, o as UnsupportedFeatureError, t as InternalError } from "./errors-Bvln0H-7.js";
3
- import { A as TEXT_ANCHORS, M as TEXT_VERTICAL, N as checkEnumOrThrow, _ as THEME_REL, a as NOTES_MASTER_REL, b as getImageSizeFromBytes, c as OFFICE_DOCUMENT_REL, f as SLIDE_LAYOUT_REL, j as TEXT_HORZ_OVERFLOW, m as SLIDE_MASTER_REL, r as IMAGE_REL, t as OOXML_NS, v as fitSrcRectPercents } from "./namespaces-Sy_m7Nhd.js";
3
+ import { F as TEXT_VERTICAL, L as checkEnumOrThrow, N as TEXT_ANCHORS, P as TEXT_HORZ_OVERFLOW, a as NOTES_MASTER_REL, c as OFFICE_DOCUMENT_REL, h as SLIDE_MASTER_REL, p as SLIDE_LAYOUT_REL, r as IMAGE_REL, t as OOXML_NS, v as THEME_REL, x as getImageSizeFromBytes, y as fitSrcRectPercents } from "./namespaces-BVnNu7sg.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) {
@@ -458,6 +458,439 @@ function setSolidFill(parent, afterOrder, color) {
458
458
  fill.appendChild(clr);
459
459
  }
460
460
  //#endregion
461
+ //#region src/read/oxml/color-transform.ts
462
+ /**
463
+ * Apply DrawingML colour-transform modifiers (`lumMod`/`lumOff`/`shade`/`tint`/
464
+ * `satMod`/`alpha`/…) to a base sRGB colour, returning the *effective* colour a
465
+ * renderer would paint.
466
+ *
467
+ * The read model resolves a `schemeClr`/`srgbClr` reference to its **base** hex
468
+ * and carries the transform children verbatim (so the `theme: 'preserve'` flatten
469
+ * path can re-emit them byte-for-byte). This module is the missing step that
470
+ * turns that base hex + transform list into the final rendered hex, so a consumer
471
+ * never hand-computes a tint/shade again.
472
+ *
473
+ * Colour-space model (verified against PowerPoint output — see
474
+ * `test/read/color-transform.test.js`): the working colour is kept canonical as
475
+ * sRGB between transforms; each transform converts into the space its family
476
+ * operates in, applies, and converts back to sRGB. Transforms apply in **document
477
+ * order** (ECMA-376 §20.1.2.3).
478
+ *
479
+ * - `lumMod` / `lumOff` — luminance (L) in HSL: `L' = L·mod + off`. PowerPoint's
480
+ * "Lighter/Darker N%" family.
481
+ * - `satMod` / `satOff` — saturation (S) in HSL.
482
+ * - `hueMod` / `hueOff` — hue (H) in HSL (`hueMod` scales, `hueOff` offsets by
483
+ * degrees). Not exercised by any current source; implemented for completeness.
484
+ * - `shade` — toward black in **linear** RGB: `c' = c·k`.
485
+ * - `tint` — toward white in **linear** RGB: `c' = c·k + (1−k)`.
486
+ * - `alpha` / `alphaMod` / `alphaOff` — opacity, returned alongside the hex
487
+ * (never folded into RGB).
488
+ * - `comp` / `inv` / `gray` / `gamma` / `invGamma` — pass-through for now (no
489
+ * current source needs them; add when one does).
490
+ *
491
+ * All DrawingML percentage values are stored in thousandths of a percent
492
+ * (`100%` → `100000`), so a modifier's fraction is `val / PERCENT_SCALE`.
493
+ */
494
+ /**
495
+ * DrawingML percentage → fraction, or `null` when unparseable.
496
+ *
497
+ * `a:ST_Percentage` is a *union* in the Transitional profile: the fixed-point integer form
498
+ * Office writes (`100%` → `100000`) and a decimal string with a literal `%` (`-?[0-9]+(\.[0-9]+)?%`),
499
+ * which is the only form the Strict profile has. Both are read here — a reader that took only the
500
+ * first dropped a schema-legal value silently, and the two are one `endsWith` apart.
501
+ * @param value - the raw attribute value, or `null` when the attribute is absent
502
+ */
503
+ function parsePercent(value) {
504
+ if (value === null || value === "") return null;
505
+ if (value.endsWith("%")) {
506
+ const n = Number(value.slice(0, -1));
507
+ return Number.isFinite(n) ? n / 100 : null;
508
+ }
509
+ const n = Number(value);
510
+ return Number.isFinite(n) ? n / PERCENT_SCALE : null;
511
+ }
512
+ const clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
513
+ /** Parse a 6-hex string (`RRGGBB`, optional `#`) into 0–1 sRGB channels, or `null`. */
514
+ function parseHex(hex) {
515
+ const h = hex.startsWith("#") ? hex.slice(1) : hex;
516
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
517
+ return {
518
+ r: parseInt(h.slice(0, 2), 16) / 255,
519
+ g: parseInt(h.slice(2, 4), 16) / 255,
520
+ b: parseInt(h.slice(4, 6), 16) / 255
521
+ };
522
+ }
523
+ /** 0–1 sRGB channels → uppercase `RRGGBB`, rounding and clamping each channel. */
524
+ function toHex(c) {
525
+ const ch = (v) => Math.round(clamp01(v) * 255).toString(16).toUpperCase().padStart(2, "0");
526
+ return `${ch(c.r)}${ch(c.g)}${ch(c.b)}`;
527
+ }
528
+ /** sRGB channel (0–1) → linear-light (gamma decode). */
529
+ function srgbToLinear(c) {
530
+ return c <= .04045 ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4);
531
+ }
532
+ /** Linear-light channel (0–1) → sRGB (gamma encode). */
533
+ function linearToSrgb(c) {
534
+ return c <= .0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - .055;
535
+ }
536
+ /** sRGB (0–1) → HSL with H in degrees [0,360), S/L in [0,1]. */
537
+ function rgbToHsl(c) {
538
+ const { r, g, b } = c;
539
+ const max = Math.max(r, g, b);
540
+ const min = Math.min(r, g, b);
541
+ const l = (max + min) / 2;
542
+ const d = max - min;
543
+ if (d === 0) return {
544
+ h: 0,
545
+ s: 0,
546
+ l
547
+ };
548
+ const s = d / (1 - Math.abs(2 * l - 1));
549
+ let h;
550
+ if (max === r) h = (g - b) / d % 6;
551
+ else if (max === g) h = (b - r) / d + 2;
552
+ else h = (r - g) / d + 4;
553
+ h *= 60;
554
+ if (h < 0) h += 360;
555
+ return {
556
+ h,
557
+ s,
558
+ l
559
+ };
560
+ }
561
+ /** HSL (H degrees, S/L 0–1) → sRGB (0–1). */
562
+ function hslToRgb({ h, s, l }) {
563
+ const c = (1 - Math.abs(2 * l - 1)) * s;
564
+ const hp = (h % 360 + 360) % 360 / 60;
565
+ const x = c * (1 - Math.abs(hp % 2 - 1));
566
+ const [r, g, b] = hp < 1 ? [
567
+ c,
568
+ x,
569
+ 0
570
+ ] : hp < 2 ? [
571
+ x,
572
+ c,
573
+ 0
574
+ ] : hp < 3 ? [
575
+ 0,
576
+ c,
577
+ x
578
+ ] : hp < 4 ? [
579
+ 0,
580
+ x,
581
+ c
582
+ ] : hp < 5 ? [
583
+ x,
584
+ 0,
585
+ c
586
+ ] : [
587
+ c,
588
+ 0,
589
+ x
590
+ ];
591
+ const m = l - c / 2;
592
+ return {
593
+ r: r + m,
594
+ g: g + m,
595
+ b: b + m
596
+ };
597
+ }
598
+ /**
599
+ * An `a:hslClr` triple → 6-hex sRGB, or `null` when any of the three is unparseable.
600
+ *
601
+ * The colour space is the one `lumMod`/`satMod` already work in — DrawingML's HSL is sRGB-HSL,
602
+ * which is what the transform family below is verified against — so this shares that conversion
603
+ * rather than introducing a second one. Units are the schema's: `@hue` is
604
+ * `a:ST_PositiveFixedAngle`, i.e. 60,000ths of a degree, and `@sat`/`@lum` are `a:ST_Percentage`
605
+ * (see {@link parsePercent}). Saturation and luminance are clamped to 0–1; hue wraps.
606
+ * @param hue - raw `@hue`
607
+ * @param sat - raw `@sat`
608
+ * @param lum - raw `@lum`
609
+ */
610
+ function hslClrToHex(hue, sat, lum) {
611
+ const h = hue === null || hue === "" ? null : Number(hue);
612
+ const s = parsePercent(sat);
613
+ const l = parsePercent(lum);
614
+ if (h === null || !Number.isFinite(h) || s === null || l === null) return null;
615
+ return toHex(hslToRgb({
616
+ h: h / ANGLE_UNITS_PER_DEGREE,
617
+ s: clamp01(s),
618
+ l: clamp01(l)
619
+ }));
620
+ }
621
+ /**
622
+ * Apply an ordered list of DrawingML colour transforms to a base sRGB hex,
623
+ * returning the effective sRGB hex (and opacity, when an `alpha*` modifier set
624
+ * it). Pure: no DOM, no theme lookup — the caller has already resolved the base
625
+ * colour and extracted each modifier's `{ name, value }`.
626
+ *
627
+ * An unparseable base hex is returned unchanged (with no transforms applied); an
628
+ * unparseable/`null` modifier value is skipped.
629
+ */
630
+ function applyColorTransforms(baseHex, transforms) {
631
+ const parsed = parseHex(baseHex);
632
+ if (!parsed) return { hex: baseHex };
633
+ let rgb = parsed;
634
+ let alpha;
635
+ for (const { name, value } of transforms) switch (name) {
636
+ case "lumMod":
637
+ case "lumOff": {
638
+ const f = parsePercent(value);
639
+ if (f === null) break;
640
+ const hsl = rgbToHsl(rgb);
641
+ hsl.l = clamp01(name === "lumMod" ? hsl.l * f : hsl.l + f);
642
+ rgb = hslToRgb(hsl);
643
+ break;
644
+ }
645
+ case "satMod":
646
+ case "satOff": {
647
+ const f = parsePercent(value);
648
+ if (f === null) break;
649
+ const hsl = rgbToHsl(rgb);
650
+ hsl.s = clamp01(name === "satMod" ? hsl.s * f : hsl.s + f);
651
+ rgb = hslToRgb(hsl);
652
+ break;
653
+ }
654
+ case "hueMod":
655
+ case "hueOff": {
656
+ const hsl = rgbToHsl(rgb);
657
+ if (name === "hueMod") {
658
+ const f = parsePercent(value);
659
+ if (f === null) break;
660
+ hsl.h = (hsl.h * f % 360 + 360) % 360;
661
+ } else {
662
+ const n = value === null ? NaN : Number(value);
663
+ if (!Number.isFinite(n)) break;
664
+ hsl.h = ((hsl.h + n / ANGLE_UNITS_PER_DEGREE) % 360 + 360) % 360;
665
+ }
666
+ rgb = hslToRgb(hsl);
667
+ break;
668
+ }
669
+ case "shade":
670
+ case "tint": {
671
+ const k = parsePercent(value);
672
+ if (k === null) break;
673
+ const lin = {
674
+ r: srgbToLinear(rgb.r),
675
+ g: srgbToLinear(rgb.g),
676
+ b: srgbToLinear(rgb.b)
677
+ };
678
+ const apply = (c) => clamp01(name === "shade" ? c * k : c * k + (1 - k));
679
+ rgb = {
680
+ r: linearToSrgb(apply(lin.r)),
681
+ g: linearToSrgb(apply(lin.g)),
682
+ b: linearToSrgb(apply(lin.b))
683
+ };
684
+ break;
685
+ }
686
+ case "alpha": {
687
+ const f = parsePercent(value);
688
+ if (f !== null) alpha = clamp01(f);
689
+ break;
690
+ }
691
+ case "alphaMod": {
692
+ const f = parsePercent(value);
693
+ if (f !== null) alpha = clamp01((alpha ?? 1) * f);
694
+ break;
695
+ }
696
+ case "alphaOff": {
697
+ const f = parsePercent(value);
698
+ if (f !== null) alpha = clamp01((alpha ?? 1) + f);
699
+ break;
700
+ }
701
+ }
702
+ return alpha === void 0 ? { hex: toHex(rgb) } : {
703
+ hex: toHex(rgb),
704
+ alpha
705
+ };
706
+ }
707
+ //#endregion
708
+ //#region src/read/oxml/preset-color.ts
709
+ /**
710
+ * The ECMA-376 preset colour table (`a:prstClr/@val` → literal RGB).
711
+ *
712
+ * `ST_PresetColorVal` (§20.1.10.47) enumerates 190 names, and the standard gives each one an
713
+ * RGB triple. They are the HTML/CSS named colours: every value in the enumeration has the same
714
+ * RGB here as the CSS colour of the same name, and the 190 names collapse to the 140 distinct
715
+ * CSS names below through three mechanical spelling rules the enumeration adds on top —
716
+ *
717
+ * - abbreviated prefixes: `dk*`/`lt*`/`med*` alongside `dark*`/`light*`/`medium*`
718
+ * (`dkSlateBlue` is `darkSlateBlue`), and only as a *prefix* — `medium…` is not `mediumium…`;
719
+ * - both spellings of grey: `dimGrey` is `dimGray`;
720
+ * - case: the enumeration is camelCase, the table below is folded to lower case.
721
+ *
722
+ * So the table holds one row per colour rather than one per spelling, and {@link presetColorHex}
723
+ * normalizes a name into it. That is the difference between 140 hand-checked constants and 190,
724
+ * and it makes the abbreviation rule something a reader can see rather than something spread
725
+ * across fifty near-duplicate rows.
726
+ *
727
+ * This is spec data, not observed behaviour: the values come from the enumeration's own
728
+ * documentation, and the table is asserted complete against the schema's 190-value list in
729
+ * `test/read/preset-color.test.js` — a name the reader cannot resolve is a hole in the table,
730
+ * and that test is what finds it.
731
+ */
732
+ /**
733
+ * Canonical (lower-case, unabbreviated, `gray`-spelled) preset colour name → 6-hex RGB.
734
+ * Reach it through {@link presetColorHex}, which does the normalizing.
735
+ */
736
+ const PRESET_COLOR_HEX = {
737
+ aliceblue: "F0F8FF",
738
+ antiquewhite: "FAEBD7",
739
+ aqua: "00FFFF",
740
+ aquamarine: "7FFFD4",
741
+ azure: "F0FFFF",
742
+ beige: "F5F5DC",
743
+ bisque: "FFE4C4",
744
+ black: "000000",
745
+ blanchedalmond: "FFEBCD",
746
+ blue: "0000FF",
747
+ blueviolet: "8A2BE2",
748
+ brown: "A52A2A",
749
+ burlywood: "DEB887",
750
+ cadetblue: "5F9EA0",
751
+ chartreuse: "7FFF00",
752
+ chocolate: "D2691E",
753
+ coral: "FF7F50",
754
+ cornflowerblue: "6495ED",
755
+ cornsilk: "FFF8DC",
756
+ crimson: "DC143C",
757
+ cyan: "00FFFF",
758
+ darkblue: "00008B",
759
+ darkcyan: "008B8B",
760
+ darkgoldenrod: "B8860B",
761
+ darkgray: "A9A9A9",
762
+ darkgreen: "006400",
763
+ darkkhaki: "BDB76B",
764
+ darkmagenta: "8B008B",
765
+ darkolivegreen: "556B2F",
766
+ darkorange: "FF8C00",
767
+ darkorchid: "9932CC",
768
+ darkred: "8B0000",
769
+ darksalmon: "E9967A",
770
+ darkseagreen: "8FBC8F",
771
+ darkslateblue: "483D8B",
772
+ darkslategray: "2F4F4F",
773
+ darkturquoise: "00CED1",
774
+ darkviolet: "9400D3",
775
+ deeppink: "FF1493",
776
+ deepskyblue: "00BFFF",
777
+ dimgray: "696969",
778
+ dodgerblue: "1E90FF",
779
+ firebrick: "B22222",
780
+ floralwhite: "FFFAF0",
781
+ forestgreen: "228B22",
782
+ fuchsia: "FF00FF",
783
+ gainsboro: "DCDCDC",
784
+ ghostwhite: "F8F8FF",
785
+ gold: "FFD700",
786
+ goldenrod: "DAA520",
787
+ gray: "808080",
788
+ green: "008000",
789
+ greenyellow: "ADFF2F",
790
+ honeydew: "F0FFF0",
791
+ hotpink: "FF69B4",
792
+ indianred: "CD5C5C",
793
+ indigo: "4B0082",
794
+ ivory: "FFFFF0",
795
+ khaki: "F0E68C",
796
+ lavender: "E6E6FA",
797
+ lavenderblush: "FFF0F5",
798
+ lawngreen: "7CFC00",
799
+ lemonchiffon: "FFFACD",
800
+ lightblue: "ADD8E6",
801
+ lightcoral: "F08080",
802
+ lightcyan: "E0FFFF",
803
+ lightgoldenrodyellow: "FAFAD2",
804
+ lightgray: "D3D3D3",
805
+ lightgreen: "90EE90",
806
+ lightpink: "FFB6C1",
807
+ lightsalmon: "FFA07A",
808
+ lightseagreen: "20B2AA",
809
+ lightskyblue: "87CEFA",
810
+ lightslategray: "778899",
811
+ lightsteelblue: "B0C4DE",
812
+ lightyellow: "FFFFE0",
813
+ lime: "00FF00",
814
+ limegreen: "32CD32",
815
+ linen: "FAF0E6",
816
+ magenta: "FF00FF",
817
+ maroon: "800000",
818
+ mediumaquamarine: "66CDAA",
819
+ mediumblue: "0000CD",
820
+ mediumorchid: "BA55D3",
821
+ mediumpurple: "9370DB",
822
+ mediumseagreen: "3CB371",
823
+ mediumslateblue: "7B68EE",
824
+ mediumspringgreen: "00FA9A",
825
+ mediumturquoise: "48D1CC",
826
+ mediumvioletred: "C71585",
827
+ midnightblue: "191970",
828
+ mintcream: "F5FFFA",
829
+ mistyrose: "FFE4E1",
830
+ moccasin: "FFE4B5",
831
+ navajowhite: "FFDEAD",
832
+ navy: "000080",
833
+ oldlace: "FDF5E6",
834
+ olive: "808000",
835
+ olivedrab: "6B8E23",
836
+ orange: "FFA500",
837
+ orangered: "FF4500",
838
+ orchid: "DA70D6",
839
+ palegoldenrod: "EEE8AA",
840
+ palegreen: "98FB98",
841
+ paleturquoise: "AFEEEE",
842
+ palevioletred: "DB7093",
843
+ papayawhip: "FFEFD5",
844
+ peachpuff: "FFDAB9",
845
+ peru: "CD853F",
846
+ pink: "FFC0CB",
847
+ plum: "DDA0DD",
848
+ powderblue: "B0E0E6",
849
+ purple: "800080",
850
+ red: "FF0000",
851
+ rosybrown: "BC8F8F",
852
+ royalblue: "4169E1",
853
+ saddlebrown: "8B4513",
854
+ salmon: "FA8072",
855
+ sandybrown: "F4A460",
856
+ seagreen: "2E8B57",
857
+ seashell: "FFF5EE",
858
+ sienna: "A0522D",
859
+ silver: "C0C0C0",
860
+ skyblue: "87CEEB",
861
+ slateblue: "6A5ACD",
862
+ slategray: "708090",
863
+ snow: "FFFAFA",
864
+ springgreen: "00FF7F",
865
+ steelblue: "4682B4",
866
+ tan: "D2B48C",
867
+ teal: "008080",
868
+ thistle: "D8BFD8",
869
+ tomato: "FF6347",
870
+ turquoise: "40E0D0",
871
+ violet: "EE82EE",
872
+ wheat: "F5DEB3",
873
+ white: "FFFFFF",
874
+ whitesmoke: "F5F5F5",
875
+ yellow: "FFFF00",
876
+ yellowgreen: "9ACD32"
877
+ };
878
+ /**
879
+ * Resolve an `a:prstClr/@val` preset colour name to its 6-hex RGB, or `null` when the name is
880
+ * not one of the 190 `ST_PresetColorVal` values.
881
+ *
882
+ * Lenient about case for the same reason the rest of the read path is: the input is a file
883
+ * someone else wrote, and a producer that spelled `Black` has still said which colour it meant.
884
+ * A name that is not in the enumeration at all resolves to `null` rather than to a guess, so the
885
+ * caller reports "no colour" instead of painting an invented one.
886
+ * @param name - the raw attribute value, or `null` when the attribute is absent
887
+ */
888
+ function presetColorHex(name) {
889
+ if (!name) return null;
890
+ const canonical = name.toLowerCase().replace(/grey/g, "gray").replace(/^dk/, "dark").replace(/^lt/, "light").replace(/^med(?!ium)/, "medium");
891
+ return PRESET_COLOR_HEX[canonical] ?? null;
892
+ }
893
+ //#endregion
461
894
  //#region src/read/oxml/theme.ts
462
895
  /**
463
896
  * Theme colour resolution: turning a DrawingML colour reference or a style-matrix reference into
@@ -551,17 +984,34 @@ function resolveSchemeToken(token, ctx) {
551
984
  function isA(el, local) {
552
985
  return !!el && el.namespaceURI === OOXML_NS.a && el.localName === local;
553
986
  }
554
- /** The literal RGB of a colour element (`srgbClr`/`sysClr`), or `null` for others. */
987
+ /**
988
+ * The literal RGB of a *non-scheme* colour element, or `null` for one that needs the theme
989
+ * (`a:schemeClr`) or that this reader does not resolve.
990
+ *
991
+ * Four of `a:EG_ColorChoice`'s six members land here. `a:scrgbClr` is the one deliberately left
992
+ * out: its channels are percentages of a colour space the schema does not pin down, so whether
993
+ * `50%` is linear-light or sRGB-encoded decides the answer and neither the schema nor the
994
+ * transform work in `color-transform.ts` settles it. Guessing would produce a colour that is
995
+ * wrong by a gamma curve and says nothing about it, which is worse than reporting no colour.
996
+ * Closing it needs a render oracle — a deck with an `a:scrgbClr` fill authored in PowerPoint
997
+ * and the painted pixel read back — not a reading of the schema.
998
+ */
555
999
  function colorElementHex(color) {
556
1000
  if (!color) return null;
557
1001
  if (isA(color, "srgbClr")) return attr(color, "val");
558
1002
  if (isA(color, "sysClr")) return attr(color, "lastClr") ?? attr(color, "val");
1003
+ if (isA(color, "prstClr")) return presetColorHex(attr(color, "val"));
1004
+ if (isA(color, "hslClr")) return hslClrToHex(attr(color, "hue"), attr(color, "sat"), attr(color, "lum"));
559
1005
  return null;
560
1006
  }
561
1007
  /**
562
1008
  * Resolve a DrawingML colour element to a literal `{ hex, transforms }`, routing
563
1009
  * `a:schemeClr` through the context. Returns `null` when the base cannot be made
564
1010
  * literal (unmapped token, or a colour model we do not flatten).
1011
+ *
1012
+ * Five of `a:EG_ColorChoice`'s six members resolve: `a:srgbClr`, `a:sysClr`, `a:schemeClr`,
1013
+ * `a:prstClr` (through the {@link presetColorHex} table) and `a:hslClr`. `a:scrgbClr` does not —
1014
+ * see {@link colorElementHex} for why that one is a `null` rather than a guess.
565
1015
  */
566
1016
  function resolveColor(color, ctx) {
567
1017
  if (!color) return null;
@@ -573,7 +1023,7 @@ function resolveColor(color, ctx) {
573
1023
  transforms
574
1024
  } : null;
575
1025
  }
576
- if (isA(color, "sysClr")) {
1026
+ if (isA(color, "sysClr") || isA(color, "prstClr") || isA(color, "hslClr")) {
577
1027
  const hex = colorElementHex(color);
578
1028
  return hex ? {
579
1029
  hex,
@@ -1003,219 +1453,39 @@ function placeholderInheritedXfrm(type, idx, ctx) {
1003
1453
  return null;
1004
1454
  }
1005
1455
  /**
1006
- * The vertical anchor (`a:bodyPr/@anchor`) a placeholder inherits from the source
1007
- * layout, then master, or `null`. Resolves per-attribute: the first tier whose
1008
- * placeholder `a:bodyPr` actually sets `@anchor` wins, so a layout `a:bodyPr`
1009
- * present but without `@anchor` does not mask the master's. The read-model
1010
- * `TextFrame.resolvedAnchor` sibling of {@link placeholderInheritedXfrm}.
1011
- */
1012
- function placeholderInheritedAnchor(type, idx, ctx) {
1013
- for (const root of [ctx.layoutRoot, ctx.masterRoot]) {
1014
- if (!root) continue;
1015
- const ph = findPlaceholder(root, type, idx);
1016
- const txBody = ph && firstChild(ph, "p:txBody");
1017
- const bodyPr = txBody && firstChild(txBody, "a:bodyPr");
1018
- const anchor = bodyPr && attr(bodyPr, "anchor");
1019
- if (anchor) return anchor;
1020
- }
1021
- return null;
1022
- }
1023
- /** Whether the *slide itself* already fixes this run's colour (so a rebind cannot change it). */
1024
- function slideDefinesColor(run, pPr, slideLst, level) {
1025
- const rPr = firstChild(run, "a:rPr");
1026
- if (rPr && FILL_CHOICES.some((q) => firstChild(rPr, q))) return true;
1027
- const defRPr = pPr && firstChild(pPr, "a:defRPr");
1028
- if (defRPr && firstChild(defRPr, "a:solidFill")) return true;
1029
- return !!lstStyleLevelFill(slideLst, level);
1030
- }
1031
- /** Whether the *slide itself* already fixes a run property (so a rebind cannot change it). */
1032
- function slideDefinesProp(name, run, pPr, slideLst, level) {
1033
- const rPr = firstChild(run, "a:rPr");
1034
- if (rPr && attr(rPr, name) != null) return true;
1035
- const defRPr = pPr && firstChild(pPr, "a:defRPr");
1036
- if (defRPr && attr(defRPr, name) != null) return true;
1037
- const slideDefRPr = lstStyleLevelDefRPr(slideLst, level);
1038
- return !!(slideDefRPr && attr(slideDefRPr, name) != null);
1039
- }
1040
- //#endregion
1041
- //#region src/read/oxml/color-transform.ts
1042
- /** DrawingML percentage (`100%` = `100000`) → fraction, or `null` when unparseable. */
1043
- function pct(value) {
1044
- if (value === null || value === "") return null;
1045
- const n = Number(value);
1046
- return Number.isFinite(n) ? n / 1e5 : null;
1047
- }
1048
- const clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
1049
- /** Parse a 6-hex string (`RRGGBB`, optional `#`) into 0–1 sRGB channels, or `null`. */
1050
- function parseHex(hex) {
1051
- const h = hex.startsWith("#") ? hex.slice(1) : hex;
1052
- if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
1053
- return {
1054
- r: parseInt(h.slice(0, 2), 16) / 255,
1055
- g: parseInt(h.slice(2, 4), 16) / 255,
1056
- b: parseInt(h.slice(4, 6), 16) / 255
1057
- };
1058
- }
1059
- /** 0–1 sRGB channels → uppercase `RRGGBB`, rounding and clamping each channel. */
1060
- function toHex(c) {
1061
- const ch = (v) => Math.round(clamp01(v) * 255).toString(16).toUpperCase().padStart(2, "0");
1062
- return `${ch(c.r)}${ch(c.g)}${ch(c.b)}`;
1063
- }
1064
- /** sRGB channel (0–1) → linear-light (gamma decode). */
1065
- function srgbToLinear(c) {
1066
- return c <= .04045 ? c / 12.92 : Math.pow((c + .055) / 1.055, 2.4);
1067
- }
1068
- /** Linear-light channel (0–1) → sRGB (gamma encode). */
1069
- function linearToSrgb(c) {
1070
- return c <= .0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - .055;
1071
- }
1072
- /** sRGB (0–1) → HSL with H in degrees [0,360), S/L in [0,1]. */
1073
- function rgbToHsl(c) {
1074
- const { r, g, b } = c;
1075
- const max = Math.max(r, g, b);
1076
- const min = Math.min(r, g, b);
1077
- const l = (max + min) / 2;
1078
- const d = max - min;
1079
- if (d === 0) return {
1080
- h: 0,
1081
- s: 0,
1082
- l
1083
- };
1084
- const s = d / (1 - Math.abs(2 * l - 1));
1085
- let h;
1086
- if (max === r) h = (g - b) / d % 6;
1087
- else if (max === g) h = (b - r) / d + 2;
1088
- else h = (r - g) / d + 4;
1089
- h *= 60;
1090
- if (h < 0) h += 360;
1091
- return {
1092
- h,
1093
- s,
1094
- l
1095
- };
1096
- }
1097
- /** HSL (H degrees, S/L 0–1) → sRGB (0–1). */
1098
- function hslToRgb({ h, s, l }) {
1099
- const c = (1 - Math.abs(2 * l - 1)) * s;
1100
- const hp = (h % 360 + 360) % 360 / 60;
1101
- const x = c * (1 - Math.abs(hp % 2 - 1));
1102
- const [r, g, b] = hp < 1 ? [
1103
- c,
1104
- x,
1105
- 0
1106
- ] : hp < 2 ? [
1107
- x,
1108
- c,
1109
- 0
1110
- ] : hp < 3 ? [
1111
- 0,
1112
- c,
1113
- x
1114
- ] : hp < 4 ? [
1115
- 0,
1116
- x,
1117
- c
1118
- ] : hp < 5 ? [
1119
- x,
1120
- 0,
1121
- c
1122
- ] : [
1123
- c,
1124
- 0,
1125
- x
1126
- ];
1127
- const m = l - c / 2;
1128
- return {
1129
- r: r + m,
1130
- g: g + m,
1131
- b: b + m
1132
- };
1133
- }
1134
- /**
1135
- * Apply an ordered list of DrawingML colour transforms to a base sRGB hex,
1136
- * returning the effective sRGB hex (and opacity, when an `alpha*` modifier set
1137
- * it). Pure: no DOM, no theme lookup — the caller has already resolved the base
1138
- * colour and extracted each modifier's `{ name, value }`.
1139
- *
1140
- * An unparseable base hex is returned unchanged (with no transforms applied); an
1141
- * unparseable/`null` modifier value is skipped.
1456
+ * The vertical anchor (`a:bodyPr/@anchor`) a placeholder inherits from the source
1457
+ * layout, then master, or `null`. Resolves per-attribute: the first tier whose
1458
+ * placeholder `a:bodyPr` actually sets `@anchor` wins, so a layout `a:bodyPr`
1459
+ * present but without `@anchor` does not mask the master's. The read-model
1460
+ * `TextFrame.resolvedAnchor` sibling of {@link placeholderInheritedXfrm}.
1142
1461
  */
1143
- function applyColorTransforms(baseHex, transforms) {
1144
- const parsed = parseHex(baseHex);
1145
- if (!parsed) return { hex: baseHex };
1146
- let rgb = parsed;
1147
- let alpha;
1148
- for (const { name, value } of transforms) switch (name) {
1149
- case "lumMod":
1150
- case "lumOff": {
1151
- const f = pct(value);
1152
- if (f === null) break;
1153
- const hsl = rgbToHsl(rgb);
1154
- hsl.l = clamp01(name === "lumMod" ? hsl.l * f : hsl.l + f);
1155
- rgb = hslToRgb(hsl);
1156
- break;
1157
- }
1158
- case "satMod":
1159
- case "satOff": {
1160
- const f = pct(value);
1161
- if (f === null) break;
1162
- const hsl = rgbToHsl(rgb);
1163
- hsl.s = clamp01(name === "satMod" ? hsl.s * f : hsl.s + f);
1164
- rgb = hslToRgb(hsl);
1165
- break;
1166
- }
1167
- case "hueMod":
1168
- case "hueOff": {
1169
- const hsl = rgbToHsl(rgb);
1170
- if (name === "hueMod") {
1171
- const f = pct(value);
1172
- if (f === null) break;
1173
- hsl.h = (hsl.h * f % 360 + 360) % 360;
1174
- } else {
1175
- const n = value === null ? NaN : Number(value);
1176
- if (!Number.isFinite(n)) break;
1177
- hsl.h = ((hsl.h + n / 6e4) % 360 + 360) % 360;
1178
- }
1179
- rgb = hslToRgb(hsl);
1180
- break;
1181
- }
1182
- case "shade":
1183
- case "tint": {
1184
- const k = pct(value);
1185
- if (k === null) break;
1186
- const lin = {
1187
- r: srgbToLinear(rgb.r),
1188
- g: srgbToLinear(rgb.g),
1189
- b: srgbToLinear(rgb.b)
1190
- };
1191
- const apply = (c) => clamp01(name === "shade" ? c * k : c * k + (1 - k));
1192
- rgb = {
1193
- r: linearToSrgb(apply(lin.r)),
1194
- g: linearToSrgb(apply(lin.g)),
1195
- b: linearToSrgb(apply(lin.b))
1196
- };
1197
- break;
1198
- }
1199
- case "alpha": {
1200
- const f = pct(value);
1201
- if (f !== null) alpha = clamp01(f);
1202
- break;
1203
- }
1204
- case "alphaMod": {
1205
- const f = pct(value);
1206
- if (f !== null) alpha = clamp01((alpha ?? 1) * f);
1207
- break;
1208
- }
1209
- case "alphaOff": {
1210
- const f = pct(value);
1211
- if (f !== null) alpha = clamp01((alpha ?? 1) + f);
1212
- break;
1213
- }
1462
+ function placeholderInheritedAnchor(type, idx, ctx) {
1463
+ for (const root of [ctx.layoutRoot, ctx.masterRoot]) {
1464
+ if (!root) continue;
1465
+ const ph = findPlaceholder(root, type, idx);
1466
+ const txBody = ph && firstChild(ph, "p:txBody");
1467
+ const bodyPr = txBody && firstChild(txBody, "a:bodyPr");
1468
+ const anchor = bodyPr && attr(bodyPr, "anchor");
1469
+ if (anchor) return anchor;
1214
1470
  }
1215
- return alpha === void 0 ? { hex: toHex(rgb) } : {
1216
- hex: toHex(rgb),
1217
- alpha
1218
- };
1471
+ return null;
1472
+ }
1473
+ /** Whether the *slide itself* already fixes this run's colour (so a rebind cannot change it). */
1474
+ function slideDefinesColor(run, pPr, slideLst, level) {
1475
+ const rPr = firstChild(run, "a:rPr");
1476
+ if (rPr && FILL_CHOICES.some((q) => firstChild(rPr, q))) return true;
1477
+ const defRPr = pPr && firstChild(pPr, "a:defRPr");
1478
+ if (defRPr && firstChild(defRPr, "a:solidFill")) return true;
1479
+ return !!lstStyleLevelFill(slideLst, level);
1480
+ }
1481
+ /** Whether the *slide itself* already fixes a run property (so a rebind cannot change it). */
1482
+ function slideDefinesProp(name, run, pPr, slideLst, level) {
1483
+ const rPr = firstChild(run, "a:rPr");
1484
+ if (rPr && attr(rPr, name) != null) return true;
1485
+ const defRPr = pPr && firstChild(pPr, "a:defRPr");
1486
+ if (defRPr && attr(defRPr, name) != null) return true;
1487
+ const slideDefRPr = lstStyleLevelDefRPr(slideLst, level);
1488
+ return !!(slideDefRPr && attr(slideDefRPr, name) != null);
1219
1489
  }
1220
1490
  //#endregion
1221
1491
  //#region src/read/api/theme-context.ts
@@ -1459,22 +1729,44 @@ function resolveInheritedRunFontFace(ph, level, pPr, slideLstStyle, ctx) {
1459
1729
  return null;
1460
1730
  }
1461
1731
  /**
1462
- * Whether a run effectively renders bold when its own `a:rPr` sets no `@b`,
1463
- * walking the same chain as {@link resolveInheritedRunSize}: the first
1464
- * `a:defRPr/@b` (`1`/`true` → `true`, `0`/`false` → `false`) in the paragraph →
1732
+ * Whether a run effectively renders with the boolean character property `name`
1733
+ * (`b`/`i`) when its own `a:rPr` sets none, walking the same chain as
1734
+ * {@link resolveInheritedRunSize}: the first `a:defRPr/@<name>` in the paragraph →
1465
1735
  * slide → (placeholder) layout → master → `p:txStyles` → `p:defaultTextStyle`
1466
- * chain. A non-placeholder run skips the placeholder tiers but still reaches
1467
- * `p:defaultTextStyle` (which sets no `@b` in a PowerPoint-written deck, so bold
1468
- * stays `null` there). `null` when nothing in the chain defines bold.
1736
+ * chain. The first tier that *states* the attribute wins, and its value is parsed
1737
+ * by {@link boolValue} — the same `xsd:boolean` parser the run's own `bold`/`italic`
1738
+ * getters use — so an unparseable value reports `null` rather than being read as
1739
+ * `false`. `null` when nothing in the chain states it.
1469
1740
  */
1470
- function resolveInheritedRunBold(ph, level, pPr, slideLstStyle, ctx) {
1741
+ function resolveInheritedRunFlag(name, ph, level, pPr, slideLstStyle, ctx) {
1471
1742
  for (const defRPr of inheritedRunDefRPrs(ph, level, pPr, slideLstStyle, ctx)) {
1472
- const b = attr(defRPr, "b");
1473
- if (b !== null) return b === "1" || b === "true";
1743
+ const raw = attr(defRPr, name);
1744
+ if (raw !== null) return boolValue(raw);
1474
1745
  }
1475
1746
  return null;
1476
1747
  }
1477
1748
  /**
1749
+ * Whether a run effectively renders bold when its own `a:rPr` sets no `@b`: the
1750
+ * first `a:defRPr/@b` in the inheritance chain (see {@link resolveInheritedRunFlag}).
1751
+ * A non-placeholder run skips the placeholder tiers but still reaches
1752
+ * `p:defaultTextStyle` (which sets no `@b` in a PowerPoint-written deck, so bold
1753
+ * stays `null` there). `null` when nothing in the chain defines bold.
1754
+ */
1755
+ function resolveInheritedRunBold(ph, level, pPr, slideLstStyle, ctx) {
1756
+ return resolveInheritedRunFlag("b", ph, level, pPr, slideLstStyle, ctx);
1757
+ }
1758
+ /**
1759
+ * Whether a run effectively renders italic when its own `a:rPr` sets no `@i`: the
1760
+ * first `a:defRPr/@i` in the inheritance chain (see {@link resolveInheritedRunFlag}).
1761
+ * `@b` and `@i` are siblings on `CT_TextCharacterProperties` and a master text
1762
+ * style states them together, so this is the exact twin of
1763
+ * {@link resolveInheritedRunBold} one attribute along. `null` when nothing in the
1764
+ * chain defines italic.
1765
+ */
1766
+ function resolveInheritedRunItalic(ph, level, pPr, slideLstStyle, ctx) {
1767
+ return resolveInheritedRunFlag("i", ph, level, pPr, slideLstStyle, ctx);
1768
+ }
1769
+ /**
1478
1770
  * The vertical anchor a placeholder text frame effectively renders when its own
1479
1771
  * `a:bodyPr` sets no `@anchor`: the value inherited from the layout → master
1480
1772
  * placeholder `a:bodyPr` (see {@link placeholderInheritedAnchor}). `null` when the
@@ -1515,8 +1807,9 @@ function resolveInheritedFrame(ph, ctx) {
1515
1807
  };
1516
1808
  }
1517
1809
  /**
1518
- * Resolve a DrawingML colour *element* (`a:srgbClr`/`a:schemeClr`/`a:sysClr`)
1519
- * against `ctx` into a full {@link ResolvedColor} — base hex, raw transform list,
1810
+ * Resolve a DrawingML colour *element* (every `a:EG_ColorChoice` member except
1811
+ * `a:scrgbClr` — see `read/oxml/theme.ts`) against `ctx` into a full
1812
+ * {@link ResolvedColor} — base hex, raw transform list,
1520
1813
  * and the `effectiveHex`/`alpha` after applying those transforms. `null` when the
1521
1814
  * element cannot be made literal (unmapped token, or a colour model we do not
1522
1815
  * resolve). Shared by the solid-fill and gradient-stop colour reads.
@@ -1616,14 +1909,17 @@ function readGradientStops(container, ctx) {
1616
1909
  const gsLst = firstChild(grad, "a:gsLst");
1617
1910
  if (!gsLst) return [];
1618
1911
  return getElements(gsLst, "a:gs").map((gs) => {
1912
+ const colorEl = firstChildElement(gs);
1913
+ const model = colorEl?.localName ?? null;
1619
1914
  const pos = intValue(attr(gs, "pos"));
1620
- const srgb = firstChild(gs, "a:srgbClr");
1621
- const scheme = firstChild(gs, "a:schemeClr");
1622
- const resolved = resolveColorElement(srgb ?? scheme ?? null, ctx);
1915
+ const resolved = resolveColorElement(colorEl, ctx);
1916
+ const rawOf = (local) => colorEl && model === local ? attr(colorEl, "val") : null;
1623
1917
  return {
1624
- position: pos === null ? null : pos / 1e5,
1625
- color: srgb ? attr(srgb, "val") : null,
1626
- schemeColor: scheme ? attr(scheme, "val") : null,
1918
+ position: pos === null ? null : pos / PERCENT_SCALE,
1919
+ color: rawOf("srgbClr"),
1920
+ schemeColor: rawOf("schemeClr"),
1921
+ presetColor: rawOf("prstClr"),
1922
+ resolvedColor: resolved,
1627
1923
  effectiveHex: resolved ? resolved.effectiveHex : null,
1628
1924
  ...resolved?.alpha !== void 0 ? { alpha: resolved.alpha } : {}
1629
1925
  };
@@ -1642,7 +1938,7 @@ function readGradientFill(container, ctx) {
1642
1938
  const ang = lin ? intValue(attr(lin, "ang")) : null;
1643
1939
  return {
1644
1940
  kind: lin ? "linear" : path ? "path" : null,
1645
- angleDeg: ang === null ? null : ang / 6e4,
1941
+ angleDeg: ang === null ? null : ang / ANGLE_UNITS_PER_DEGREE,
1646
1942
  path: path ? attr(path, "path") ?? null : null,
1647
1943
  stops: readGradientStops(container, ctx) ?? []
1648
1944
  };
@@ -1664,7 +1960,7 @@ function readGradientFill(container, ctx) {
1664
1960
  function readRect(rect) {
1665
1961
  const edge = (name) => {
1666
1962
  const v = intValue(attr(rect, name));
1667
- return v === null ? 0 : v / 1e5;
1963
+ return v === null ? 0 : v / PERCENT_SCALE;
1668
1964
  };
1669
1965
  return {
1670
1966
  left: edge("l"),
@@ -1677,7 +1973,7 @@ function readRect(rect) {
1677
1973
  function readTile(tile) {
1678
1974
  const scale = (name) => {
1679
1975
  const v = intValue(attr(tile, name));
1680
- return v === null ? 1 : v / 1e5;
1976
+ return v === null ? 1 : v / PERCENT_SCALE;
1681
1977
  };
1682
1978
  return {
1683
1979
  offsetXEmu: intValue(attr(tile, "tx")) ?? 0,
@@ -1720,7 +2016,7 @@ function readPictureFill(container, rels = null) {
1720
2016
  srcRect: srcRect ? readRect(srcRect) : null,
1721
2017
  fillRect: fillRect ? readRect(fillRect) : null,
1722
2018
  tile: tile ? readTile(tile) : null,
1723
- alpha: alphaMod ? amt === null ? 1 : amt / 1e5 : null,
2019
+ alpha: alphaMod ? amt === null ? 1 : amt / PERCENT_SCALE : null,
1724
2020
  dpi: intValue(attr(blipFill, "dpi")),
1725
2021
  rotWithShape: boolValue(attr(blipFill, "rotWithShape"))
1726
2022
  };
@@ -1862,8 +2158,8 @@ function readGeometryPath(path) {
1862
2158
  cmd: "arcTo",
1863
2159
  wR: intValue(attr(seg, "wR")) ?? 0,
1864
2160
  hR: intValue(attr(seg, "hR")) ?? 0,
1865
- stAng: (intValue(attr(seg, "stAng")) ?? 0) / 6e4,
1866
- swAng: (intValue(attr(seg, "swAng")) ?? 0) / 6e4
2161
+ stAng: (intValue(attr(seg, "stAng")) ?? 0) / ANGLE_UNITS_PER_DEGREE,
2162
+ swAng: (intValue(attr(seg, "swAng")) ?? 0) / ANGLE_UNITS_PER_DEGREE
1867
2163
  });
1868
2164
  break;
1869
2165
  case "close": commands.push({ cmd: "close" });
@@ -1901,7 +2197,7 @@ function readBox(xfrm, offName, extName) {
1901
2197
  }
1902
2198
  function rotationDegrees(xfrm) {
1903
2199
  const rot = intValue(attr(xfrm, "rot"));
1904
- return rot === null ? 0 : rot / 6e4;
2200
+ return rot === null ? 0 : rot / ANGLE_UNITS_PER_DEGREE;
1905
2201
  }
1906
2202
  function transformFlipH(xfrm) {
1907
2203
  return boolValue(attr(xfrm, "flipH")) === true;
@@ -2045,9 +2341,10 @@ var Run = class {
2045
2341
  inheritedSize;
2046
2342
  inheritedFace;
2047
2343
  inheritedBold;
2344
+ inheritedItalic;
2048
2345
  relationships;
2049
2346
  fontRef;
2050
- constructor(element, part, themeContext, inheritedColor, inheritedSize, inheritedFace, inheritedBold, relationships, fontRef) {
2347
+ constructor(element, part, themeContext, inheritedColor, inheritedSize, inheritedFace, inheritedBold, inheritedItalic, relationships, fontRef) {
2051
2348
  this.element = element;
2052
2349
  this.part = part;
2053
2350
  this.themeContext = themeContext;
@@ -2055,6 +2352,7 @@ var Run = class {
2055
2352
  this.inheritedSize = inheritedSize;
2056
2353
  this.inheritedFace = inheritedFace;
2057
2354
  this.inheritedBold = inheritedBold;
2355
+ this.inheritedItalic = inheritedItalic;
2058
2356
  this.relationships = relationships;
2059
2357
  this.fontRef = fontRef;
2060
2358
  }
@@ -2284,6 +2582,19 @@ var Run = class {
2284
2582
  get resolvedBold() {
2285
2583
  return this.bold ?? this.inheritedBold?.() ?? null;
2286
2584
  }
2585
+ /**
2586
+ * Whether this run effectively renders italic. It is the run's own `@i`
2587
+ * ({@link italic}) when set; otherwise, for a run inside a placeholder, the italic
2588
+ * state it inherits from the placeholder/list-style chain (paragraph `a:defRPr` →
2589
+ * slide `a:lstStyle` → layout → master placeholder `a:lstStyle` → master
2590
+ * `p:txStyles`). `null` when the run sets no `@i` and inherits none — the
2591
+ * resolved counterpart of {@link italic}, and the twin of {@link resolvedBold}:
2592
+ * `@b` and `@i` are siblings a master text style states together, so a deck that
2593
+ * can be authored with an inherited italic can be read back with one.
2594
+ */
2595
+ get resolvedItalic() {
2596
+ return this.italic ?? this.inheritedItalic?.() ?? null;
2597
+ }
2287
2598
  /** Escape hatch: the underlying `a:r` element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
2288
2599
  get element_() {
2289
2600
  return this.element;
@@ -2352,8 +2663,9 @@ var Paragraph = class {
2352
2663
  const inheritedSize = this.#inheritedResolver((ph, level, pPr, slideLst, ctx) => resolveInheritedRunSize(ph, level, pPr, slideLst, ctx));
2353
2664
  const inheritedFace = this.#inheritedResolver((ph, level, pPr, slideLst, ctx) => resolveInheritedRunFontFace(ph, level, pPr, slideLst, ctx));
2354
2665
  const inheritedBold = this.#inheritedResolver((ph, level, pPr, slideLst, ctx) => resolveInheritedRunBold(ph, level, pPr, slideLst, ctx));
2666
+ const inheritedItalic = this.#inheritedResolver((ph, level, pPr, slideLst, ctx) => resolveInheritedRunItalic(ph, level, pPr, slideLst, ctx));
2355
2667
  const fontRef = this.inherit?.placeholder.fontRef ?? null;
2356
- return getElements(this.element, "a:r").map((element) => new Run(element, this.part, this.themeContext, inheritedColor, inheritedSize, inheritedFace, inheritedBold, this.relationships, fontRef));
2668
+ return getElements(this.element, "a:r").map((element) => new Run(element, this.part, this.themeContext, inheritedColor, inheritedSize, inheritedFace, inheritedBold, inheritedItalic, this.relationships, fontRef));
2357
2669
  }
2358
2670
  /**
2359
2671
  * A memoized thunk resolving the colour every run in this paragraph inherits
@@ -2366,7 +2678,7 @@ var Paragraph = class {
2366
2678
  }
2367
2679
  /**
2368
2680
  * Build a memoized per-paragraph thunk for one inherited run property
2369
- * (colour/size/face), or `undefined` for non-placeholder paragraphs. All runs in
2681
+ * (colour/size/face/bold/italic), or `undefined` for non-placeholder paragraphs. All runs in
2370
2682
  * a paragraph share its level and `a:pPr`, so each `resolve` runs at most once
2371
2683
  * and only when a run actually lacks its own value and asks.
2372
2684
  */
@@ -2513,7 +2825,7 @@ var Paragraph = class {
2513
2825
  #emuAttrPt(name) {
2514
2826
  const pPr = firstChild(this.element, "a:pPr");
2515
2827
  const emu = pPr ? intValue(attr(pPr, name)) : null;
2516
- return emu === null ? null : emu / 12700;
2828
+ return emu === null ? null : emu / EMU_PER_POINT;
2517
2829
  }
2518
2830
  /**
2519
2831
  * The paragraph's text: run (`a:r`) and field (`a:fld`) text concatenated in
@@ -2529,6 +2841,16 @@ var Paragraph = class {
2529
2841
  }
2530
2842
  return out;
2531
2843
  }
2844
+ /**
2845
+ * Replace **this paragraph's** content with a single run, preserving the character
2846
+ * formatting (`a:rPr`) of its first existing run and leaving its `a:pPr` (level,
2847
+ * alignment, bullet) alone. Sibling paragraphs are untouched — the difference from
2848
+ * {@link TextFrame.text}, which collapses the whole body to one paragraph.
2849
+ */
2850
+ set text(value) {
2851
+ setParagraphText(this.element, value);
2852
+ this.part.markDirty();
2853
+ }
2532
2854
  /** Escape hatch: the underlying `a:p` element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
2533
2855
  get element_() {
2534
2856
  return this.element;
@@ -2570,7 +2892,7 @@ var TextFrame = class {
2570
2892
  const insetsPt = {};
2571
2893
  const inset = (qn, key) => {
2572
2894
  const v = intValue(attr(bodyPr, qn));
2573
- if (v !== null) insetsPt[key] = v / 12700;
2895
+ if (v !== null) insetsPt[key] = v / EMU_PER_POINT;
2574
2896
  };
2575
2897
  inset("lIns", "left");
2576
2898
  inset("rIns", "right");
@@ -2672,8 +2994,6 @@ function setTextBodyText(txBody, value) {
2672
2994
  const doc = txBody.ownerDocument;
2673
2995
  if (!doc) throw new InternalError("oxml/node-has-no-document", "Cannot set text: text body has no owner document");
2674
2996
  const paragraphs = getElements(txBody, "a:p");
2675
- const firstRun = paragraphs[0] && firstChild(paragraphs[0], "a:r");
2676
- const rPrTemplate = firstRun && firstChild(firstRun, "a:rPr");
2677
2997
  for (let i = paragraphs.length - 1; i >= 1; i--) {
2678
2998
  const extra = paragraphs[i];
2679
2999
  if (extra) txBody.removeChild(extra);
@@ -2683,6 +3003,21 @@ function setTextBodyText(txBody, value) {
2683
3003
  p = createElement(doc, "a:p");
2684
3004
  txBody.appendChild(p);
2685
3005
  }
3006
+ setParagraphText(p, value);
3007
+ }
3008
+ /**
3009
+ * Replace **one paragraph's** content with a single run, preserving the `a:rPr` of that
3010
+ * paragraph's first existing run and leaving its `a:pPr` (level, alignment, bullet) alone.
3011
+ * Sibling paragraphs are untouched, which is the whole difference from
3012
+ * {@link setTextBodyText} and the reason this exists: a SmartArt drawing cache packs several
3013
+ * nodes' text into one `dsp:txBody`, so collapsing the body there would delete the other
3014
+ * nodes' strings. Does **not** mark any part dirty — the caller owns the `Part`.
3015
+ */
3016
+ function setParagraphText(p, value) {
3017
+ const doc = p.ownerDocument;
3018
+ if (!doc) throw new InternalError("oxml/node-has-no-document", "Cannot set text: paragraph has no owner document");
3019
+ const firstRun = firstChild(p, "a:r");
3020
+ const rPrTemplate = firstRun && firstChild(firstRun, "a:rPr");
2686
3021
  for (const child of childElements(p)) if (child.localName === "r" || child.localName === "br" || child.localName === "fld") p.removeChild(child);
2687
3022
  const run = createElement(doc, "a:r");
2688
3023
  if (rPrTemplate) run.appendChild(rPrTemplate.cloneNode(true));
@@ -3060,7 +3395,7 @@ var Shape = class {
3060
3395
  get lineWidthPt() {
3061
3396
  const ln = this.#line();
3062
3397
  const w = ln ? intValue(attr(ln, "w")) : null;
3063
- return w === null ? null : w / 12700;
3398
+ return w === null ? null : w / EMU_PER_POINT;
3064
3399
  }
3065
3400
  /**
3066
3401
  * The line/border dash style (`spPr/a:ln/a:prstDash/@val`), e.g. `'dash'`,
@@ -3243,7 +3578,7 @@ var Shape = class {
3243
3578
  const out = { color: null };
3244
3579
  this.#applyEffectColor(out, firstChildElement(glow));
3245
3580
  const rad = intValue(attr(glow, "rad"));
3246
- if (rad !== null) out.radiusPt = rad / 12700;
3581
+ if (rad !== null) out.radiusPt = rad / EMU_PER_POINT;
3247
3582
  return out;
3248
3583
  }
3249
3584
  /**
@@ -3259,14 +3594,14 @@ var Shape = class {
3259
3594
  const v = intValue(attr(refl, name));
3260
3595
  if (v !== null) out[target] = v / div;
3261
3596
  };
3262
- put("blurPt", "blurRad", 12700);
3263
- put("distPt", "dist", 12700);
3264
- put("angleDeg", "dir", 6e4);
3265
- put("fadeAngleDeg", "fadeDir", 6e4);
3266
- put("startAlpha", "stA", 1e5);
3267
- put("startPos", "stPos", 1e5);
3268
- put("endAlpha", "endA", 1e5);
3269
- put("endPos", "endPos", 1e5);
3597
+ put("blurPt", "blurRad", EMU_PER_POINT);
3598
+ put("offsetPt", "dist", EMU_PER_POINT);
3599
+ put("angleDeg", "dir", ANGLE_UNITS_PER_DEGREE);
3600
+ put("fadeAngleDeg", "fadeDir", ANGLE_UNITS_PER_DEGREE);
3601
+ put("startAlpha", "stA", PERCENT_SCALE);
3602
+ put("startPos", "stPos", PERCENT_SCALE);
3603
+ put("endAlpha", "endA", PERCENT_SCALE);
3604
+ put("endPos", "endPos", PERCENT_SCALE);
3270
3605
  return out;
3271
3606
  }
3272
3607
  /**
@@ -3277,7 +3612,7 @@ var Shape = class {
3277
3612
  const soft = this.#effect("a:softEdge");
3278
3613
  if (!soft) return null;
3279
3614
  const rad = intValue(attr(soft, "rad"));
3280
- return { radiusPt: rad === null ? 0 : rad / 12700 };
3615
+ return { radiusPt: rad === null ? 0 : rad / EMU_PER_POINT };
3281
3616
  }
3282
3617
  /**
3283
3618
  * The shape's pattern (hatch) fill (`spPr/a:pattFill`), or `null` when the fill
@@ -3321,13 +3656,13 @@ var Shape = class {
3321
3656
  /** Decode a shadow element (`a:outerShdw`/`a:innerShdw` share the fields), resolving its colour. */
3322
3657
  #readShadow(shdw) {
3323
3658
  const out = { color: null };
3324
- this.#applyEffectColor(out, firstChild(shdw, "a:srgbClr") ?? firstChild(shdw, "a:schemeClr"));
3659
+ this.#applyEffectColor(out, firstChildElement(shdw));
3325
3660
  const blur = intValue(attr(shdw, "blurRad"));
3326
3661
  const dist = intValue(attr(shdw, "dist"));
3327
3662
  const dir = intValue(attr(shdw, "dir"));
3328
- if (blur !== null) out.blurPt = blur / 12700;
3329
- if (dist !== null) out.offsetPt = dist / 12700;
3330
- if (dir !== null) out.angleDeg = dir / 6e4;
3663
+ if (blur !== null) out.blurPt = blur / EMU_PER_POINT;
3664
+ if (dist !== null) out.offsetPt = dist / EMU_PER_POINT;
3665
+ if (dir !== null) out.angleDeg = dir / ANGLE_UNITS_PER_DEGREE;
3331
3666
  return out;
3332
3667
  }
3333
3668
  /**
@@ -3633,7 +3968,7 @@ var Picture = class extends Shape {
3633
3968
  if (!srcRect) return null;
3634
3969
  const edge = (name) => {
3635
3970
  const v = intValue(attr(srcRect, name));
3636
- return v === null ? 0 : v / 1e5;
3971
+ return v === null ? 0 : v / PERCENT_SCALE;
3637
3972
  };
3638
3973
  return {
3639
3974
  left: edge("l"),
@@ -3678,14 +4013,14 @@ var Picture = class extends Shape {
3678
4013
  const thresh = intValue(attr(child, "thresh"));
3679
4014
  return {
3680
4015
  kind: "biLevel",
3681
- threshold: thresh === null ? null : thresh / 1e5
4016
+ threshold: thresh === null ? null : thresh / PERCENT_SCALE
3682
4017
  };
3683
4018
  }
3684
4019
  case "alphaModFix": {
3685
4020
  const amt = intValue(attr(child, "amt"));
3686
4021
  return {
3687
4022
  kind: "alphaModFix",
3688
- amount: amt === null ? 1 : amt / 1e5
4023
+ amount: amt === null ? 1 : amt / PERCENT_SCALE
3689
4024
  };
3690
4025
  }
3691
4026
  }
@@ -3821,6 +4156,75 @@ var Connector = class extends Shape {
3821
4156
  }
3822
4157
  };
3823
4158
  //#endregion
4159
+ //#region src/read/oxml/point-cache.ts
4160
+ /**
4161
+ * The one place a chart point cache is turned into an idx-ordered array.
4162
+ *
4163
+ * Both chart families cache their category/value data the same way: a run of indexed point
4164
+ * elements plus a declared count, which the reader widens back into a dense array so
4165
+ * `series.values[3]` means the point at `idx="3"` and a gap reads as `null`. Classic `c:`
4166
+ * charts and 2016 `cx:` charts spell it differently — `c:ptCount/@val` and a `c:v` child
4167
+ * against `cx:lvl/@ptCount` and the point's own text — and nothing else about the two decodes
4168
+ * differs, so the shape parameterizes over exactly those two differences.
4169
+ *
4170
+ * **Why neither number out of the file is trusted.** `@ptCount` is `xsd:unsignedInt`, so
4171
+ * `4294967295` is schema-valid, and `new Array(n).fill(null)` at that size is not a slow path
4172
+ * but a process kill: V8 answers `FATAL ERROR: invalid table size` and the host dies with no
4173
+ * exception to catch. That was reachable through the public read API — `Presentation.load()`
4174
+ * on a deck with an edited `c:ptCount` returned normally and the first `chart.series` access
4175
+ * killed the process. A single `<c:pt idx="900000000"/>` does the same thing on its own.
4176
+ * Reading a deck is the one part of this library whose input comes from somewhere other than
4177
+ * the calling program, so both numbers are bounded here.
4178
+ *
4179
+ * The array is sized by the points that are really there, not by the declared count. The cost
4180
+ * of that is a *trailing* gap: a cache declaring four points but carrying three (the fourth
4181
+ * cell blank) reads three long rather than four. No value is lost — the dropped slots are the
4182
+ * ones that would have been `null` — and the disagreement is warned about either way.
4183
+ */
4184
+ /**
4185
+ * The highest point index this reader will honour.
4186
+ *
4187
+ * A cache references a range in the embedded workbook, and a worksheet has 1,048,576 rows, so
4188
+ * a point index at or above that cannot describe data any producer could have charted. Using
4189
+ * the sheet bound rather than a round number keeps the ceiling something a reader can check:
4190
+ * below it every real cache fits, above it the file is wrong by construction. It also caps the
4191
+ * worst-case allocation at about 8 MB of `null` — survivable, and reported — instead of an
4192
+ * unbounded one that takes the process with it.
4193
+ */
4194
+ const MAX_POINT_INDEX = 1048576;
4195
+ /**
4196
+ * Read indexed cache points into a dense, idx-ordered array.
4197
+ *
4198
+ * The result is as long as the highest `@idx` actually present, and holds `null` at every
4199
+ * index no point claimed. A declared count that disagrees is warned about and not obeyed; a
4200
+ * point indexed past {@link MAX_POINT_INDEX} is warned about and dropped.
4201
+ * @param pts - the point elements, in document order
4202
+ * @param declaredCount - the cache's own count attribute, or `null` when absent
4203
+ * @param valueOf - reads one point's value; the two chart families differ only here
4204
+ * @param label - what to call the count in a diagnostic, e.g. `c:ptCount`
4205
+ */
4206
+ function readIndexedPoints(pts, declaredCount, valueOf, label) {
4207
+ let count = 0;
4208
+ let dropped = 0;
4209
+ for (const pt of pts) {
4210
+ const idx = intValue(attr(pt, "idx")) ?? 0;
4211
+ if (idx < 0 || idx >= MAX_POINT_INDEX) {
4212
+ dropped++;
4213
+ continue;
4214
+ }
4215
+ if (idx + 1 > count) count = idx + 1;
4216
+ }
4217
+ if (dropped > 0) warn("chart/point-index-out-of-range", `${label}: ${dropped} cache point(s) are indexed outside 0..1048575, which no worksheet-backed chart can reference; they were dropped`);
4218
+ if (declaredCount !== null && declaredCount !== count) warn("chart/point-count-mismatch", `${label} declares ${declaredCount} point(s) but the cache carries ${pts.length}; reading ${count} rather than allocating the declared size`);
4219
+ const points = new Array(count).fill(null);
4220
+ for (const pt of pts) {
4221
+ const idx = intValue(attr(pt, "idx")) ?? 0;
4222
+ if (idx < 0 || idx >= count) continue;
4223
+ points[idx] = valueOf(pt);
4224
+ }
4225
+ return points;
4226
+ }
4227
+ //#endregion
3824
4228
  //#region src/read/api/chart.ts
3825
4229
  /** A chart graphic-frame's chart, backed by its chart part. */
3826
4230
  var Chart = class {
@@ -4094,7 +4498,7 @@ var ChartSeries = class {
4094
4498
  const dash = firstChild(ln, "a:prstDash");
4095
4499
  const solid = readSolid(ln);
4096
4500
  return {
4097
- widthPt: w === null ? null : w / 12700,
4501
+ widthPt: w === null ? null : w / EMU_PER_POINT,
4098
4502
  dash: dash ? attr(dash, "val") ?? null : null,
4099
4503
  color: solid.color,
4100
4504
  schemeColor: solid.schemeColor,
@@ -4188,22 +4592,11 @@ function findCache(container) {
4188
4592
  }
4189
4593
  return firstChild(container, "c:numLit") ?? firstChild(container, "c:strLit");
4190
4594
  }
4191
- /** Read a cache's points (`c:pt[@idx]/c:v`) into an idx-ordered array sized by `c:ptCount`. */
4595
+ /** Read a cache's points (`c:pt[@idx]/c:v`) into an idx-ordered array; `c:ptCount` is the declared count. */
4192
4596
  function readPoints(cache) {
4193
4597
  if (!cache) return [];
4194
4598
  const ptCount = firstChild(cache, "c:ptCount");
4195
- let count = ptCount ? intValue(attr(ptCount, "val")) ?? 0 : 0;
4196
- const pts = getElements(cache, "c:pt");
4197
- for (const pt of pts) {
4198
- const idx = intValue(attr(pt, "idx")) ?? 0;
4199
- if (idx + 1 > count) count = idx + 1;
4200
- }
4201
- const points = new Array(count).fill(null);
4202
- for (const pt of pts) {
4203
- const idx = intValue(attr(pt, "idx")) ?? 0;
4204
- points[idx] = firstChild(pt, "c:v")?.textContent ?? null;
4205
- }
4206
- return points;
4599
+ return readIndexedPoints(getElements(cache, "c:pt"), ptCount ? intValue(attr(ptCount, "val")) : null, (pt) => firstChild(pt, "c:v")?.textContent ?? null, "c:ptCount");
4207
4600
  }
4208
4601
  //#endregion
4209
4602
  //#region src/read/api/chartex.ts
@@ -4437,22 +4830,10 @@ var ChartExAxis = class {
4437
4830
  this.chart.part.markDirty();
4438
4831
  }
4439
4832
  };
4440
- /** Read a `cx:lvl`'s points (`cx:pt[@idx]` text) into an idx-ordered array sized by `@ptCount`. */
4833
+ /** Read a `cx:lvl`'s points (`cx:pt[@idx]` text) into an idx-ordered array; `@ptCount` is the declared count. */
4441
4834
  function readLevelPoints(lvl) {
4442
4835
  if (!lvl) return [];
4443
- const ptCount = intValue(attr(lvl, "ptCount"));
4444
- const pts = getElements(lvl, "cx:pt");
4445
- let count = ptCount ?? 0;
4446
- for (const pt of pts) {
4447
- const idx = intValue(attr(pt, "idx")) ?? 0;
4448
- if (idx + 1 > count) count = idx + 1;
4449
- }
4450
- const points = new Array(count).fill(null);
4451
- for (const pt of pts) {
4452
- const idx = intValue(attr(pt, "idx")) ?? 0;
4453
- points[idx] = pt.textContent ?? null;
4454
- }
4455
- return points;
4836
+ return readIndexedPoints(getElements(lvl, "cx:pt"), intValue(attr(lvl, "ptCount")), (pt) => pt.textContent ?? null, "cx:lvl/@ptCount");
4456
4837
  }
4457
4838
  /** Parse an attribute as a boolean (`1`/`true` → true, `0`/`false` → false), or `null` when absent. */
4458
4839
  function boolAttr(element, name) {
@@ -4468,6 +4849,55 @@ function floatValue(value) {
4468
4849
  }
4469
4850
  //#endregion
4470
4851
  //#region src/read/api/diagram.ts
4852
+ /**
4853
+ * Read proxies for a SmartArt diagram (`dgm:dataModel`) hosted in a `p:graphicFrame`.
4854
+ *
4855
+ * SmartArt is a four-part construct, and the frame itself holds none of it: the
4856
+ * `a:graphicData` carries only a `dgm:relIds` with four relationship ids pointing at the
4857
+ * data, layout, quick-style and colours parts under `/ppt/diagrams/`. The **data** part is
4858
+ * the one with content in it — a flat `dgm:ptLst` of points plus a `dgm:cxnLst` of
4859
+ * connections that give the points their tree — and it is what this surface decodes. The
4860
+ * other three are generated presets (`urn:microsoft.com/office/officeart/…`) describing how
4861
+ * PowerPoint *draws* the graph rather than what it says; they surface as parts, not getters.
4862
+ *
4863
+ * The text is ordinary DrawingML: `dgm:pt/dgm:t` is an `a:CT_TextBody`, the same type a
4864
+ * shape's `p:txBody` is, so a point's text reads back through the same
4865
+ * {@link TextFrame}/`Paragraph`/`Run` proxies as any other text on the slide, run formatting
4866
+ * included.
4867
+ *
4868
+ * **Not every point is content.** A saved data model interleaves the user's nodes with
4869
+ * points the layout engine generated (`pres`), the diagram root (`doc`), and the transition
4870
+ * points sitting on each edge (`parTrans`/`sibTrans`, which carry text only where a layout
4871
+ * labels its arrows). {@link Diagram.text} applies exactly that filter; {@link Diagram.points}
4872
+ * applies none, so a consumer that needs the raw graph gets it.
4873
+ *
4874
+ * **The structure is in the connections, and {@link Diagram.nodes} is the view of it.** A
4875
+ * `parOf` edge names the parent in `@srcId`, the child in `@destId` and the child's sibling
4876
+ * position in `@srcOrd`; `points` order is document order and says nothing about depth. Three
4877
+ * decisions that tree makes, each of which could defensibly have gone the other way:
4878
+ *
4879
+ * - **`asst` points are in it.** An assistant is user content, and an org chart merely draws
4880
+ * it off the main branch rather than under it. {@link DiagramPoint.type} carries the
4881
+ * distinction, so folding it silently in with `node` would hide something a consumer can
4882
+ * act on while omitting it would lose authored text.
4883
+ * - **Transition points are not.** A `parTrans`/`sibTrans` labels an *edge*, not a node, and
4884
+ * reaches its edge through {@link DiagramConnection.parentTransitionId} /
4885
+ * `siblingTransitionId`. Hanging one off `children` would make the tree disagree with what
4886
+ * PowerPoint's own text pane shows.
4887
+ * - **A cycle in `parOf` throws.** It is a corrupt model rather than a shape the format can
4888
+ * express, and the walk would not terminate; see `diagram/parent-edge-cycle`.
4889
+ *
4890
+ * **A point's text is stored twice.** The data model is what PowerPoint reads, but a deck
4891
+ * also carries a *drawing* part (`dsp:drawing`) holding a copy of every drawn string, and
4892
+ * every renderer without a SmartArt layout engine — LibreOffice, Google Slides, thumbnailers
4893
+ * — paints that copy and nothing else. {@link DiagramPoint.drawnShape} is the link between
4894
+ * the two, and {@link DiagramPoint.text}'s setter is what keeps them in step.
4895
+ *
4896
+ * Authoring a SmartArt graphic from nothing is out of scope: the layout part is a
4897
+ * constraint-solver program PowerPoint executes, and the presentation tree it generates is
4898
+ * not derivable from the user's content. Editing the text of one that already exists is
4899
+ * supported — see {@link DiagramPoint.text}.
4900
+ */
4471
4901
  const POINT_TYPES = [
4472
4902
  "node",
4473
4903
  "asst",
@@ -4484,17 +4914,100 @@ const CONNECTION_TYPES = [
4484
4914
  ];
4485
4915
  /** The MS extension (`dgm:extLst/a:ext`) whose `@relId` names the fallback drawing part. */
4486
4916
  const DRAWING_EXT_URI = "http://schemas.microsoft.com/office/drawing/2008/diagram";
4917
+ /**
4918
+ * The node → drawn-shape index of one diagram: the `presOf` edges of the data model and the
4919
+ * `dsp:sp` elements of the drawing part, both keyed for lookup.
4920
+ *
4921
+ * Built once per {@link Diagram}, and only when a point is actually asked what draws it — the
4922
+ * drawing part is a second XML DOM and a consumer reading node text should not pay to parse
4923
+ * it. The consequence of that memo: a consumer who *adds* or *removes* shapes in the drawing
4924
+ * part through the escape hatches must re-reach the diagram to see them here.
4925
+ */
4926
+ var DrawingIndex = class {
4927
+ drawingPart;
4928
+ themeContext;
4929
+ /** `presOf` arms out of each source point, in `@srcOrd` order. */
4930
+ #arms = /* @__PURE__ */ new Map();
4931
+ /** Each `dsp:sp` by the `pres` modelId it draws. */
4932
+ #shapes = /* @__PURE__ */ new Map();
4933
+ constructor(root, drawingPart, themeContext) {
4934
+ this.drawingPart = drawingPart;
4935
+ this.themeContext = themeContext;
4936
+ const cxnLst = root && firstChild(root, "dgm:cxnLst");
4937
+ for (const cxn of cxnLst ? getElements(cxnLst, "dgm:cxn") : []) {
4938
+ if (attr(cxn, "type") !== "presOf") continue;
4939
+ const srcId = attr(cxn, "srcId");
4940
+ const destId = attr(cxn, "destId");
4941
+ if (!srcId || !destId) continue;
4942
+ const arm = {
4943
+ destId,
4944
+ destinationOrder: intValue(attr(cxn, "destOrd")) ?? 0,
4945
+ sourceOrder: intValue(attr(cxn, "srcOrd")) ?? 0
4946
+ };
4947
+ const list = this.#arms.get(srcId);
4948
+ if (list) list.push(arm);
4949
+ else this.#arms.set(srcId, [arm]);
4950
+ }
4951
+ for (const list of this.#arms.values()) list.sort((a, b) => a.sourceOrder - b.sourceOrder);
4952
+ const drawingRoot = drawingPart?.dom.documentElement ?? null;
4953
+ for (const sp of drawingRoot ? descendantsByTag(drawingRoot, OOXML_NS.dsp, "sp") : []) {
4954
+ const modelId = attr(sp, "modelId");
4955
+ if (modelId !== null && !this.#shapes.has(modelId)) this.#shapes.set(modelId, sp);
4956
+ }
4957
+ }
4958
+ /**
4959
+ * Resolve one authored point to the `pres` point that presents it and to the drawn
4960
+ * paragraph carrying its text.
4961
+ *
4962
+ * A point commonly has **several** `presOf` arms — an org-chart node's box and the
4963
+ * connector beneath it are two presentations of one node — so the arm is chosen by what it
4964
+ * reaches rather than by its `@srcOrd`: the first, in `@srcOrd` order, whose `pres` point
4965
+ * draws a `dsp:sp` that has a `dsp:txBody`. That selector produced a unique hit for all 90
4966
+ * authored points measured across five layout families, and it degrades to "no arm" rather
4967
+ * than to "the wrong arm" on a layout that was not.
4968
+ */
4969
+ resolve(modelId) {
4970
+ const arms = (modelId === null ? void 0 : this.#arms.get(modelId)) ?? [];
4971
+ const first = arms[0];
4972
+ if (!first) return {
4973
+ presentationId: null,
4974
+ drawnShape: null
4975
+ };
4976
+ for (const arm of arms) {
4977
+ const sp = this.#shapes.get(arm.destId);
4978
+ const txBody = sp && firstChild(sp, "dsp:txBody");
4979
+ if (!sp || !txBody || !this.drawingPart) continue;
4980
+ const paragraph = getElements(txBody, "a:p")[arm.destinationOrder];
4981
+ return {
4982
+ presentationId: arm.destId,
4983
+ drawnShape: paragraph ? {
4984
+ part: this.drawingPart,
4985
+ modelId: arm.destId,
4986
+ paragraphIndex: arm.destinationOrder,
4987
+ textFrame: new TextFrame(txBody, this.drawingPart, this.themeContext),
4988
+ element_: sp
4989
+ } : null
4990
+ };
4991
+ }
4992
+ return {
4993
+ presentationId: first.destId,
4994
+ drawnShape: null
4995
+ };
4996
+ }
4997
+ };
4487
4998
  /** One point (`dgm:pt`) of a diagram's data model. */
4488
4999
  var DiagramPoint = class {
4489
5000
  pt;
4490
5001
  part;
4491
5002
  themeContext;
4492
5003
  relationships;
4493
- constructor(pt, part, themeContext, relationships) {
5004
+ drawing;
5005
+ constructor(pt, part, themeContext, relationships, drawing) {
4494
5006
  this.pt = pt;
4495
5007
  this.part = part;
4496
5008
  this.themeContext = themeContext;
4497
5009
  this.relationships = relationships;
5010
+ this.drawing = drawing;
4498
5011
  }
4499
5012
  /** This point's id (`@modelId`), which connections reference. */
4500
5013
  get modelId() {
@@ -4530,15 +5043,87 @@ var DiagramPoint = class {
4530
5043
  * This point's text body (`dgm:t`) as a {@link TextFrame}, or `null` when the point
4531
5044
  * carries none. It is an `a:CT_TextBody` like any other, so `paragraphs`, `runs` and the
4532
5045
  * `resolved*` getters all apply.
5046
+ *
5047
+ * **Writing through this edits the data model only**, and leaves the drawing cache
5048
+ * holding the old string — which is what PowerPoint-less renderers draw. It is the escape
5049
+ * hatch for run-level formatting, so it does exactly what it is told and nothing more;
5050
+ * {@link text}'s setter is the path that mirrors.
4533
5051
  */
4534
5052
  get textFrame() {
4535
5053
  const t = firstChild(this.pt, "dgm:t");
4536
5054
  return t ? new TextFrame(t, this.part, this.themeContext, void 0, this.relationships) : null;
4537
5055
  }
5056
+ /**
5057
+ * The generated `pres` point that presents this one (`dgm:cxn[@type="presOf"]/@destId`),
5058
+ * or `null` when nothing presents it — the normal state of an unlabelled transition point.
5059
+ *
5060
+ * Where a point has several `presOf` arms the one named here is the arm that draws text;
5061
+ * see {@link drawnShape}, which resolves both.
5062
+ */
5063
+ get presentationId() {
5064
+ return this.drawing?.().resolve(this.modelId).presentationId ?? null;
5065
+ }
5066
+ /**
5067
+ * The paragraph of the fallback drawing part (`dsp:drawing`) that carries this point's
5068
+ * text, or `null` when nothing draws it.
5069
+ *
5070
+ * The mapping runs through the `pres` point rather than directly: a `dsp:sp/@modelId` is
5071
+ * always a `pres` point's id, so a lookup keyed on the authored point's own id finds
5072
+ * nothing at all. It is many-to-one in both directions — one shape draws several points
5073
+ * (`@destOrd` says which paragraph is whose, and it can contradict document order), and
5074
+ * one point has several presentations, of which at most one holds text.
5075
+ *
5076
+ * `null` is a defined outcome, not a failure, and covers four real cases: the package
5077
+ * carries no drawing part; the point has no `presOf` edge (every unlabelled
5078
+ * `parTrans`/`sibTrans`); the `pres` point draws no `dsp:sp`; and the shape it draws has
5079
+ * no `dsp:txBody` (a connector, or a picture node's filled rectangle).
5080
+ */
5081
+ get drawnShape() {
5082
+ return this.drawing?.().resolve(this.modelId).drawnShape ?? null;
5083
+ }
4538
5084
  /** This point's text, paragraphs joined by `\n`; `''` when it carries none. */
4539
5085
  get text() {
4540
5086
  return this.textFrame?.text ?? "";
4541
5087
  }
5088
+ /**
5089
+ * Replace this point's text, in the data model **and** in the drawing cache.
5090
+ *
5091
+ * A diagram stores every string twice. `dgm:t` is what PowerPoint reads, and it recomputes
5092
+ * the drawing from the data model on open; the `dsp:drawing` part is what every other
5093
+ * renderer paints, and none of them recompute anything. Writing only the first is an edit
5094
+ * that is invisible in LibreOffice, Google Slides, thumbnailers and web previews until
5095
+ * someone opens the deck in PowerPoint and saves it.
5096
+ *
5097
+ * The data-model write always happens. The mirror is best-effort: when the point resolves
5098
+ * to no drawn paragraph (see {@link drawnShape} for the four ways that occurs) the cache
5099
+ * keeps the old string and a `diagram/drawing-cache-not-updated` diagnostic says so.
5100
+ * Throwing instead would make the common case unusable in order to report the rare one,
5101
+ * and returning a status would be a shape no other text setter here has.
5102
+ *
5103
+ * Two things it deliberately does not do. It does **not** recompute geometry: the drawn
5104
+ * shape keeps its cached position and size, so a much longer string overflows its box in
5105
+ * those same renderers until PowerPoint re-lays the diagram out. And it does not *create*
5106
+ * a place to draw text — a transition point whose layout has no label slot has no
5107
+ * `dsp:sp`, and PowerPoint strips text put on one, so the honest outcome is the
5108
+ * diagnostic. Like every other `text` setter here it collapses the point to one run,
5109
+ * keeping the first run's `a:rPr`; use {@link textFrame} for run-level work.
5110
+ */
5111
+ set text(value) {
5112
+ const frame = this.textFrame;
5113
+ if (!frame) {
5114
+ warn("diagram/point-has-no-text-body", `SmartArt point ${this.#label()} (type ${this.type}) has no dgm:t, so it is not a point text can be written to; nothing was changed`);
5115
+ return;
5116
+ }
5117
+ frame.text = value;
5118
+ const drawn = this.drawnShape;
5119
+ const paragraph = drawn?.textFrame.paragraphs[drawn.paragraphIndex];
5120
+ if (!drawn || !paragraph) {
5121
+ warn("diagram/drawing-cache-not-updated", `SmartArt point ${this.#label()} resolves to no drawn paragraph, so ${this.part.partName} now holds the new text but the drawing cache still holds the old one; PowerPoint redraws it on open, other renderers do not`);
5122
+ return;
5123
+ }
5124
+ setParagraphText(paragraph.element_, value);
5125
+ drawn.part.markDirty();
5126
+ }
4542
5127
  /** Escape hatch: the underlying `dgm:pt` element. After mutating it call {@link Diagram.markDirty}. */
4543
5128
  get element_() {
4544
5129
  return this.pt;
@@ -4546,7 +5131,29 @@ var DiagramPoint = class {
4546
5131
  #prSet() {
4547
5132
  return firstChild(this.pt, "dgm:prSet");
4548
5133
  }
5134
+ /** How a diagnostic names this point: its `@modelId`, which is all a corrupt point may have. */
5135
+ #label() {
5136
+ return this.modelId ?? "(no modelId)";
5137
+ }
5138
+ };
5139
+ /**
5140
+ * One user-authored point in its place in the diagram's tree — a `node` or an `asst`, never a
5141
+ * generated `pres` point or a transition label. See {@link Diagram.nodes}.
5142
+ */
5143
+ var DiagramNode = class {
5144
+ point;
5145
+ parent;
5146
+ level;
5147
+ /** Child nodes, ordered by their `parOf` edge's `@srcOrd`. Empty for a leaf. */
5148
+ children = [];
5149
+ constructor(point, parent, level) {
5150
+ this.point = point;
5151
+ this.parent = parent;
5152
+ this.level = level;
5153
+ }
4549
5154
  };
5155
+ /** Sort key for a root with no `parOf` edge at all, so it lands after the ordered ones. */
5156
+ const MAX_ORDER = Number.MAX_SAFE_INTEGER;
4550
5157
  /** A SmartArt graphic frame's diagram, backed by its `dgm:dataModel` part. */
4551
5158
  var Diagram = class {
4552
5159
  part;
@@ -4554,6 +5161,9 @@ var Diagram = class {
4554
5161
  opc;
4555
5162
  frameRelationships;
4556
5163
  themeContext;
5164
+ #relationships;
5165
+ #docPrSetCache;
5166
+ #drawing;
4557
5167
  constructor(part, relIds, opc, frameRelationships, themeContext) {
4558
5168
  this.part = part;
4559
5169
  this.relIds = relIds;
@@ -4568,14 +5178,88 @@ var Diagram = class {
4568
5178
  /**
4569
5179
  * Every point (`dgm:ptLst/dgm:pt`) in document order, unfiltered — generated `pres`
4570
5180
  * points and the `doc` root included. Filter on {@link DiagramPoint.type} for the user's
4571
- * nodes; {@link text} is the shortcut for the common case.
5181
+ * nodes; {@link text} is the shortcut for the common case, {@link nodes} the structured one.
5182
+ *
5183
+ * Built per access like every other read proxy (`docs/reference/pptx-read.md`, "Object
5184
+ * model"), so `points[0] !== points[0]` while both wrap the same `dgm:pt`. Key a `Map`
5185
+ * on {@link DiagramPoint.modelId}, not on the point.
4572
5186
  */
4573
5187
  get points() {
4574
5188
  const root = this.#root();
4575
5189
  const ptLst = root && firstChild(root, "dgm:ptLst");
4576
5190
  if (!ptLst) return [];
4577
- const relationships = this.opc.relationshipsFor(this.part.partName);
4578
- return getElements(ptLst, "dgm:pt").map((pt) => new DiagramPoint(pt, this.part, this.themeContext, relationships));
5191
+ const relationships = this.#relationships ??= this.opc.relationshipsFor(this.part.partName);
5192
+ const drawing = () => this.#drawingIndex();
5193
+ return getElements(ptLst, "dgm:pt").map((pt) => new DiagramPoint(pt, this.part, this.themeContext, relationships, drawing));
5194
+ }
5195
+ /**
5196
+ * The authored tree — every `node` and `asst` point in its place, roots in `@srcOrd`
5197
+ * order — as a view over {@link points} and {@link connections} rather than a replacement
5198
+ * for either.
5199
+ *
5200
+ * A root is a content point whose `parOf` parent is not itself content, which in a
5201
+ * PowerPoint-authored model means the `doc` point. Transition points are not members; see
5202
+ * this module's header for that decision and the two beside it.
5203
+ *
5204
+ * @throws PackageReadError `diagram/parent-edge-cycle` when the `parOf` edges make a
5205
+ * point its own ancestor, which is a corrupt model rather than a diagram shape.
5206
+ */
5207
+ get nodes() {
5208
+ const content = /* @__PURE__ */ new Map();
5209
+ for (const point of this.points) {
5210
+ const modelId = point.modelId;
5211
+ if (modelId !== null && (point.type === "node" || point.type === "asst")) content.set(modelId, point);
5212
+ }
5213
+ const childrenOf = /* @__PURE__ */ new Map();
5214
+ const edgeTo = /* @__PURE__ */ new Map();
5215
+ for (const connection of this.connections) {
5216
+ const { sourceId, destinationId } = connection;
5217
+ if (connection.type !== "parOf" || sourceId === null || destinationId === null) continue;
5218
+ if (!content.has(destinationId)) continue;
5219
+ const edge = {
5220
+ parentId: sourceId,
5221
+ childId: destinationId,
5222
+ order: connection.sourceOrder ?? 0
5223
+ };
5224
+ const list = childrenOf.get(sourceId);
5225
+ if (list) list.push(edge);
5226
+ else childrenOf.set(sourceId, [edge]);
5227
+ edgeTo.set(destinationId, edge);
5228
+ }
5229
+ for (const list of childrenOf.values()) list.sort((a, b) => a.order - b.order);
5230
+ const roots = [...content].filter(([modelId]) => {
5231
+ const parentId = edgeTo.get(modelId)?.parentId;
5232
+ return parentId === void 0 || !content.has(parentId);
5233
+ });
5234
+ roots.sort(([a], [b]) => (edgeTo.get(a)?.order ?? MAX_ORDER) - (edgeTo.get(b)?.order ?? MAX_ORDER));
5235
+ const placed = /* @__PURE__ */ new Set();
5236
+ const build = (point, parent, level, path) => {
5237
+ const modelId = point.modelId;
5238
+ if (modelId !== null && path.has(modelId)) throw this.#cycleError(modelId);
5239
+ const node = new DiagramNode(point, parent, level);
5240
+ if (modelId === null) return node;
5241
+ placed.add(modelId);
5242
+ path.add(modelId);
5243
+ for (const edge of childrenOf.get(modelId) ?? []) {
5244
+ const child = content.get(edge.childId);
5245
+ if (child) node.children.push(build(child, node, level + 1, path));
5246
+ }
5247
+ path.delete(modelId);
5248
+ return node;
5249
+ };
5250
+ const tree = roots.map(([, point]) => build(point, null, 0, /* @__PURE__ */ new Set()));
5251
+ if (placed.size !== content.size) {
5252
+ const orphan = [...content.keys()].find((modelId) => !placed.has(modelId));
5253
+ throw this.#cycleError(orphan ?? "(unidentified)");
5254
+ }
5255
+ return tree;
5256
+ }
5257
+ /**
5258
+ * The point with this `@modelId`, or `null` — the lookup every connection getter implies,
5259
+ * since a `DiagramConnection` names its ends by id and nothing else resolves them.
5260
+ */
5261
+ point(modelId) {
5262
+ return this.points.find((candidate) => candidate.modelId === modelId) ?? null;
4579
5263
  }
4580
5264
  /** Every edge (`dgm:cxnLst/dgm:cxn`) in document order. */
4581
5265
  get connections() {
@@ -4666,14 +5350,32 @@ var Diagram = class {
4666
5350
  #root() {
4667
5351
  return this.part.dom.documentElement;
4668
5352
  }
4669
- #docPrSetAttr(name) {
5353
+ #cycleError(modelId) {
5354
+ 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`);
5355
+ }
5356
+ /**
5357
+ * The `doc` point's `dgm:prSet`, which carries all three preset ids — resolved once
5358
+ * rather than once per getter, since each lookup is a scan of the whole `ptLst`.
5359
+ * `undefined` means "not looked up yet"; `null` means "looked up, not there".
5360
+ */
5361
+ #docPrSet() {
5362
+ if (this.#docPrSetCache !== void 0) return this.#docPrSetCache;
4670
5363
  const root = this.#root();
4671
5364
  const ptLst = root && firstChild(root, "dgm:ptLst");
4672
- if (!ptLst) return null;
4673
- const doc = getElements(ptLst, "dgm:pt").find((pt) => attr(pt, "type") === "doc");
4674
- const prSet = doc && firstChild(doc, "dgm:prSet");
5365
+ const doc = ptLst && getElements(ptLst, "dgm:pt").find((pt) => attr(pt, "type") === "doc");
5366
+ return this.#docPrSetCache = doc && firstChild(doc, "dgm:prSet") || null;
5367
+ }
5368
+ #docPrSetAttr(name) {
5369
+ const prSet = this.#docPrSet();
4675
5370
  return prSet ? attr(prSet, name) : null;
4676
5371
  }
5372
+ /**
5373
+ * The drawing index, built on first use and kept — see {@link DrawingIndex} for what that
5374
+ * memo costs a consumer who mutates the drawing part's shape tree by hand.
5375
+ */
5376
+ #drawingIndex() {
5377
+ return this.#drawing ??= new DrawingIndex(this.#root(), this.drawingPart, this.themeContext);
5378
+ }
4677
5379
  #relPart(qname) {
4678
5380
  return this.#partForRelId(attr(this.relIds, qname));
4679
5381
  }
@@ -5588,7 +6290,7 @@ var TableCell = class {
5588
6290
  removeChildrenByQName(tcPr, [qname]);
5589
6291
  const doc = ownerDocumentOf(tcPr);
5590
6292
  const ln = createElement(doc, qname);
5591
- if (border.widthPt !== void 0 && border.widthPt !== null) setAttr(ln, "w", String(checkFiniteEmu(border.widthPt * 12700, "widthPt", "table/invalid-cell-border")));
6293
+ if (border.widthPt !== void 0 && border.widthPt !== null) setAttr(ln, "w", String(checkFiniteEmu(border.widthPt * EMU_PER_POINT, "widthPt", "table/invalid-cell-border")));
5592
6294
  if (border.noFill) ln.appendChild(createElement(doc, "a:noFill"));
5593
6295
  else if (border.schemeColor || border.color) {
5594
6296
  const fill = createElement(doc, "a:solidFill");
@@ -5778,7 +6480,8 @@ var TableCell = class {
5778
6480
  /**
5779
6481
  * The cell's edge borders (`a:tcPr/a:lnL|lnR|lnT|lnB|lnTlToBr|lnBlToTr`), or
5780
6482
  * `null` when the cell defines none. Each present edge decodes to a
5781
- * {@link CellBorder} (width / dash / resolved + raw colour / suppressed flag);
6483
+ * {@link CellBorder} (width / dash / resolved colour + its raw token and transform
6484
+ * list / suppressed flag);
5782
6485
  * absent edges are `null`. Cell borders are the biggest visible table gap — a
5783
6486
  * replica built only from geometry and fill draws every cell edge-to-edge with
5784
6487
  * no rule, so this surfaces the per-side stroke the writer's `border` option emits.
@@ -5794,8 +6497,9 @@ var TableCell = class {
5794
6497
  const scheme = this.#fillSchemeColorOf(ln);
5795
6498
  const resolved = this.themeColors ? resolveSolidFillColor(ln, this.themeColors) : null;
5796
6499
  return {
5797
- widthPt: w === null ? null : w / 12700,
6500
+ widthPt: w === null ? null : w / EMU_PER_POINT,
5798
6501
  dash: dash ? attr(dash, "val") ?? null : null,
6502
+ resolvedColor: resolved,
5799
6503
  color: resolved ? resolved.effectiveHex : null,
5800
6504
  schemeColor: scheme,
5801
6505
  noFill: !!firstChild(ln, "a:noFill")
@@ -5860,7 +6564,7 @@ var TableCell = class {
5860
6564
  const rig = firstChild(cell3D, "a:lightRig");
5861
6565
  const pts = (value) => {
5862
6566
  const emu = intValue(value);
5863
- return emu === null ? null : emu / 12700;
6567
+ return emu === null ? null : emu / EMU_PER_POINT;
5864
6568
  };
5865
6569
  return {
5866
6570
  material: attr(cell3D, "prstMaterial") ?? null,
@@ -6242,6 +6946,6 @@ function findShapeByIdDeep(shapes, id) {
6242
6946
  }
6243
6947
  }
6244
6948
  //#endregion
6245
- export { collectElements as $, readPatternFill as A, zipPathToPartName as At, LST_STYLE_LEVELS as B, getOrAddChild as Bt, Picture as C, SPPR_SP3D_AFTER as Ct, Run as D, relsPartNameFor as Dt, Paragraph as E, relativePartName as Et, resolveMasterColorContext as F, descendantsByTag as Ft, placeholderInheritedRunProps as G, removeAttr as Gt, placeholderInheritedAnchor as H, intValue as Ht, resolveNotesColorContext as I, escapeXmlAttribute as It, slideDefinesColor as J, serializeXml as Jt, placeholderInheritedXfrm as K, removeChildrenByQName as Kt, resolveSlideColorContext as L, firstChild as Lt, readGradientFill as M, boolValue as Mt, resolveColorElement as N, childElements as Nt, TextFrame as O, resolveRelativePartName as Ot, resolveLayoutColorContext as P, createElement as Pt, carriedDecorations as Q, resolveSlideThemeParts as R, firstChildElement as Rt, Connector as S, SPPR_SCENE3D_AFTER as St, Shape as T, partNameToZipPath as Tt, placeholderInheritedColor as U, ownerDocumentOf as Ut, RUN_PROP_NAMES as V, insertInOrder as Vt, placeholderInheritedListStyles as W, parseXml as Wt, cSldName as X, slideDefinesProp as Y, setAttr as Yt, cSldOf as Z, ChartExAxis as _, SHAPE_AFTER_SPPR as _t, isConnector as a, fmtEntry as at, ChartAxis as b, SPPR_FILL_AFTER as bt, isPicture as c, parseClrMap as ct, Table as d, resolveSchemeToken as dt, firstShapeChild as et, TableCell as f, styleRefFill as ft, ChartEx as g, RPR_FILL_AFTER as gt, DiagramPoint as h, FILL_CHOICES as ht, isAutoShape as i, SCHEME_SLOTS as it, readPictureFill as j, attr as jt, spPrXfrmEmu as k, resolveSingleRel as kt, wrapShapeElement as l, parseClrScheme as lt, Diagram as m, substitutePhClr as mt, buildShapes as n, nvPrOf as nt, isGraphicFrame as o, intAttr as ot, TableRow as p, styleRefLine as pt, placeholderOf as q, replaceInParent as qt, findShapeByIdDeep as r, spTreeOf as rt, isGroupShape as s, isA as st, GroupShape as t, nthShapeChild as tt, GraphicFrame as u, resolveColor as ut, ChartExSeries as v, SPPR_AFTER_XFRM as vt, AutoShape as w, partNameExtension as wt, ChartSeries as x, SPPR_LN_AFTER as xt, Chart as y, SPPR_EFFECT_AFTER as yt, applyColorTransforms as z, getElements as zt };
6949
+ export { collectElements as $, spPrXfrmEmu as A, resolveRelativePartName as At, LST_STYLE_LEVELS as B, firstChildElement as Bt, Connector as C, SPPR_LN_AFTER as Ct, Paragraph as D, partNameToZipPath as Dt, Shape as E, partNameExtension as Et, resolveLayoutColorContext as F, childElements as Ft, placeholderInheritedRunProps as G, ownerDocumentOf as Gt, placeholderInheritedAnchor as H, getOrAddChild as Ht, resolveMasterColorContext as I, createElement as It, slideDefinesColor as J, removeChildrenByQName as Jt, placeholderInheritedXfrm as K, parseXml as Kt, resolveNotesColorContext as L, descendantsByTag as Lt, readPictureFill as M, zipPathToPartName as Mt, readGradientFill as N, attr as Nt, Run as O, relativePartName as Ot, resolveColorElement as P, boolValue as Pt, carriedDecorations as Q, resolveSlideColorContext as R, escapeXmlAttribute as Rt, ChartSeries as S, SPPR_FILL_AFTER as St, AutoShape as T, SPPR_SP3D_AFTER as Tt, placeholderInheritedColor as U, insertInOrder as Ut, RUN_PROP_NAMES as V, getElements as Vt, placeholderInheritedListStyles as W, intValue as Wt, cSldName as X, serializeXml as Xt, slideDefinesProp as Y, replaceInParent as Yt, cSldOf as Z, setAttr as Zt, ChartEx as _, FILL_CHOICES as _t, isConnector as a, fmtEntry as at, Chart as b, SPPR_AFTER_XFRM as bt, isPicture as c, parseClrMap as ct, Table as d, resolveSchemeToken as dt, firstShapeChild as et, TableCell as f, styleRefFill as ft, DiagramPoint as g, applyColorTransforms as gt, DiagramNode as h, presetColorHex as ht, isAutoShape as i, SCHEME_SLOTS as it, readPatternFill as j, resolveSingleRel as jt, TextFrame as k, relsPartNameFor as kt, wrapShapeElement as l, parseClrScheme as lt, Diagram as m, substitutePhClr as mt, buildShapes as n, nvPrOf as nt, isGraphicFrame as o, intAttr as ot, TableRow as p, styleRefLine as pt, placeholderOf as q, removeAttr as qt, findShapeByIdDeep as r, spTreeOf as rt, isGroupShape as s, isA as st, GroupShape as t, nthShapeChild as tt, GraphicFrame as u, resolveColor as ut, ChartExAxis as v, RPR_FILL_AFTER as vt, Picture as w, SPPR_SCENE3D_AFTER as wt, ChartAxis as x, SPPR_EFFECT_AFTER as xt, ChartExSeries as y, SHAPE_AFTER_SPPR as yt, resolveSlideThemeParts as z, firstChild as zt };
6246
6950
 
6247
- //# sourceMappingURL=shapes-Dyw5bxwO.js.map
6951
+ //# sourceMappingURL=shapes-BemroU-z.js.map