@shbernal/ts-pptx 0.0.1 → 2.0.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 (53) hide show
  1. package/README.md +3 -1
  2. package/dist/browser.d.ts +5 -5
  3. package/dist/browser.js +2 -2
  4. package/dist/{diagnostics-DzY9Tyfm.d.ts → diagnostics-CQ0JWc9m.d.ts} +4 -21
  5. package/dist/{diagnostics-DzY9Tyfm.d.ts.map → diagnostics-CQ0JWc9m.d.ts.map} +1 -1
  6. package/dist/{errors-DRLpOpgQ.d.ts → errors-B22h8Qd0.d.ts} +3 -3
  7. package/dist/{errors-DRLpOpgQ.d.ts.map → errors-B22h8Qd0.d.ts.map} +1 -1
  8. package/dist/errors-DGE9zh3U.js.map +1 -1
  9. package/dist/{fit-CbxyHN69.js → fit-BSWY0AYt.js} +0 -0
  10. package/dist/{fit-CbxyHN69.js.map → fit-BSWY0AYt.js.map} +1 -1
  11. package/dist/{html-dom-wVa3TiG7.js → html-dom-BQLUntho.js} +2 -2
  12. package/dist/html-dom-BQLUntho.js.map +1 -0
  13. package/dist/html.d.ts +3 -3
  14. package/dist/html.js +1 -1
  15. package/dist/{image-size-C5Z1sKof.js → image-size-C8uYE8GJ.js} +3 -3
  16. package/dist/{image-size-C5Z1sKof.js.map → image-size-C8uYE8GJ.js.map} +1 -1
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +2 -2
  19. package/dist/inspect.d.ts +1 -1
  20. package/dist/inspect.js +2 -2
  21. package/dist/math.d.ts +1 -1
  22. package/dist/math.js +2 -1
  23. package/dist/math.js.map +1 -1
  24. package/dist/measure.d.ts +2 -2
  25. package/dist/measure.js +1 -1
  26. package/dist/node.d.ts +5 -5
  27. package/dist/node.js +1 -1
  28. package/dist/{pres-5x1bjCzk.d.ts → pres-GU-jjBEx.d.ts} +2 -2
  29. package/dist/{pres-5x1bjCzk.d.ts.map → pres-GU-jjBEx.d.ts.map} +1 -1
  30. package/dist/{presentation-RGOiun5P.d.ts → presentation-CVaTZw1V.d.ts} +316 -18
  31. package/dist/presentation-CVaTZw1V.d.ts.map +1 -0
  32. package/dist/{presentation-B0JWdTN1.js → presentation-MJe9WDi4.js} +21 -27
  33. package/dist/presentation-MJe9WDi4.js.map +1 -0
  34. package/dist/read.d.ts +4 -4
  35. package/dist/read.js +2 -2
  36. package/dist/script.d.ts +2 -2
  37. package/dist/script.d.ts.map +1 -1
  38. package/dist/script.js +0 -0
  39. package/dist/script.js.map +1 -1
  40. package/dist/{shapes-BZU8UTW2.js → shapes-CaOgR0i1.js} +910 -22
  41. package/dist/shapes-CaOgR0i1.js.map +1 -0
  42. package/dist/{slide-B6mDaM-P.d.ts → slide-BBlElWr9.d.ts} +223 -92
  43. package/dist/slide-BBlElWr9.d.ts.map +1 -0
  44. package/dist/{text-WRUbOy2r.js → text-B1ZrPEeK.js} +441 -186
  45. package/dist/text-B1ZrPEeK.js.map +1 -0
  46. package/dist/zip.d.ts +1 -1
  47. package/package.json +9 -20
  48. package/dist/html-dom-wVa3TiG7.js.map +0 -1
  49. package/dist/presentation-B0JWdTN1.js.map +0 -1
  50. package/dist/presentation-RGOiun5P.d.ts.map +0 -1
  51. package/dist/shapes-BZU8UTW2.js.map +0 -1
  52. package/dist/slide-B6mDaM-P.d.ts.map +0 -1
  53. package/dist/text-WRUbOy2r.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { n as warn } from "./diagnostics-DYQC7xLV.js";
2
2
  import { i as PackageReadError, n as InvalidOptionError, o as UnsupportedFeatureError, t as InternalError } from "./errors-DGE9zh3U.js";
3
- import { r as getImageSizeFromBytes, t as fitSrcRectPercents } from "./image-size-C5Z1sKof.js";
3
+ import { r as getImageSizeFromBytes, t as fitSrcRectPercents } from "./image-size-C8uYE8GJ.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) {
@@ -1484,7 +1484,6 @@ function applyColorTransforms(baseHex, transforms) {
1484
1484
  if (f !== null) alpha = clamp01((alpha ?? 1) + f);
1485
1485
  break;
1486
1486
  }
1487
- default: break;
1488
1487
  }
1489
1488
  return alpha === void 0 ? { hex: toHex(rgb) } : {
1490
1489
  hex: toHex(rgb),
@@ -2011,6 +2010,38 @@ function readPictureFill(container, rels = null) {
2011
2010
  };
2012
2011
  }
2013
2012
  //#endregion
2013
+ //#region src/read/api/pattern-fill.ts
2014
+ /**
2015
+ * Shared pattern-fill reader for DrawingML colour-bearing containers: a shape's `p:spPr`, a
2016
+ * slide's `p:bg/p:bgPr`, and a table's `a:tblPr` / cell's `a:tcPr`.
2017
+ *
2018
+ * `a:pattFill` is a preset hatch name plus two wrapped colours, and decoding it is three
2019
+ * lines — which is exactly why it had been open-coded in each place that needed it. Sharing
2020
+ * it is what keeps the three from drifting into different answers for the same element, the
2021
+ * same reason {@link import('./gradient.js').readGradientFill} and
2022
+ * {@link import('./picture-fill.js').readPictureFill} are shared.
2023
+ */
2024
+ /**
2025
+ * Read a container's `a:pattFill`, or `null` when its fill is not a pattern.
2026
+ * @param {Element} container - the fill-bearing parent (`p:spPr`, `p:bgPr`, `a:tblPr`, `a:tcPr`, …)
2027
+ * @param {ColorContext} ctx - the theme colour context each colour resolves against
2028
+ * @returns {PatternFill | null} the decoded hatch, or `null`
2029
+ */
2030
+ function readPatternFill(container, ctx) {
2031
+ const patt = firstChild(container, "a:pattFill");
2032
+ if (!patt) return null;
2033
+ const wrapColor = (qname) => {
2034
+ const wrap = firstChild(patt, qname);
2035
+ const colorEl = wrap && firstChildElement(wrap);
2036
+ return colorEl ? resolveColorElement(colorEl, ctx) : null;
2037
+ };
2038
+ return {
2039
+ preset: attr(patt, "prst") ?? null,
2040
+ foreground: wrapColor("a:fgClr"),
2041
+ background: wrapColor("a:bgClr")
2042
+ };
2043
+ }
2044
+ //#endregion
2014
2045
  //#region src/read/api/shapes/oxml.ts
2015
2046
  /**
2016
2047
  * Shape-element plumbing: the schema-successor lists that keep a get-or-added child in document
@@ -2910,9 +2941,7 @@ function readGeometryPath(path) {
2910
2941
  swAng: (intValue(attr(seg, "swAng")) ?? 0) / 6e4
2911
2942
  });
2912
2943
  break;
2913
- case "close":
2914
- commands.push({ cmd: "close" });
2915
- break;
2944
+ case "close": commands.push({ cmd: "close" });
2916
2945
  }
2917
2946
  }
2918
2947
  return {
@@ -3491,19 +3520,7 @@ var Shape = class {
3491
3520
  */
3492
3521
  get patternFill() {
3493
3522
  const props = this.properties();
3494
- const patt = props && firstChild(props, "a:pattFill");
3495
- if (!patt) return null;
3496
- const ctx = this.slide.themeContext();
3497
- const resolveWrap = (qname) => {
3498
- const wrap = firstChild(patt, qname);
3499
- const colorEl = wrap && firstChildElement(wrap);
3500
- return colorEl ? resolveColorElement(colorEl, ctx) : null;
3501
- };
3502
- return {
3503
- preset: attr(patt, "prst") ?? null,
3504
- foreground: resolveWrap("a:fgClr"),
3505
- background: resolveWrap("a:bgClr")
3506
- };
3523
+ return props ? readPatternFill(props, this.slide.themeContext()) : null;
3507
3524
  }
3508
3525
  /**
3509
3526
  * The shape's picture (image) fill (`spPr/a:blipFill`), or `null` when the fill
@@ -4715,6 +4732,452 @@ function toNumberOrNull(value) {
4715
4732
  return Number.isFinite(number) ? number : null;
4716
4733
  }
4717
4734
  //#endregion
4735
+ //#region src/read/api/table-edit.ts
4736
+ /**
4737
+ * Ordered mutation of a table cell's `a:tcPr`, and the schema facts every such edit needs.
4738
+ *
4739
+ * `CT_TableCellProperties` declares its children as a **sequence**, so an append-only
4740
+ * implementation produces an out-of-order `a:tcPr` — which PowerPoint reports as a corrupt
4741
+ * file rather than as a bad edit. Every insertion therefore goes through
4742
+ * {@link getOrAddChild} / {@link insertInOrder} with the new element's *successors*, which
4743
+ * is what {@link TCPR_AFTER} tabulates. Writing that table once, here, is the point of this
4744
+ * module: a setter that hand-rolls its own successor list is one rename away from silently
4745
+ * producing an invalid part.
4746
+ *
4747
+ * The attribute values are the other half. A value outside its `ST_` union is equally fatal
4748
+ * and equally silent, so each is checked against the enum before it is written — the same
4749
+ * validate-at-the-boundary rule the write path follows, with the difference that here there
4750
+ * is a caller to throw at rather than a deck to degrade.
4751
+ */
4752
+ /**
4753
+ * `CT_TableCellProperties`' child sequence, in declaration order.
4754
+ * @see ECMA-376 Part 1 §21.1.3.17 (`a:tcPr`)
4755
+ */
4756
+ const TCPR_SEQUENCE = [
4757
+ "a:lnL",
4758
+ "a:lnR",
4759
+ "a:lnT",
4760
+ "a:lnB",
4761
+ "a:lnTlToBr",
4762
+ "a:lnBlToTr",
4763
+ "a:cell3D",
4764
+ "a:noFill",
4765
+ "a:solidFill",
4766
+ "a:gradFill",
4767
+ "a:blipFill",
4768
+ "a:pattFill",
4769
+ "a:grpFill",
4770
+ "a:headers",
4771
+ "a:extLst"
4772
+ ];
4773
+ /**
4774
+ * For each `a:tcPr` child, the children that must follow it — i.e. what a new one is
4775
+ * inserted *before*. Derived from {@link TCPR_SEQUENCE} rather than written out, so the two
4776
+ * cannot disagree and adding a child to the sequence updates every successor list at once.
4777
+ */
4778
+ const TCPR_AFTER = Object.fromEntries(TCPR_SEQUENCE.map((qname, index) => [qname, TCPR_SEQUENCE.slice(index + 1)]));
4779
+ /** The four edges plus the two diagonals, keyed as {@link TableCellEdge} names them. */
4780
+ const EDGE_QNAMES = {
4781
+ left: "a:lnL",
4782
+ right: "a:lnR",
4783
+ top: "a:lnT",
4784
+ bottom: "a:lnB",
4785
+ tlToBr: "a:lnTlToBr",
4786
+ blToTr: "a:lnBlToTr"
4787
+ };
4788
+ /** `ST_TextAnchoringType` — `a:tcPr/@anchor`. */
4789
+ const ANCHOR_VALUES = [
4790
+ "t",
4791
+ "ctr",
4792
+ "b",
4793
+ "just",
4794
+ "dist"
4795
+ ];
4796
+ /** `ST_TextVerticalType` — `a:tcPr/@vert`. */
4797
+ const VERT_VALUES = [
4798
+ "horz",
4799
+ "vert",
4800
+ "vert270",
4801
+ "wordArtVert",
4802
+ "eaVert",
4803
+ "mongolianVert",
4804
+ "wordArtVertRtl"
4805
+ ];
4806
+ /** `ST_TextHorzOverflowType` — `a:tcPr/@horzOverflow`. */
4807
+ const HORZ_OVERFLOW_VALUES = ["clip", "overflow"];
4808
+ /**
4809
+ * Check a value against its `ST_` union, throwing when it is outside.
4810
+ *
4811
+ * Throwing rather than warn-and-drop, which is the opposite of what the *write* path does
4812
+ * with the same enums — and deliberately so. On the write path an option comes from a deck
4813
+ * being built, and dropping one value is better than failing a whole build; here it comes
4814
+ * from a caller editing one attribute, so silently doing nothing would leave them looking at
4815
+ * an unchanged deck with no explanation.
4816
+ *
4817
+ * @param {string} value - the value the caller asked for
4818
+ * @param {readonly string[]} valid - the enum's members
4819
+ * @param {string} attribute - the attribute's name, for the message
4820
+ * @param {InvalidOptionErrorCode} code - the condition to report
4821
+ * @returns {string} `value`, when it is legal
4822
+ */
4823
+ function checkEnum(value, valid, attribute, code) {
4824
+ if (valid.includes(value)) return value;
4825
+ throw new InvalidOptionError(code, `Invalid ${attribute}: ${JSON.stringify(value)}. Expected one of: ${valid.join(", ")}.`);
4826
+ }
4827
+ /**
4828
+ * Get or create a child of `a:tcPr` at its schema position.
4829
+ * @param {Element} tcPr - the cell's properties element
4830
+ * @param {string} qname - the child to get or create
4831
+ * @returns {Element} the existing or newly-inserted child
4832
+ */
4833
+ function tcPrChild(tcPr, qname) {
4834
+ return getOrAddChild(tcPr, qname, TCPR_AFTER[qname] ?? []);
4835
+ }
4836
+ /**
4837
+ * Insert an already-built subtree into `a:tcPr` at its schema position.
4838
+ * @param {Element} tcPr - the cell's properties element
4839
+ * @param {string} qname - the qname of the node being inserted (decides where it goes)
4840
+ * @param {Element} node - the subtree to insert
4841
+ */
4842
+ function insertTcPrChild(tcPr, qname, node) {
4843
+ insertInOrder(tcPr, node, TCPR_AFTER[qname] ?? []);
4844
+ }
4845
+ /**
4846
+ * Round a measurement to whole EMU, rejecting a value that cannot be written.
4847
+ * `NaN`/`Infinity` would reach the attribute verbatim and make the part invalid, which is
4848
+ * exactly the "silent coercion of invalid input" the project's policy rules out.
4849
+ */
4850
+ function checkFiniteEmu(value, field, code) {
4851
+ if (!Number.isFinite(value)) throw new InvalidOptionError(code, `${field} must be a finite number of EMU, got: ${String(value)}`);
4852
+ return Math.round(value);
4853
+ }
4854
+ //#endregion
4855
+ //#region src/read/api/table-structure.ts
4856
+ /**
4857
+ * Structural edits to an existing `a:tbl`: adding and removing rows and columns, and
4858
+ * merging and unmerging cells.
4859
+ *
4860
+ * The one fact that makes this tractable is that a stored table's grid is already
4861
+ * **rectangular**. Unlike the write path — which receives lopsided row arrays and has to
4862
+ * build the merge grid itself (`gen/slide/objects/table.ts` STEP 3) — every `a:tr` in a
4863
+ * conformant `a:tbl` holds exactly one `a:tc` per grid column, with the covered half of each
4864
+ * span present and flagged. So "the cell at (r, c)" is simply the c-th `a:tc` of the r-th
4865
+ * `a:tr`, and an edit's whole job is keeping that rectangle true.
4866
+ *
4867
+ * Three things have to stay in step, and every function here is written around them:
4868
+ *
4869
+ * 1. `a:tblGrid/a:gridCol` count == each row's `a:tc` count.
4870
+ * 2. A cell with `@gridSpan="n"` is followed by exactly `n-1` cells carrying `@hMerge="1"`,
4871
+ * and a cell with `@rowSpan="n"` is under-hung by `n-1` cells carrying `@vMerge="1"`, in
4872
+ * the same columns.
4873
+ * 3. A covered cell carries no span attributes of its own — its extent is the origin's.
4874
+ *
4875
+ * Breaking any of them produces a table PowerPoint reports as a corrupt file rather than as
4876
+ * a bad edit, which is why the span bookkeeping here is explicit rather than incidental.
4877
+ */
4878
+ /** Every `a:tr` of a table, in document order. */
4879
+ function rowsOf(tbl) {
4880
+ return getElements(tbl, "a:tr");
4881
+ }
4882
+ /** Every `a:tc` of a row, in document (left-to-right) order. */
4883
+ function cellsOf(tr) {
4884
+ return getElements(tr, "a:tc");
4885
+ }
4886
+ /** The table's `a:tblGrid`, or a thrown error — `CT_Table` requires one. */
4887
+ function gridOf(tbl) {
4888
+ const grid = firstChild(tbl, "a:tblGrid");
4889
+ if (!grid) throw new InvalidOptionError("table/column-index-out-of-range", "This table has no a:tblGrid, so its columns cannot be edited");
4890
+ return grid;
4891
+ }
4892
+ /** A cell's `@gridSpan`, defaulting to 1. */
4893
+ function gridSpanOf(tc) {
4894
+ return intValue(attr(tc, "gridSpan")) ?? 1;
4895
+ }
4896
+ /** A cell's `@rowSpan`, defaulting to 1. */
4897
+ function rowSpanOf(tc) {
4898
+ return intValue(attr(tc, "rowSpan")) ?? 1;
4899
+ }
4900
+ /** Whether a cell is the covered half of a horizontal merge. */
4901
+ function isHMerge(tc) {
4902
+ return attr(tc, "hMerge") === "1" || attr(tc, "hMerge") === "true";
4903
+ }
4904
+ /** Whether a cell is the covered half of a vertical merge. */
4905
+ function isVMerge(tc) {
4906
+ return attr(tc, "vMerge") === "1" || attr(tc, "vMerge") === "true";
4907
+ }
4908
+ /**
4909
+ * Set a span attribute, removing it when the span collapses to 1 — the schema default, and
4910
+ * what PowerPoint itself writes for an unmerged cell.
4911
+ */
4912
+ function setSpan(tc, name, value) {
4913
+ if (value <= 1) removeAttr(tc, name);
4914
+ else setAttr(tc, name, String(value));
4915
+ }
4916
+ /**
4917
+ * Walk left from `col` to the origin of the horizontal run containing it.
4918
+ * A run is an origin followed by its `hMerge` continuations, so the origin is the first
4919
+ * cell at or left of `col` that is not one.
4920
+ * @returns the origin's column index, or `col` itself when the cell stands alone
4921
+ */
4922
+ function hRunOrigin(cells, col) {
4923
+ let idx = col;
4924
+ while (idx > 0) {
4925
+ const cell = cells[idx];
4926
+ if (!cell || !isHMerge(cell)) break;
4927
+ idx -= 1;
4928
+ }
4929
+ return idx;
4930
+ }
4931
+ /**
4932
+ * Walk up from `row` to the origin of the vertical run containing `(row, col)`.
4933
+ * @returns the origin's row index, or `row` itself when the cell stands alone
4934
+ */
4935
+ function vRunOrigin(rows, row, col) {
4936
+ let idx = row;
4937
+ while (idx > 0) {
4938
+ const cell = rows[idx]?.[col];
4939
+ if (!cell || !isVMerge(cell)) break;
4940
+ idx -= 1;
4941
+ }
4942
+ return idx;
4943
+ }
4944
+ /** A fresh, empty `a:tc`, shaped the way PowerPoint writes one. */
4945
+ function makeCell(doc) {
4946
+ const tc = createElement(doc, "a:tc");
4947
+ const txBody = createElement(doc, "a:txBody");
4948
+ txBody.appendChild(createElement(doc, "a:bodyPr"));
4949
+ txBody.appendChild(createElement(doc, "a:lstStyle"));
4950
+ txBody.appendChild(createElement(doc, "a:p"));
4951
+ tc.appendChild(txBody);
4952
+ tc.appendChild(createElement(doc, "a:tcPr"));
4953
+ return tc;
4954
+ }
4955
+ /** The grid as a row-major array of `a:tc` elements. */
4956
+ function gridOfCells(tbl) {
4957
+ return rowsOf(tbl).map((tr) => cellsOf(tr));
4958
+ }
4959
+ /** Guard an index against `[0, limit]` (inclusive upper bound, for insertion points). */
4960
+ function checkIndex(index, limit, what, inclusive) {
4961
+ const max = inclusive ? limit : limit - 1;
4962
+ if (!Number.isInteger(index) || index < 0 || index > max) throw new InvalidOptionError(what === "row" ? "table/row-index-out-of-range" : "table/column-index-out-of-range", `${what} index ${String(index)} is out of range (expected 0..${max})`);
4963
+ return index;
4964
+ }
4965
+ /**
4966
+ * Insert a row at `index` (default: append).
4967
+ *
4968
+ * The subtle case is inserting **through** a vertical merge. If the row currently at `index`
4969
+ * holds a `vMerge` continuation in some column, the span it belongs to straddles the
4970
+ * insertion point — so the new row must continue that span rather than interrupt it: the
4971
+ * origin's `@rowSpan` grows by one and the new cell is another continuation. Interrupting it
4972
+ * instead would leave an origin claiming more rows than it has continuations, which is the
4973
+ * corrupt-file case.
4974
+ */
4975
+ function insertRow(tbl, index) {
4976
+ const rows = rowsOf(tbl);
4977
+ const at = index === void 0 ? rows.length : checkIndex(index, rows.length, "row", true);
4978
+ const grid = gridOfCells(tbl);
4979
+ const colCount = getElements(gridOf(tbl), "a:gridCol").length;
4980
+ const doc = ownerDocumentOf(tbl);
4981
+ const tr = createElement(doc, "a:tr");
4982
+ setAttr(tr, "h", "0");
4983
+ for (let col = 0; col < colCount; col++) {
4984
+ const tc = makeCell(doc);
4985
+ const displaced = grid[at]?.[col];
4986
+ if (displaced && isVMerge(displaced)) {
4987
+ const origin = grid[vRunOrigin(grid, at, col)]?.[col];
4988
+ if (origin) setSpan(origin, "rowSpan", rowSpanOf(origin) + 1);
4989
+ setAttr(tc, "vMerge", "1");
4990
+ if (isHMerge(displaced)) setAttr(tc, "hMerge", "1");
4991
+ const span = gridSpanOf(displaced);
4992
+ if (span > 1) setSpan(tc, "gridSpan", span);
4993
+ }
4994
+ tr.appendChild(tc);
4995
+ }
4996
+ const before = rows[at] ?? null;
4997
+ tbl.insertBefore(tr, before);
4998
+ return tr;
4999
+ }
5000
+ /**
5001
+ * Remove the row at `index`.
5002
+ *
5003
+ * Two span cases, and they pull in opposite directions. A cell in this row that *continues*
5004
+ * a span from above shortens that span by one. A cell in this row that *starts* one cannot
5005
+ * simply take its span away — the rows below still hold its continuations — so the first
5006
+ * continuation is promoted to origin and inherits the remaining extent. Its content is gone
5007
+ * with the row, which is inherent to removing a row rather than a choice made here.
5008
+ */
5009
+ function removeRow(tbl, index) {
5010
+ const rows = rowsOf(tbl);
5011
+ const at = checkIndex(index, rows.length, "row", false);
5012
+ const grid = gridOfCells(tbl);
5013
+ const row = grid[at] ?? [];
5014
+ for (let col = 0; col < row.length; col++) {
5015
+ const tc = row[col];
5016
+ if (!tc) continue;
5017
+ if (isVMerge(tc)) {
5018
+ const origin = grid[vRunOrigin(grid, at, col)]?.[col];
5019
+ if (origin) setSpan(origin, "rowSpan", rowSpanOf(origin) - 1);
5020
+ continue;
5021
+ }
5022
+ const span = rowSpanOf(tc);
5023
+ if (span > 1) {
5024
+ const heir = grid[at + 1]?.[col];
5025
+ if (heir) {
5026
+ removeAttr(heir, "vMerge");
5027
+ setSpan(heir, "rowSpan", span - 1);
5028
+ setSpan(heir, "gridSpan", gridSpanOf(tc));
5029
+ }
5030
+ }
5031
+ }
5032
+ const tr = rows[at];
5033
+ if (tr) tbl.removeChild(tr);
5034
+ }
5035
+ /**
5036
+ * Insert a column at `index` (default: append), `widthEmu` wide.
5037
+ *
5038
+ * Mirrors {@link insertRow}'s split case on the other axis: when the cell currently at
5039
+ * `index` is an `hMerge` continuation, the insertion point falls inside a horizontal span,
5040
+ * so the span widens by one and the new cell joins it as another continuation.
5041
+ */
5042
+ function insertColumn(tbl, index, widthEmu = 914400) {
5043
+ const grid = gridOf(tbl);
5044
+ const cols = getElements(grid, "a:gridCol");
5045
+ const at = index === void 0 ? cols.length : checkIndex(index, cols.length, "column", true);
5046
+ const doc = ownerDocumentOf(tbl);
5047
+ const gridCol = createElement(doc, "a:gridCol");
5048
+ setAttr(gridCol, "w", String(Math.round(widthEmu)));
5049
+ grid.insertBefore(gridCol, cols[at] ?? null);
5050
+ for (const tr of rowsOf(tbl)) {
5051
+ const cells = cellsOf(tr);
5052
+ const tc = makeCell(doc);
5053
+ const displaced = cells[at];
5054
+ if (displaced && isHMerge(displaced)) {
5055
+ const origin = cells[hRunOrigin(cells, at)];
5056
+ if (origin) setSpan(origin, "gridSpan", gridSpanOf(origin) + 1);
5057
+ setAttr(tc, "hMerge", "1");
5058
+ if (isVMerge(displaced)) setAttr(tc, "vMerge", "1");
5059
+ const span = rowSpanOf(displaced);
5060
+ if (span > 1) setSpan(tc, "rowSpan", span);
5061
+ }
5062
+ tr.insertBefore(tc, cells[at] ?? firstChild(tr, "a:extLst"));
5063
+ }
5064
+ return gridCol;
5065
+ }
5066
+ /**
5067
+ * Remove the column at `index`.
5068
+ *
5069
+ * When the column falls inside a horizontal span, the span shrinks by one and a
5070
+ * *continuation* is removed rather than the origin — so the merged region narrows but keeps
5071
+ * its content. Only a column whose cell stands alone loses that cell outright.
5072
+ */
5073
+ function removeColumn(tbl, index) {
5074
+ const grid = gridOf(tbl);
5075
+ const cols = getElements(grid, "a:gridCol");
5076
+ const at = checkIndex(index, cols.length, "column", false);
5077
+ const col = cols[at];
5078
+ if (col) grid.removeChild(col);
5079
+ for (const tr of rowsOf(tbl)) {
5080
+ const cells = cellsOf(tr);
5081
+ const originIdx = hRunOrigin(cells, at);
5082
+ const origin = cells[originIdx];
5083
+ if (!origin) continue;
5084
+ const span = gridSpanOf(origin);
5085
+ if (span > 1) {
5086
+ setSpan(origin, "gridSpan", span - 1);
5087
+ const victim = cells[originIdx + span - 1];
5088
+ if (victim) tr.removeChild(victim);
5089
+ continue;
5090
+ }
5091
+ const tc = cells[at];
5092
+ if (tc) tr.removeChild(tc);
5093
+ }
5094
+ }
5095
+ /**
5096
+ * Merge the rectangle `(row1, col1)`–`(row2, col2)` into one cell.
5097
+ *
5098
+ * The top-left cell becomes the origin and keeps its content; every other cell in the
5099
+ * rectangle becomes a covered cell — flagged, stripped of its own span attributes, and
5100
+ * emptied, since a covered cell is never rendered.
5101
+ *
5102
+ * A rectangle whose boundary **cuts through** an existing merge is rejected rather than
5103
+ * silently widened. Widening would be the friendlier-looking choice and the wrong one: the
5104
+ * caller asked for a specific region, and quietly returning a different one is how a layout
5105
+ * ends up subtly wrong with nothing to point at.
5106
+ */
5107
+ function mergeCells(tbl, row1, col1, row2, col2) {
5108
+ const grid = gridOfCells(tbl);
5109
+ const rowCount = grid.length;
5110
+ const colCount = getElements(gridOf(tbl), "a:gridCol").length;
5111
+ const r1 = Math.min(row1, row2);
5112
+ const r2 = Math.max(row1, row2);
5113
+ const c1 = Math.min(col1, col2);
5114
+ const c2 = Math.max(col1, col2);
5115
+ checkIndex(r1, rowCount, "row", false);
5116
+ checkIndex(r2, rowCount, "row", false);
5117
+ checkIndex(c1, colCount, "column", false);
5118
+ checkIndex(c2, colCount, "column", false);
5119
+ if (r1 === r2 && c1 === c2) throw new InvalidOptionError("table/merge-range-invalid", "A merge needs at least two cells; the given range covers one");
5120
+ for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) {
5121
+ if (!grid[r]?.[c]) continue;
5122
+ const originRow = vRunOrigin(grid, r, c);
5123
+ const originCol = hRunOrigin(grid[r] ?? [], c);
5124
+ const origin = grid[originRow]?.[originCol];
5125
+ if (!origin) continue;
5126
+ if (originRow < r1 || originCol < c1) throw new InvalidOptionError("table/merge-range-invalid", `The range (${r1},${c1})-(${r2},${c2}) starts inside an existing merged cell at (${originRow},${originCol}); unmerge it first`);
5127
+ if (originRow + rowSpanOf(origin) - 1 > r2 || originCol + gridSpanOf(origin) - 1 > c2) throw new InvalidOptionError("table/merge-range-invalid", `The range (${r1},${c1})-(${r2},${c2}) cuts through an existing merged cell at (${originRow},${originCol}); unmerge it first`);
5128
+ }
5129
+ const origin = grid[r1]?.[c1];
5130
+ if (!origin) return;
5131
+ setSpan(origin, "gridSpan", c2 - c1 + 1);
5132
+ setSpan(origin, "rowSpan", r2 - r1 + 1);
5133
+ removeAttr(origin, "hMerge");
5134
+ removeAttr(origin, "vMerge");
5135
+ for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) {
5136
+ if (r === r1 && c === c1) continue;
5137
+ const tc = grid[r]?.[c];
5138
+ if (!tc) continue;
5139
+ removeAttr(tc, "gridSpan");
5140
+ removeAttr(tc, "rowSpan");
5141
+ if (c > c1) setAttr(tc, "hMerge", "1");
5142
+ else removeAttr(tc, "hMerge");
5143
+ if (r > r1) setAttr(tc, "vMerge", "1");
5144
+ else removeAttr(tc, "vMerge");
5145
+ emptyCellText(tc);
5146
+ }
5147
+ }
5148
+ /**
5149
+ * Split the merged cell whose origin is `(row, col)` back into individual cells.
5150
+ * The origin keeps its content; the cells it covered come back empty, which is what they
5151
+ * already were.
5152
+ */
5153
+ function unmergeCell(tbl, row, col) {
5154
+ const grid = gridOfCells(tbl);
5155
+ checkIndex(row, grid.length, "row", false);
5156
+ checkIndex(col, getElements(gridOf(tbl), "a:gridCol").length, "column", false);
5157
+ const origin = grid[row]?.[col];
5158
+ if (!origin) return;
5159
+ if (isHMerge(origin) || isVMerge(origin)) throw new InvalidOptionError("table/merge-range-invalid", `The cell at (${row},${col}) is a covered cell, not a merge origin; unmerge the origin at (${vRunOrigin(grid, row, col)},${hRunOrigin(grid[row] ?? [], col)}) instead`);
5160
+ const rowSpan = rowSpanOf(origin);
5161
+ const gridSpan = gridSpanOf(origin);
5162
+ if (rowSpan === 1 && gridSpan === 1) return;
5163
+ removeAttr(origin, "gridSpan");
5164
+ removeAttr(origin, "rowSpan");
5165
+ for (let r = row; r < row + rowSpan; r++) for (let c = col; c < col + gridSpan; c++) {
5166
+ if (r === row && c === col) continue;
5167
+ const tc = grid[r]?.[c];
5168
+ if (!tc) continue;
5169
+ removeAttr(tc, "hMerge");
5170
+ removeAttr(tc, "vMerge");
5171
+ }
5172
+ }
5173
+ /** Reduce a cell's text body to a single empty paragraph. */
5174
+ function emptyCellText(tc) {
5175
+ const txBody = firstChild(tc, "a:txBody");
5176
+ if (!txBody) return;
5177
+ for (const p of getElements(txBody, "a:p")) txBody.removeChild(p);
5178
+ txBody.appendChild(createElement(ownerDocumentOf(txBody), "a:p"));
5179
+ }
5180
+ //#endregion
4718
5181
  //#region src/read/api/table-style-resolve.ts
4719
5182
  const TABLE_STYLES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
4720
5183
  /**
@@ -4893,6 +5356,63 @@ var Table = class {
4893
5356
  const id = (tblPr && firstChild(tblPr, "a:tableStyleId"))?.textContent?.trim();
4894
5357
  return id ? id : null;
4895
5358
  }
5359
+ /**
5360
+ * The table's own background (`a:tblPr` fill) resolved against the slide's theme colour
5361
+ * context to a literal hex, or `null` when the table carries no solid background.
5362
+ *
5363
+ * This is the table-level counterpart of {@link TableCell.resolvedFill}, and it is a
5364
+ * genuinely different thing from a cell fill: a `a:tblPr` fill sits *behind* the grid, so
5365
+ * a cell with no fill of its own shows it through. Reports `null` for a non-solid choice
5366
+ * (`a:blipFill`/`a:gradFill`/`a:pattFill`/`a:noFill`) — read {@link pictureFill} for an
5367
+ * image background — and `null` with no theme context.
5368
+ */
5369
+ get resolvedFill() {
5370
+ if (!this.themeColors) return null;
5371
+ const tblPr = firstChild(this.tbl, "a:tblPr");
5372
+ if (!tblPr || !FILL_CHOICES.some((q) => firstChild(tblPr, q))) return null;
5373
+ return resolveSolidFillColor(tblPr, this.themeColors);
5374
+ }
5375
+ /**
5376
+ * The table's picture background (`a:tblPr/a:blipFill`), or `null` when the table is not
5377
+ * image-backed. The table-level twin of {@link TableCell.pictureFill}, and needed for the
5378
+ * same reason: {@link resolvedFill} decodes only solid colours, so without this an
5379
+ * image-backed table is indistinguishable from an unfilled one.
5380
+ */
5381
+ get pictureFill() {
5382
+ const tblPr = firstChild(this.tbl, "a:tblPr");
5383
+ return tblPr ? readPictureFill(tblPr, this.rels ?? null) : null;
5384
+ }
5385
+ /**
5386
+ * The table's gradient background (`a:tblPr/a:gradFill`), or `null` when it is not
5387
+ * gradient-filled. Needed for the same reason {@link pictureFill} is:
5388
+ * {@link resolvedFill} decodes only solid colours.
5389
+ */
5390
+ get gradientFill() {
5391
+ if (!this.themeColors) return null;
5392
+ const tblPr = firstChild(this.tbl, "a:tblPr");
5393
+ return tblPr ? readGradientFill(tblPr, this.themeColors) : null;
5394
+ }
5395
+ /**
5396
+ * The table's pattern (hatch) background (`a:tblPr/a:pattFill`), or `null` when it is not
5397
+ * pattern-filled.
5398
+ */
5399
+ get patternFill() {
5400
+ if (!this.themeColors) return null;
5401
+ const tblPr = firstChild(this.tbl, "a:tblPr");
5402
+ return tblPr ? readPatternFill(tblPr, this.themeColors) : null;
5403
+ }
5404
+ /**
5405
+ * The raw `schemeClr` token of the table's own background
5406
+ * (`a:tblPr/a:solidFill/a:schemeClr/@val`), or `null` for an absent or `srgbClr` fill.
5407
+ * {@link resolvedFill} is the literal it resolves to; this is the unresolved reference,
5408
+ * which is what a replica should carry so the copy still tracks its theme.
5409
+ */
5410
+ get fillSchemeColor() {
5411
+ const tblPr = firstChild(this.tbl, "a:tblPr");
5412
+ const fill = tblPr && firstChild(tblPr, "a:solidFill");
5413
+ const scheme = fill && firstChild(fill, "a:schemeClr");
5414
+ return scheme ? attr(scheme, "val") : null;
5415
+ }
4896
5416
  /** Whether the first row is styled as a header (`a:tblPr/@firstRow`). */
4897
5417
  get firstRowHeader() {
4898
5418
  return this.#tblPrFlag("firstRow");
@@ -4909,6 +5429,83 @@ var Table = class {
4909
5429
  cell(rowIndex, columnIndex) {
4910
5430
  return this.rows[rowIndex]?.cells[columnIndex] ?? null;
4911
5431
  }
5432
+ /**
5433
+ * Insert a row, defaulting to the bottom of the table. Returns the new {@link TableRow}.
5434
+ *
5435
+ * The new row is auto-height (`a:tr/@h="0"`) and its cells are empty. Inserting *through*
5436
+ * a vertical merge extends that merge rather than interrupting it: the span's origin
5437
+ * grows by a row and the new cell joins as a continuation, because an origin claiming
5438
+ * more rows than it has continuations is a table PowerPoint reports as corrupt.
5439
+ * @param {number} [index] - where to insert, `0`..`rowCount`; appended when omitted
5440
+ * @throws {InvalidOptionError} when `index` is outside that range
5441
+ */
5442
+ addRow(index) {
5443
+ const tr = insertRow(this.tbl, index);
5444
+ this.part.markDirty();
5445
+ return new TableRow(tr, this.part, this.themeColors, this.#styleContext(), rowsOf(this.tbl).indexOf(tr), this.rels);
5446
+ }
5447
+ /**
5448
+ * Remove the row at `index`, with its content.
5449
+ *
5450
+ * A cell in the row that *continues* a vertical merge shortens that merge. A cell that
5451
+ * *starts* one hands the region to its first continuation, which becomes the new origin —
5452
+ * the merged region survives one row shorter, and only the removed row's own text is lost.
5453
+ * @throws {InvalidOptionError} when `index` is out of range
5454
+ */
5455
+ removeRow(index) {
5456
+ removeRow(this.tbl, index);
5457
+ this.part.markDirty();
5458
+ }
5459
+ /**
5460
+ * Insert a column, defaulting to the right-hand end. Updates `a:tblGrid` **and** every
5461
+ * row, which is the pair that has to stay in step.
5462
+ *
5463
+ * The mirror of {@link addRow}'s merge case: inserting inside a horizontal merge widens
5464
+ * it instead of splitting it.
5465
+ * @param {number} [index] - where to insert, `0`..`columnCount`; appended when omitted
5466
+ * @param {number} [widthEmu] - the new column's width; defaults to 1 inch
5467
+ * @throws {InvalidOptionError} when `index` is outside that range
5468
+ */
5469
+ addColumn(index, widthEmu) {
5470
+ insertColumn(this.tbl, index, widthEmu);
5471
+ this.part.markDirty();
5472
+ }
5473
+ /**
5474
+ * Remove the column at `index`, from `a:tblGrid` and from every row.
5475
+ *
5476
+ * Inside a horizontal merge the region narrows by one and keeps its content: a covered
5477
+ * cell is dropped rather than the origin. Elsewhere the column's cells go with it.
5478
+ * @throws {InvalidOptionError} when `index` is out of range
5479
+ */
5480
+ removeColumn(index) {
5481
+ removeColumn(this.tbl, index);
5482
+ this.part.markDirty();
5483
+ }
5484
+ /**
5485
+ * Merge the rectangle between two cell positions into one cell. The top-left cell keeps
5486
+ * its content; the rest become covered cells and are emptied, since a covered cell is
5487
+ * never rendered.
5488
+ *
5489
+ * A rectangle that cuts through an existing merge is **rejected**, not silently widened
5490
+ * to fit — the caller asked for a specific region, and quietly producing a different one
5491
+ * is how a layout ends up wrong with nothing to point at. Unmerge first.
5492
+ * @throws {InvalidOptionError} when an index is out of range, the range covers one cell,
5493
+ * or it partially overlaps an existing merge
5494
+ */
5495
+ mergeCells(row1, col1, row2, col2) {
5496
+ mergeCells(this.tbl, row1, col1, row2, col2);
5497
+ this.part.markDirty();
5498
+ }
5499
+ /**
5500
+ * Split the merged cell whose **origin** is `(row, col)` back into individual cells.
5501
+ * A no-op on a cell that is not merged; addressing a covered cell instead of the origin
5502
+ * throws, and the message names the origin to use.
5503
+ * @throws {InvalidOptionError} when an index is out of range or the cell is a covered cell
5504
+ */
5505
+ unmergeCell(row, col) {
5506
+ unmergeCell(this.tbl, row, col);
5507
+ this.part.markDirty();
5508
+ }
4912
5509
  /** Escape hatch: the underlying `a:tbl` element. After mutating it call {@link markDirty}, or `save()` writes the original bytes. */
4913
5510
  get element_() {
4914
5511
  return this.tbl;
@@ -4999,6 +5596,183 @@ var TableCell = class {
4999
5596
  return firstChild(this.tc, "a:tcPr");
5000
5597
  }
5001
5598
  /**
5599
+ * The cell's `a:tcPr`, creating it when absent.
5600
+ *
5601
+ * `CT_TableCell` sequences `a:txBody` then `a:tcPr` then `a:extLst`, so a newly created
5602
+ * one is inserted before `a:extLst` — appending it blindly would put it after and make
5603
+ * the part invalid. Every setter below goes through here for that reason.
5604
+ */
5605
+ #getOrAddTcPr() {
5606
+ return getOrAddChild(this.tc, "a:tcPr", ["a:extLst"]);
5607
+ }
5608
+ /**
5609
+ * Set (or clear) the cell's vertical text anchor (`a:tcPr/@anchor`).
5610
+ * `null` removes the attribute, leaving PowerPoint's default of top.
5611
+ * @throws {InvalidOptionError} when the value is outside `ST_TextAnchoringType`
5612
+ */
5613
+ setAnchor(value) {
5614
+ this.#setTcPrAttr("anchor", value, ANCHOR_VALUES, "table/invalid-cell-anchor");
5615
+ }
5616
+ /**
5617
+ * Set (or clear) the cell's text direction (`a:tcPr/@vert`), e.g. `'vert270'`.
5618
+ * `null` removes the attribute, leaving the default horizontal text.
5619
+ * @throws {InvalidOptionError} when the value is outside `ST_TextVerticalType`
5620
+ */
5621
+ setVerticalText(value) {
5622
+ this.#setTcPrAttr("vert", value, VERT_VALUES, "table/invalid-cell-vert");
5623
+ }
5624
+ /**
5625
+ * Set (or clear) `a:tcPr/@horzOverflow` — what a single glyph too wide for the cell does.
5626
+ * `null` removes the attribute; so does `'clip'` in effect, since it is the schema
5627
+ * default, but the attribute is still written as asked.
5628
+ * @throws {InvalidOptionError} when the value is outside `ST_TextHorzOverflowType`
5629
+ */
5630
+ setHorzOverflow(value) {
5631
+ this.#setTcPrAttr("horzOverflow", value, HORZ_OVERFLOW_VALUES, "table/invalid-cell-overflow");
5632
+ }
5633
+ /**
5634
+ * Centre the cell's text block horizontally, or stop doing so (`a:tcPr/@anchorCtr`).
5635
+ * `false` removes the attribute rather than writing `"0"`, since `false` is the schema
5636
+ * default and the two are indistinguishable to a renderer.
5637
+ */
5638
+ setAnchorCtr(value) {
5639
+ const tcPr = this.#getOrAddTcPr();
5640
+ if (value) setAttr(tcPr, "anchorCtr", "1");
5641
+ else removeAttr(tcPr, "anchorCtr");
5642
+ this.part.markDirty();
5643
+ }
5644
+ /**
5645
+ * Set the cell's text insets in EMU (`a:tcPr/@marL`/`@marR`/`@marT`/`@marB`).
5646
+ *
5647
+ * Partial: only the sides named are touched, so `{ left: 0 }` flushes the text left and
5648
+ * leaves the other three alone. A side given as `null` has its attribute removed, which
5649
+ * returns it to the schema default (91440 EMU left/right, 45720 top/bottom) — not to
5650
+ * zero. Pass `{}` to change nothing.
5651
+ * @throws {InvalidOptionError} when a value is not a finite number
5652
+ */
5653
+ setMarginsEmu(margins) {
5654
+ const tcPr = this.#getOrAddTcPr();
5655
+ const sides = [
5656
+ ["marL", margins.left],
5657
+ ["marR", margins.right],
5658
+ ["marT", margins.top],
5659
+ ["marB", margins.bottom]
5660
+ ];
5661
+ for (const [name, value] of sides) {
5662
+ if (value === void 0) continue;
5663
+ if (value === null) removeAttr(tcPr, name);
5664
+ else setAttr(tcPr, name, String(checkFiniteEmu(value, name, "table/invalid-cell-margin")));
5665
+ }
5666
+ this.part.markDirty();
5667
+ }
5668
+ /**
5669
+ * Set (or clear) one of the cell's six borders — the four edges and the two diagonals.
5670
+ *
5671
+ * `null` removes the element entirely, which is "inherit", and is a different thing from
5672
+ * `{ noFill: true }`, which writes an explicit no-border and suppresses whatever would
5673
+ * otherwise be inherited. The element is inserted at its schema position rather than
5674
+ * appended: `CT_TableCellProperties` is a sequence, and an out-of-order `a:tcPr` is
5675
+ * reported by PowerPoint as a corrupt file rather than as a bad edit.
5676
+ *
5677
+ * Colour is either `color` (6-hex, `#` optional) or `schemeColor` (a theme token); giving
5678
+ * both prefers the token, matching how the read side reports them.
5679
+ * @throws {InvalidOptionError} when the width or colour cannot be written
5680
+ */
5681
+ setBorder(edge, border) {
5682
+ const qname = EDGE_QNAMES[edge];
5683
+ if (!qname) throw new InvalidOptionError("table/invalid-cell-border", `Unknown table cell edge: ${JSON.stringify(edge)}. Expected one of: ${Object.keys(EDGE_QNAMES).join(", ")}.`);
5684
+ if (border === null) {
5685
+ const tcPr = this.#tcPr();
5686
+ if (!tcPr || !firstChild(tcPr, qname)) return;
5687
+ removeChildrenByQName(tcPr, [qname]);
5688
+ this.part.markDirty();
5689
+ return;
5690
+ }
5691
+ const tcPr = this.#getOrAddTcPr();
5692
+ removeChildrenByQName(tcPr, [qname]);
5693
+ const doc = ownerDocumentOf(tcPr);
5694
+ const ln = createElement(doc, qname);
5695
+ if (border.widthPt !== void 0 && border.widthPt !== null) setAttr(ln, "w", String(checkFiniteEmu(border.widthPt * 12700, "widthPt", "table/invalid-cell-border")));
5696
+ if (border.noFill) ln.appendChild(createElement(doc, "a:noFill"));
5697
+ else if (border.schemeColor || border.color) {
5698
+ const fill = createElement(doc, "a:solidFill");
5699
+ const scheme = border.schemeColor;
5700
+ const clr = createElement(doc, scheme ? "a:schemeClr" : "a:srgbClr");
5701
+ setAttr(clr, "val", scheme ? scheme : normalizeHex(border.color));
5702
+ fill.appendChild(clr);
5703
+ ln.appendChild(fill);
5704
+ }
5705
+ if (border.dash) {
5706
+ const dash = createElement(doc, "a:prstDash");
5707
+ setAttr(dash, "val", border.dash);
5708
+ ln.appendChild(dash);
5709
+ }
5710
+ insertTcPrChild(tcPr, qname, ln);
5711
+ this.part.markDirty();
5712
+ }
5713
+ /**
5714
+ * Replace the cell's fill with a solid colour (`a:tcPr/a:solidFill`), or clear it.
5715
+ *
5716
+ * `null` removes the `a:solidFill` and lets the cell inherit from the table style again.
5717
+ * That is not the same as {@link noFill}, which writes an explicit `a:noFill` and so
5718
+ * suppresses the inherited shading. Any competing fill choice (`a:gradFill`,
5719
+ * `a:blipFill`, …) is dropped first — `EG_FillProperties` admits one.
5720
+ * @throws {InvalidOptionError} when `color` is not a 6-digit hex string
5721
+ */
5722
+ setFillColor(color) {
5723
+ this.#setFill(color === null ? null : {
5724
+ qname: "a:srgbClr",
5725
+ val: normalizeHex(color)
5726
+ });
5727
+ }
5728
+ /**
5729
+ * Replace the cell's fill with a theme colour token (`a:solidFill/a:schemeClr/@val`), or
5730
+ * clear it. Preferred over {@link setFillColor} when the deck's theme should keep
5731
+ * driving the colour.
5732
+ */
5733
+ setFillSchemeColor(token) {
5734
+ this.#setFill(token === null ? null : {
5735
+ qname: "a:schemeClr",
5736
+ val: token
5737
+ });
5738
+ }
5739
+ /**
5740
+ * Write an explicit `<a:noFill/>` on the cell — a transparent cell that shows the table
5741
+ * background (or the slide) through. Distinct from `setFillColor(null)`, which removes
5742
+ * the fill and lets the table style's shading apply again.
5743
+ */
5744
+ noFill() {
5745
+ const tcPr = this.#getOrAddTcPr();
5746
+ removeChildrenByQName(tcPr, FILL_CHOICES);
5747
+ tcPrChild(tcPr, "a:noFill");
5748
+ this.part.markDirty();
5749
+ }
5750
+ /** Set or clear one `a:tcPr` attribute, vetting it against its schema enum first. */
5751
+ #setTcPrAttr(name, value, valid, code) {
5752
+ if (value === null) {
5753
+ const tcPr = this.#tcPr();
5754
+ if (!tcPr) return;
5755
+ removeAttr(tcPr, name);
5756
+ this.part.markDirty();
5757
+ return;
5758
+ }
5759
+ const checked = checkEnum(value, valid, name, code);
5760
+ setAttr(this.#getOrAddTcPr(), name, checked);
5761
+ this.part.markDirty();
5762
+ }
5763
+ /** Replace (or remove) the cell's solid fill, keeping `EG_FillProperties` single-valued. */
5764
+ #setFill(color) {
5765
+ if (color === null) {
5766
+ const tcPr = this.#tcPr();
5767
+ if (!tcPr || !firstChild(tcPr, "a:solidFill")) return;
5768
+ removeChildrenByQName(tcPr, ["a:solidFill"]);
5769
+ this.part.markDirty();
5770
+ return;
5771
+ }
5772
+ setSolidFill(this.#getOrAddTcPr(), TCPR_AFTER["a:solidFill"] ?? [], color);
5773
+ this.part.markDirty();
5774
+ }
5775
+ /**
5002
5776
  * The cell's solid fill resolved against the table's theme colour context to a
5003
5777
  * literal hex — the table-cell counterpart of
5004
5778
  * {@link import('./shapes.js').AutoShape.resolvedFill}. The cell's own
@@ -5020,11 +5794,25 @@ var TableCell = class {
5020
5794
  */
5021
5795
  get resolvedFill() {
5022
5796
  if (!this.themeColors) return null;
5023
- const tcPr = this.#tcPr();
5024
- if (tcPr && FILL_CHOICES.some((q) => firstChild(tcPr, q))) return resolveSolidFillColor(tcPr, this.themeColors);
5797
+ if (this.hasOwnFill) return resolveSolidFillColor(this.#tcPr(), this.themeColors);
5025
5798
  return this.style ? this.#styleFill() : null;
5026
5799
  }
5027
5800
  /**
5801
+ * Whether the cell carries a fill of its **own** (`a:tcPr` holds an `EG_FillProperties`
5802
+ * child), as opposed to inheriting one from the table style's header/banding rules.
5803
+ *
5804
+ * This is the flag that disambiguates {@link resolvedFill}, which deliberately reports
5805
+ * either — it answers "what colour does this cell render as", and both sources are valid
5806
+ * answers to that. Anything that has to reproduce the cell rather than describe it needs
5807
+ * to know which: baking an inherited banding colour into a copy makes the copy look right
5808
+ * until someone changes its table style, and then nothing moves. `false` for a cell whose
5809
+ * `a:tcPr` is empty or absent, whatever the style graph would render it as.
5810
+ */
5811
+ get hasOwnFill() {
5812
+ const tcPr = this.#tcPr();
5813
+ return !!tcPr && FILL_CHOICES.some((q) => firstChild(tcPr, q));
5814
+ }
5815
+ /**
5028
5816
  * The cell's picture (image) fill (`a:tcPr/a:blipFill`), or `null` when the cell
5029
5817
  * is not image-filled. The cell counterpart of
5030
5818
  * {@link import('./shapes.js').AutoShape.pictureFill}: {@link resolvedFill}
@@ -5045,6 +5833,24 @@ var TableCell = class {
5045
5833
  return resolveTableCellStyleFill(s.style.element_, s.flags, this.rowIndex, this.colIndex, s.rowCount, s.colCount, s.ctx);
5046
5834
  }
5047
5835
  /**
5836
+ * The cell's gradient fill (`a:tcPr/a:gradFill`), or `null` when the cell is not
5837
+ * gradient-filled. The cell twin of {@link Table.gradientFill}, and needed for the same
5838
+ * reason {@link pictureFill} is: {@link resolvedFill} reports `null` for every non-solid
5839
+ * choice, so without this a gradient cell is indistinguishable from an unfilled one.
5840
+ */
5841
+ get gradientFill() {
5842
+ const tcPr = this.#tcPr();
5843
+ return tcPr && this.themeColors ? readGradientFill(tcPr, this.themeColors) : null;
5844
+ }
5845
+ /**
5846
+ * The cell's pattern (hatch) fill (`a:tcPr/a:pattFill`), or `null` when the cell is not
5847
+ * pattern-filled.
5848
+ */
5849
+ get patternFill() {
5850
+ const tcPr = this.#tcPr();
5851
+ return tcPr && this.themeColors ? readPatternFill(tcPr, this.themeColors) : null;
5852
+ }
5853
+ /**
5048
5854
  * The raw `schemeClr` token of the cell's solid fill (`a:tcPr/a:solidFill/a:schemeClr/@val`),
5049
5855
  * e.g. `accent1`/`bg1`, or `null` when the fill is absent or an explicit `srgbClr`.
5050
5856
  * The resolved literal is {@link resolvedFill}; this is the unresolved reference.
@@ -5111,6 +5917,88 @@ var TableCell = class {
5111
5917
  return (tcPr && attr(tcPr, "anchor")) ?? null;
5112
5918
  }
5113
5919
  /**
5920
+ * Whether the cell's whole text **block** is centred horizontally within it
5921
+ * (`a:tcPr/@anchorCtr`). `false` when unset — that is the schema default, and it is
5922
+ * what PowerPoint writes nothing for.
5923
+ *
5924
+ * Distinct from a paragraph's `align`, which decides where each *line* sits inside the
5925
+ * text block; this decides where the block sits inside the cell.
5926
+ */
5927
+ get anchorCtr() {
5928
+ const tcPr = this.#tcPr();
5929
+ return tcPr ? attr(tcPr, "anchorCtr") === "1" || attr(tcPr, "anchorCtr") === "true" : false;
5930
+ }
5931
+ /**
5932
+ * The cell's 3-D bevel (`a:tcPr/a:cell3D`), or `null` when it has none.
5933
+ *
5934
+ * `CT_Cell3D` requires an `a:bevel`, so a present `cell3D` always reports a bevel;
5935
+ * `lightRig` is `null` when the cell leaves the scene lighting to the renderer. Sizes are
5936
+ * reported in **points** (the attributes are EMU), matching how the write option takes them.
5937
+ */
5938
+ get cell3D() {
5939
+ const tcPr = this.#tcPr();
5940
+ const cell3D = tcPr && firstChild(tcPr, "a:cell3D");
5941
+ if (!cell3D) return null;
5942
+ const bevel = firstChild(cell3D, "a:bevel");
5943
+ const rig = firstChild(cell3D, "a:lightRig");
5944
+ const pts = (value) => {
5945
+ const emu = intValue(value);
5946
+ return emu === null ? null : emu / 12700;
5947
+ };
5948
+ return {
5949
+ material: attr(cell3D, "prstMaterial") ?? null,
5950
+ bevel: {
5951
+ preset: bevel ? attr(bevel, "prst") ?? null : null,
5952
+ widthPt: bevel ? pts(attr(bevel, "w")) : null,
5953
+ heightPt: bevel ? pts(attr(bevel, "h")) : null
5954
+ },
5955
+ lightRig: rig ? {
5956
+ rig: attr(rig, "rig") ?? null,
5957
+ dir: attr(rig, "dir") ?? null
5958
+ } : null
5959
+ };
5960
+ }
5961
+ /**
5962
+ * The cell's unique identifier (`a:tc/@id`), or `null` when it has none.
5963
+ *
5964
+ * This is what {@link headerIds} references: a data cell names the header cells that
5965
+ * govern it by their `@id`, which is how a complex table tells a screen reader what a
5966
+ * value means. Unrelated to the shape-level `p:cNvPr/@id` — it is an `xs:string` scoped
5967
+ * to the table, not a slide-wide numeric id.
5968
+ *
5969
+ * **PowerPoint does not write this and strips it on save**, so it will read `null` on
5970
+ * any deck PowerPoint has saved, however it was produced. It is surfaced for decks from
5971
+ * other producers. There is deliberately no write-API counterpart — see
5972
+ * `test/read/fixtures/authoring/probe-table-cell-a11y-and-3d.ps1` for the measurement.
5973
+ */
5974
+ get id() {
5975
+ return attr(this.tc, "id") ?? null;
5976
+ }
5977
+ /**
5978
+ * The `@id`s of the header cells that govern this cell
5979
+ * (`a:tcPr/a:headers/a:header/@val`), in document order. Empty when the cell declares no
5980
+ * header association — which is the common case, since only a complex table needs one.
5981
+ *
5982
+ * Carries the same caveat as {@link id}: PowerPoint strips `a:headers` on save, so this
5983
+ * is empty on any PowerPoint-saved deck. `Table.firstRowHeader` (`a:tblPr/@firstRow`) is
5984
+ * the header marker PowerPoint does keep.
5985
+ */
5986
+ get headerIds() {
5987
+ const tcPr = this.#tcPr();
5988
+ const headers = tcPr && firstChild(tcPr, "a:headers");
5989
+ if (!headers) return [];
5990
+ return getElements(headers, "a:header").map((header) => attr(header, "val")).filter((val) => val !== null && val !== "");
5991
+ }
5992
+ /**
5993
+ * How the cell treats a glyph too wide for its text width (`a:tcPr/@horzOverflow`):
5994
+ * `'clip'` cuts it at the cell edge, `'overflow'` lets it draw past. `null` when unset
5995
+ * (PowerPoint clips). Not a wrap flag — cell text always wraps to the column width.
5996
+ */
5997
+ get horzOverflow() {
5998
+ const tcPr = this.#tcPr();
5999
+ return (tcPr && attr(tcPr, "horzOverflow")) ?? null;
6000
+ }
6001
+ /**
5114
6002
  * The cell's text insets in EMU (`a:tcPr/@marL`/`@marR`/`@marT`/`@marB`), or
5115
6003
  * `null` when the cell sets none. Each side is `null` when only some are set.
5116
6004
  */
@@ -5360,6 +6248,6 @@ function buildShapes(parent, slide) {
5360
6248
  return shapes;
5361
6249
  }
5362
6250
  //#endregion
5363
- export { escapeXmlAttribute as $, resolveMasterColorContext as A, restyleSlide as B, Paragraph as C, readGradientFill as D, readPictureFill as E, flattenShape as F, relsPartNameFor as G, partNameExtension as H, flattenSlide as I, OOXML_NS as J, resolveRelativePartName as K, parseClrMap as L, resolveSlideColorContext as M, resolveSlideThemeParts as N, resolveColorElement as O, applyColorTransforms as P, createElement as Q, parseClrScheme as R, Shape as S, TextFrame as T, partNameToZipPath as U, styleRefFill as V, relativePartName as W, boolValue as X, attr as Y, childElements$2 as Z, ChartAxis as _, isGraphicFrame as a, intValue as at, Picture as b, wrapShapeElement as c, removeAttr as ct, TableCell as d, setAttr as dt, firstChild as et, TableRow as f, Chart as g, ChartExSeries as h, isConnector as i, insertInOrder as it, resolveNotesColorContext as j, resolveLayoutColorContext as k, GraphicFrame as l, removeChildrenByQName as lt, ChartExAxis as m, buildShapes as n, getElements as nt, isGroupShape as o, ownerDocumentOf as ot, ChartEx as p, zipPathToPartName as q, isAutoShape as r, getOrAddChild as rt, isPicture as s, parseXml as st, GroupShape as t, firstChildElement as tt, Table as u, serializeXml as ut, ChartSeries as v, Run as w, AutoShape as x, Connector as y, remapLiteralColors as z };
6251
+ export { createElement as $, resolveLayoutColorContext as A, remapLiteralColors as B, Paragraph as C, readPictureFill as D, readPatternFill as E, applyColorTransforms as F, relativePartName as G, styleRefFill as H, flattenShape as I, zipPathToPartName as J, relsPartNameFor as K, flattenSlide as L, resolveNotesColorContext as M, resolveSlideColorContext as N, readGradientFill as O, resolveSlideThemeParts as P, childElements$2 as Q, parseClrMap as R, Shape as S, TextFrame as T, partNameExtension as U, restyleSlide as V, partNameToZipPath as W, attr as X, OOXML_NS as Y, boolValue as Z, ChartAxis as _, isGraphicFrame as a, insertInOrder as at, Picture as b, wrapShapeElement as c, parseXml as ct, TableCell as d, serializeXml as dt, escapeXmlAttribute as et, TableRow as f, setAttr as ft, Chart as g, ChartExSeries as h, isConnector as i, getOrAddChild as it, resolveMasterColorContext as j, resolveColorElement as k, GraphicFrame as l, removeAttr as lt, ChartExAxis as m, buildShapes as n, firstChildElement as nt, isGroupShape as o, intValue as ot, ChartEx as p, resolveRelativePartName as q, isAutoShape as r, getElements as rt, isPicture as s, ownerDocumentOf as st, GroupShape as t, firstChild as tt, Table as u, removeChildrenByQName as ut, ChartSeries as v, Run as w, AutoShape as x, Connector as y, parseClrScheme as z };
5364
6252
 
5365
- //# sourceMappingURL=shapes-BZU8UTW2.js.map
6253
+ //# sourceMappingURL=shapes-CaOgR0i1.js.map