@shbernal/ts-pptx 1.0.0 → 3.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 (72) hide show
  1. package/README.md +10 -3
  2. package/dist/{autopage-tmfjRzL4.js → autopage-MxulehqQ.js} +3 -4
  3. package/dist/{autopage-tmfjRzL4.js.map → autopage-MxulehqQ.js.map} +1 -1
  4. package/dist/browser.d.ts +14 -6
  5. package/dist/browser.d.ts.map +1 -1
  6. package/dist/browser.js +12 -4
  7. package/dist/browser.js.map +1 -1
  8. package/dist/{diagnostics-DzY9Tyfm.d.ts → diagnostics-BqBTmA3a.d.ts} +95 -23
  9. package/dist/diagnostics-BqBTmA3a.d.ts.map +1 -0
  10. package/dist/{enums-D3aF4QGb.d.ts → enums-oLLeaSW3.d.ts} +2 -1
  11. package/dist/{enums-D3aF4QGb.d.ts.map → enums-oLLeaSW3.d.ts.map} +1 -1
  12. package/dist/{errors-DRLpOpgQ.d.ts → errors-BENA-Xuw.d.ts} +3 -3
  13. package/dist/{errors-DRLpOpgQ.d.ts.map → errors-BENA-Xuw.d.ts.map} +1 -1
  14. package/dist/errors-DGE9zh3U.js.map +1 -1
  15. package/dist/{fit-CbxyHN69.js → fit-DGSwMMNs.js} +0 -0
  16. package/dist/{fit-CbxyHN69.js.map → fit-DGSwMMNs.js.map} +1 -1
  17. package/dist/{html-dom-wVa3TiG7.js → html-dom--J-Xuucf.js} +19 -5
  18. package/dist/html-dom--J-Xuucf.js.map +1 -0
  19. package/dist/html.d.ts +3 -3
  20. package/dist/html.d.ts.map +1 -1
  21. package/dist/html.js +6 -3
  22. package/dist/html.js.map +1 -1
  23. package/dist/index.d.ts +6 -6
  24. package/dist/index.js +4 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/inspect.d.ts +1 -1
  27. package/dist/inspect.js +2 -2
  28. package/dist/inspect.js.map +1 -1
  29. package/dist/math.d.ts +1 -1
  30. package/dist/math.js +2 -1
  31. package/dist/math.js.map +1 -1
  32. package/dist/measure.d.ts +2 -2
  33. package/dist/measure.js +1 -1
  34. package/dist/node.d.ts +14 -6
  35. package/dist/node.d.ts.map +1 -1
  36. package/dist/node.js +11 -3
  37. package/dist/node.js.map +1 -1
  38. package/dist/{pres-5x1bjCzk.d.ts → pres-2WsBGimU.d.ts} +3 -3
  39. package/dist/{pres-5x1bjCzk.d.ts.map → pres-2WsBGimU.d.ts.map} +1 -1
  40. package/dist/{presentation-RGOiun5P.d.ts → presentation-CTVPpiwP.d.ts} +912 -485
  41. package/dist/presentation-CTVPpiwP.d.ts.map +1 -0
  42. package/dist/{presentation-B0JWdTN1.js → presentation-DAcOfzsP.js} +880 -366
  43. package/dist/presentation-DAcOfzsP.js.map +1 -0
  44. package/dist/read.d.ts +4 -4
  45. package/dist/read.d.ts.map +1 -1
  46. package/dist/read.js +2 -2
  47. package/dist/script.d.ts +2 -2
  48. package/dist/script.d.ts.map +1 -1
  49. package/dist/script.js +0 -0
  50. package/dist/script.js.map +1 -1
  51. package/dist/{shapes-BZU8UTW2.js → shapes-BWifcHDV.js} +1520 -996
  52. package/dist/shapes-BWifcHDV.js.map +1 -0
  53. package/dist/{slide-B6mDaM-P.d.ts → slide-YuSCGa45.d.ts} +409 -103
  54. package/dist/slide-YuSCGa45.d.ts.map +1 -0
  55. package/dist/{image-size-C5Z1sKof.js → st-enums-CgadEyPM.js} +235 -4
  56. package/dist/st-enums-CgadEyPM.js.map +1 -0
  57. package/dist/{text-WRUbOy2r.js → text-120VFSDm.js} +1027 -369
  58. package/dist/text-120VFSDm.js.map +1 -0
  59. package/dist/{units-internal-7JcMfYti.js → units-internal-CiFWZWk4.js} +2 -1
  60. package/dist/units-internal-CiFWZWk4.js.map +1 -0
  61. package/dist/zip.d.ts +2 -2
  62. package/dist/zip.js.map +1 -1
  63. package/package.json +38 -36
  64. package/dist/diagnostics-DzY9Tyfm.d.ts.map +0 -1
  65. package/dist/html-dom-wVa3TiG7.js.map +0 -1
  66. package/dist/image-size-C5Z1sKof.js.map +0 -1
  67. package/dist/presentation-B0JWdTN1.js.map +0 -1
  68. package/dist/presentation-RGOiun5P.d.ts.map +0 -1
  69. package/dist/shapes-BZU8UTW2.js.map +0 -1
  70. package/dist/slide-B6mDaM-P.d.ts.map +0 -1
  71. package/dist/text-WRUbOy2r.js.map +0 -1
  72. package/dist/units-internal-7JcMfYti.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { n as warn, r as warnOnce } from "./diagnostics-DYQC7xLV.js";
2
2
  import { n as InvalidOptionError, o as UnsupportedFeatureError, r as MediaError, t as InternalError } from "./errors-DGE9zh3U.js";
3
3
  import { a as FIXED_PCT_PER_PERCENT, d as emuToInches, g as pixelsToEmu, i as EMU_PER_POINT, l as STANDARD_LAYOUTS, m as inchesToEmu, r as EMU_PER_INCH, s as PERCENT_SCALE, t as ANGLE_UNITS_PER_DEGREE, v as ptToHundredths } from "./units-B-JUKwCS.js";
4
- import { $ as connectorPresetFor, C as DEF_PRES_LAYOUT_NAME, D as DEF_TEXT_GLOW, E as DEF_SLIDE_MARGIN_IN, F as PIECHART_COLORS, G as PlaceholderType, I as REGEX_HEX_COLOR, K as SchemeColor, L as SLDNUMFLDID, M as LAYOUT_IDX_SERIES_BASE, N as LETTERS, O as DEF_TEXT_SHADOW, Q as asChartType, R as XML_DECL, S as DEF_PRES_LAYOUT, T as DEF_SHAPE_SHADOW, Z as VALID_SHAPE_PRESETS, _ as DEF_CELL_BORDER, a as lineWidthToEmu, b as DEF_CHART_GRIDLINE, c as resolveTableColWidthsEmu, d as AXIS_ID_CATEGORY_PRIMARY, et as isChartExType, f as AXIS_ID_CATEGORY_SECONDARY, g as BARCHART_COLORS, h as AXIS_ID_VALUE_SECONDARY, i as inch2Emu, k as IMG_BROKEN, l as transparencyToAlpha, m as AXIS_ID_VALUE_PRIMARY, n as convertRotationDegrees, o as marginToEmu, p as AXIS_ID_SERIES_PRIMARY, r as getSmartParseNumber, s as opacityToAlpha, t as convertArcAngle, u as valToPts, v as DEF_CELL_MARGIN_IN, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./units-internal-7JcMfYti.js";
5
- import { a as decodeBase64ToBytes, n as getImageSizeFromBase64, o as svgMarkupToDataUri, t as fitSrcRectPercents } from "./image-size-C5Z1sKof.js";
6
- import { t as getSlidesForTableRows } from "./autopage-tmfjRzL4.js";
7
- import { _ as parseFontMetrics, m as FontMetricsRegistry, n as measureText, r as computeTableLayout, t as applyMeasuredFit } from "./fit-CbxyHN69.js";
4
+ import { $ as connectorPresetFor, C as DEF_PRES_LAYOUT_NAME, D as DEF_TEXT_GLOW, E as DEF_SLIDE_MARGIN_IN, F as PIECHART_COLORS, G as PlaceholderType, I as REGEX_HEX_COLOR, K as SchemeColor, L as SLDNUMFLDID, M as LAYOUT_IDX_SERIES_BASE, N as LETTERS, O as DEF_TEXT_SHADOW, Q as asChartType, R as XML_DECL, S as DEF_PRES_LAYOUT, T as DEF_SHAPE_SHADOW, Z as VALID_SHAPE_PRESETS, _ as DEF_CELL_BORDER, a as lineWidthToEmu, b as DEF_CHART_GRIDLINE, c as resolveTableColWidthsEmu, d as AXIS_ID_CATEGORY_PRIMARY, et as isChartExType, f as AXIS_ID_CATEGORY_SECONDARY, g as BARCHART_COLORS, h as AXIS_ID_VALUE_SECONDARY, i as inch2Emu, k as IMG_BROKEN, l as transparencyToAlpha, m as AXIS_ID_VALUE_PRIMARY, n as convertRotationDegrees, o as marginToEmu, p as AXIS_ID_SERIES_PRIMARY, r as getSmartParseNumber, s as opacityToAlpha, t as convertArcAngle, u as valToPts, v as DEF_CELL_MARGIN_IN, x as DEF_FONT_COLOR, y as DEF_CHART_BORDER } from "./units-internal-CiFWZWk4.js";
5
+ import { E as svgMarkupToDataUri, S as getImageSizeFromBase64, T as decodeBase64ToBytes, a as PRESET_MATERIALS, g as PACKAGE_REL_NS, h as OFFICE_REL, i as PRESET_LINE_DASHES, n as LIGHT_RIGS, r as LIGHT_RIG_DIRECTIONS, s as TEXT_HORZ_OVERFLOW, t as BEVEL_PRESETS, u as checkEnumOrWarn, x as fitSrcRectPercents } from "./st-enums-CgadEyPM.js";
6
+ import { t as getSlidesForTableRows } from "./autopage-MxulehqQ.js";
7
+ import { _ as parseFontMetrics, m as FontMetricsRegistry, n as measureText, r as computeTableLayout, t as applyMeasuredFit } from "./fit-DGSwMMNs.js";
8
8
  import { a as flattenEmbeddedFaces, i as FONT_REL_TYPE, n as FONT_DATA_CONTENT_TYPE, o as serializeEmbeddedFontLst, r as FONT_DATA_EXTENSION, t as EMBEDDED_FONT_SLOTS } from "./embedded-fonts-MaynAHa9.js";
9
9
  import { ZipWriter } from "./zip.js";
10
10
  //#region src/media/content-type.ts
@@ -1326,7 +1326,7 @@ function addImageDefinition(target, opt) {
1326
1326
  let defWidth = intWidth;
1327
1327
  let defHeight = intHeight;
1328
1328
  let szAuto;
1329
- if ((!intWidth || !intHeight) && strImgExtn !== "svg") {
1329
+ if ((!intWidth || !intHeight) && !(strImgExtn === "svg" && !intWidth && !intHeight)) {
1330
1330
  const natural = strImageData ? getImageSizeFromBase64(strImageData) : null;
1331
1331
  if (natural) {
1332
1332
  if (!intWidth && !intHeight) {
@@ -1829,7 +1829,10 @@ function groupObjectsDefinition(target, objectNames, opts) {
1829
1829
  const key = encodeXmlAttrValue(name);
1830
1830
  const matches = target._slideObjects.filter((obj) => obj.options?.objectName === key);
1831
1831
  const [obj, ambiguous] = matches;
1832
- if (!obj) throw new InvalidOptionError("group/unresolved-object-name", `groupObjects(): cannot group "${name}" — ${findNameInGroups(target._slideObjects, key) ? "it is already inside a group (an object can only belong to one group)" : "no top-level object on this slide has that objectName"}.`);
1832
+ if (!obj) {
1833
+ const hint = findNameInGroups(target._slideObjects, key) ? "it is already inside a group (an object can only belong to one group)" : "no top-level object on this slide has that objectName";
1834
+ throw new InvalidOptionError("group/unresolved-object-name", `groupObjects(): cannot group "${name}" — ${hint}.`);
1835
+ }
1833
1836
  if (ambiguous) throw new InvalidOptionError("group/ambiguous-object-name", `groupObjects(): objectName "${name}" is ambiguous — ${matches.length} objects on this slide share it. Give them unique objectNames.`);
1834
1837
  if (!GROUPABLE_TYPES.includes(obj._type) || obj.options?.placeholder) {
1835
1838
  const kind = obj.options?.placeholder ? "placeholder" : obj._type;
@@ -1958,23 +1961,6 @@ function addMediaDefinition(target, opt) {
1958
1961
  target._slideObjects.push(slideData);
1959
1962
  }
1960
1963
  //#endregion
1961
- //#region src/gen/define/notes.ts
1962
- /**
1963
- * Adds Notes to a slide.
1964
- * @param {PresSlideInternal} `target` slide object
1965
- * @param {string | NotesProps | NotesProps[]} `notes` plain text, or rich runs (inline formatting / hyperlinks)
1966
- */
1967
- function addNotesDefinition(target, notes) {
1968
- const runs = typeof notes === "string" ? [{ text: notes }] : (Array.isArray(notes) ? notes : [notes]).map((run) => ({
1969
- text: run.text,
1970
- options: run.options
1971
- }));
1972
- target._slideObjects.push({
1973
- _type: "notes",
1974
- text: runs
1975
- });
1976
- }
1977
- //#endregion
1978
1964
  //#region src/gen/define/preview-image.ts
1979
1965
  /**
1980
1966
  * Register a preview/cover image as a slide media rel (deduped like `addImage`) and return its rId.
@@ -2007,11 +1993,140 @@ function registerPreviewImage(target, cover) {
2007
1993
  return rId;
2008
1994
  }
2009
1995
  //#endregion
1996
+ //#region src/gen/define/model3d.ts
1997
+ /** OPC content type for a glTF binary, as PowerPoint spells it in `[Content_Types].xml`. */
1998
+ const GLB_CONTENT_TYPE = "model/gltf.binary";
1999
+ /** `.rels` `Type` URI for the embedded 3D model part. Note `2017/06` — not the namespace's `2017`. */
2000
+ const MODEL3D_REL_TYPE = "http://schemas.microsoft.com/office/2017/06/relationships/model3d";
2001
+ /** Fixed-point denominator for `am3d` linear values: `am3d:up@dy="36000000"` is the unit vector. */
2002
+ const AM3D_UNIT = 36e6;
2003
+ /** Denominator of every `am3d` rational (`meterPerModelUnit`, `scale`, `illuminance`, `intensity`). */
2004
+ const AM3D_RATIO_DEN = 1e6;
2005
+ /** `am3d:perspective@fov` is in 60000ths of a degree, like every other DrawingML angle. */
2006
+ const FOV_PER_DEGREE = 6e4;
2007
+ /**
2008
+ * The camera PowerPoint wrote for the 2×2×2 cube fixture, in caller units (metres / degrees).
2009
+ * `z` is `sqrt(3)/2 / sin(22.5°)` — the bounding sphere of a 1-metre cube framed at a 45° fov.
2010
+ */
2011
+ const DEFAULT_CAMERA = {
2012
+ pos: {
2013
+ x: 0,
2014
+ y: 0,
2015
+ z: 2.2630334
2016
+ },
2017
+ lookAt: {
2018
+ x: 0,
2019
+ y: 0,
2020
+ z: 0
2021
+ },
2022
+ up: {
2023
+ x: 0,
2024
+ y: 1,
2025
+ z: 0
2026
+ },
2027
+ fov: 45
2028
+ };
2029
+ /** `am3d:meterPerModelUnit` for the cube fixture: 1 / (its 2-unit largest extent). */
2030
+ const DEFAULT_METER_PER_MODEL_UNIT = .5;
2031
+ /** Reject a non-finite camera component rather than letting `NaN` reach the XML as `"NaN"`. */
2032
+ function toAm3dUnits(point, fallback, label) {
2033
+ const src = point ?? fallback;
2034
+ for (const axis of [
2035
+ "x",
2036
+ "y",
2037
+ "z"
2038
+ ]) if (!Number.isFinite(src[axis])) throw new InvalidOptionError("model3d/invalid-camera", `addModel3d(): camera.${label}.${axis} must be a finite number, got ${String(src[axis])}.`);
2039
+ return {
2040
+ x: Math.round(src.x * AM3D_UNIT),
2041
+ y: Math.round(src.y * AM3D_UNIT),
2042
+ z: Math.round(src.z * AM3D_UNIT)
2043
+ };
2044
+ }
2045
+ /** Resolve `camera` + `meterPerModelUnit` into the wire units {@link Model3dInternal} carries. */
2046
+ function resolveCamera(camera, meterPerModelUnit) {
2047
+ const fovDeg = camera?.fov ?? DEFAULT_CAMERA.fov;
2048
+ if (!Number.isFinite(fovDeg) || fovDeg <= 0 || fovDeg >= 180) throw new InvalidOptionError("model3d/invalid-fov", `addModel3d(): camera.fov must be greater than 0 and less than 180 degrees, got ${String(fovDeg)}.`);
2049
+ const scale = meterPerModelUnit ?? DEFAULT_METER_PER_MODEL_UNIT;
2050
+ if (!Number.isFinite(scale) || scale <= 0) throw new InvalidOptionError("model3d/invalid-scale", `addModel3d(): meterPerModelUnit must be a finite number greater than 0, got ${String(scale)}.`);
2051
+ const up = toAm3dUnits(camera?.up, DEFAULT_CAMERA.up, "up");
2052
+ return {
2053
+ pos: toAm3dUnits(camera?.pos, DEFAULT_CAMERA.pos, "pos"),
2054
+ lookAt: toAm3dUnits(camera?.lookAt, DEFAULT_CAMERA.lookAt, "lookAt"),
2055
+ up: {
2056
+ dx: up.x,
2057
+ dy: up.y,
2058
+ dz: up.z
2059
+ },
2060
+ fov: Math.round(fovDeg * FOV_PER_DEGREE),
2061
+ meterPerModelUnitN: Math.round(scale * AM3D_RATIO_DEN)
2062
+ };
2063
+ }
2064
+ /**
2065
+ * Adds an embedded 3D model to a slide definition.
2066
+ * @param {PresSlideInternal} target - slide the model will be added to
2067
+ * @param {Model3dProps} opt - 3D model options
2068
+ */
2069
+ function addModel3dDefinition(target, opt) {
2070
+ const strData = opt.data || "";
2071
+ const strPath = opt.path || "";
2072
+ if (!strPath && !strData) throw new InvalidOptionError("model3d/missing-source", "addModel3d(): either `data` or `path` are required!");
2073
+ const camera = resolveCamera(opt.camera, opt.meterPerModelUnit);
2074
+ const nameIdx = nextObjectNameIdx(target, "model3d");
2075
+ const objectName = opt.objectName ? encodeXmlAttrValue(validateObjectName(opt.objectName, "model3d")) : `3D Model ${nameIdx + 1}`;
2076
+ const modelRid = getNewRelId(target);
2077
+ const mediaSlideKey = target._slideNum == null ? "sm" : target._slideNum >= 1e3 ? `sl-${target._slideNum}` : target._slideNum;
2078
+ target._relsMedia.push({
2079
+ path: strPath || "preencoded.glb",
2080
+ type: GLB_CONTENT_TYPE,
2081
+ extn: "glb",
2082
+ data: strData,
2083
+ rId: modelRid,
2084
+ model3dRelType: MODEL3D_REL_TYPE,
2085
+ Target: `../media/model3d-${mediaSlideKey}-${target._relsMedia.length + 1}.glb`
2086
+ });
2087
+ if (!opt.preview?.path && !opt.preview?.data) warn("model3d/preview-missing", "addModel3d(): no `preview` image supplied, so a gray placeholder is embedded. PowerPoint 2019+ draws the live model over it, but thumbnails, PDF export and older viewers show the placeholder.");
2088
+ const previewRid = registerPreviewImage(target, opt.preview);
2089
+ const slideData = {
2090
+ _type: "model3d",
2091
+ options: {
2092
+ x: opt.x ?? 0,
2093
+ y: opt.y ?? 0,
2094
+ w: opt.w ?? 4,
2095
+ h: opt.h ?? 3,
2096
+ objectName,
2097
+ ...opt.altText ? { altText: opt.altText } : {}
2098
+ },
2099
+ model3d: {
2100
+ modelRid,
2101
+ previewRid,
2102
+ ...camera
2103
+ }
2104
+ };
2105
+ target._slideObjects.push(slideData);
2106
+ }
2107
+ //#endregion
2108
+ //#region src/gen/define/notes.ts
2109
+ /**
2110
+ * Adds Notes to a slide.
2111
+ * @param {PresSlideInternal} `target` slide object
2112
+ * @param {string | NotesProps | NotesProps[]} `notes` plain text, or rich runs (inline formatting / hyperlinks)
2113
+ */
2114
+ function addNotesDefinition(target, notes) {
2115
+ const runs = typeof notes === "string" ? [{ text: notes }] : (Array.isArray(notes) ? notes : [notes]).map((run) => ({
2116
+ text: run.text,
2117
+ options: run.options
2118
+ }));
2119
+ target._slideObjects.push({
2120
+ _type: "notes",
2121
+ text: runs
2122
+ });
2123
+ }
2124
+ //#endregion
2010
2125
  //#region src/gen/define/ole.ts
2011
2126
  /** Rel type for an embedded OPC package (an Office file, itself a zip). */
2012
- const PACKAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package";
2127
+ const PACKAGE_REL = OFFICE_REL + "package";
2013
2128
  /** Rel type for a generic OLE-server blob — a compound-file `.bin` (ECMA-376 Part 1 §15.2.10). */
2014
- const OLE_OBJECT_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject";
2129
+ const OLE_OBJECT_REL = OFFICE_REL + "oleObject";
2015
2130
  /**
2016
2131
  * Embeddable payload formats, keyed by file extension. The six Office extensions are OPC packages
2017
2132
  * and keep their own extension in `ppt/embeddings/`; everything else is embedded as a generic OLE
@@ -2148,6 +2263,44 @@ function normalizeBorderTuple(border) {
2148
2263
  border
2149
2264
  ];
2150
2265
  }
2266
+ /** Fill a border's defaulted keys, preserving anything else the caller set (`cap`, `dashType`, …). */
2267
+ function withBorderDefaults(side) {
2268
+ return {
2269
+ ...side,
2270
+ type: side.type || DEF_CELL_BORDER.type,
2271
+ color: side.color || DEF_CELL_BORDER.color,
2272
+ width: typeof side.width === "number" ? side.width : DEF_CELL_BORDER.width
2273
+ };
2274
+ }
2275
+ /**
2276
+ * Resolve `TableProps.outerBorder` into the `[top, right, bottom, left]` tuple the emitter
2277
+ * applies to the table's edge cells, with `undefined` for a side the caller left out.
2278
+ *
2279
+ * A sparse side is *not* the same as `{ type: 'none' }`: it means "leave this edge to
2280
+ * whatever `border` (or the cell's own `options.border`) already drew", whereas `'none'` is
2281
+ * an explicit instruction to erase the rule there. Keeping the hole is what lets
2282
+ * `outerBorder: [rule, undefined, rule, undefined]` add rules above and below a table
2283
+ * without also clearing its left and right edges.
2284
+ */
2285
+ function normalizeOuterBorder(outer) {
2286
+ if (!outer || typeof outer !== "object") return void 0;
2287
+ const sides = Array.isArray(outer) ? outer : [
2288
+ outer,
2289
+ outer,
2290
+ outer,
2291
+ outer
2292
+ ];
2293
+ const resolved = [
2294
+ 0,
2295
+ 1,
2296
+ 2,
2297
+ 3
2298
+ ].map((idx) => {
2299
+ const side = sides[idx];
2300
+ return side && typeof side === "object" ? withBorderDefaults(side) : void 0;
2301
+ });
2302
+ return resolved.some((side) => side !== void 0) ? resolved : void 0;
2303
+ }
2151
2304
  /**
2152
2305
  * Adds a table object to a slide definition.
2153
2306
  * @param {PresSlideInternal} target - slide object that the table should be added to
@@ -2197,6 +2350,9 @@ function applyTableHeaderColumnSugar(tableRows, opt) {
2197
2350
  /**
2198
2351
  * Transform loosely-typed table rows (strings / numbers / TableCell) into a grid of
2199
2352
  * well-formed TableCell objects with fully-resolved 4-side cell borders.
2353
+ *
2354
+ * @param srcRows - the rows as authored, after the `headerRow`/`columns` sugar
2355
+ * @param opt - the table's options
2200
2356
  */
2201
2357
  function normalizeTableRows(srcRows, opt) {
2202
2358
  const arrRows = [];
@@ -2238,12 +2394,7 @@ function normalizeTableRows(srcRows, opt) {
2238
2394
  3
2239
2395
  ].forEach((idx) => {
2240
2396
  const side = cellBorderTuple[idx];
2241
- cellBorderTuple[idx] = {
2242
- ...side,
2243
- type: side.type || DEF_CELL_BORDER.type,
2244
- color: side.color || DEF_CELL_BORDER.color,
2245
- width: typeof side.width === "number" ? side.width : DEF_CELL_BORDER.width
2246
- };
2397
+ cellBorderTuple[idx] = withBorderDefaults(side);
2247
2398
  });
2248
2399
  newCellOptions.border = cellBorderTuple;
2249
2400
  newRow.push(newCell);
@@ -2259,10 +2410,11 @@ function normalizeTableRows(srcRows, opt) {
2259
2410
  * the fill. Tables were the last major object kind not wired into `registerImageFillMedia`
2260
2411
  * (shapes do it at `define/shape.ts`, text boxes at `define/text.ts`).
2261
2412
  *
2262
- * Two fill sources are walked, which between them cover all four ways a fill reaches a cell:
2413
+ * Three fill sources are walked, which between them cover every way a picture reaches a table:
2263
2414
  * the resolved cells (per-cell `options.fill`, plus `headerRow` and `columns[i]`, both baked
2264
- * onto cells by the sugar step above), and the table-level `fill`, which is *not* baked —
2265
- * `gen/slide/objects/table.ts` copies it onto each cell at emit time.
2415
+ * onto cells by the sugar step above), the table-level `fill`, which is *not* baked —
2416
+ * `gen/slide/objects/table.ts` copies it onto each cell at emit time — and `tableFill`, which
2417
+ * lands on `a:tblPr` itself and so needs its own relationship for the same reason.
2266
2418
  *
2267
2419
  * Keyed on fill **object identity**, not on the image source. The sugar spreads its options
2268
2420
  * shallowly, so one `headerRow` fill object is shared by every cell it styles; registering
@@ -2285,6 +2437,7 @@ function registerTableImageFills(target, rows, opt) {
2285
2437
  registerImageFillMedia(target, fill);
2286
2438
  };
2287
2439
  register(opt.fill);
2440
+ register(opt.tableFill);
2288
2441
  rows.forEach((row) => {
2289
2442
  row.forEach((cell) => {
2290
2443
  register(cell.options?.fill);
@@ -2319,7 +2472,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
2319
2472
  ];
2320
2473
  if (!Array.isArray(opt.margin) || opt.margin.length !== 4 || opt.margin.some((v) => typeof v !== "number" || !Number.isFinite(v))) opt.margin = DEF_CELL_MARGIN_IN;
2321
2474
  if (!JSON.stringify({ arrRows }).includes("hyperlink")) {
2322
- if (!opt.color) opt.color = opt.color || "000000";
2475
+ if (!opt.color) opt.color = DEF_FONT_COLOR;
2323
2476
  }
2324
2477
  if (typeof opt.border === "string") {
2325
2478
  warn("table/invalid-border", "addTable `border` option must be an object. Ex: `{border: {type:'none'}}`");
@@ -2332,14 +2485,15 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
2332
2485
  2,
2333
2486
  3
2334
2487
  ].forEach((idx) => {
2335
- border[idx] = border[idx] ? {
2336
- ...border[idx],
2337
- type: border[idx].type || DEF_CELL_BORDER.type,
2338
- color: border[idx].color || DEF_CELL_BORDER.color,
2339
- width: border[idx].width || DEF_CELL_BORDER.width
2340
- } : { type: "none" };
2488
+ const side = border[idx];
2489
+ border[idx] = side ? withBorderDefaults(side) : { type: "none" };
2341
2490
  });
2342
2491
  }
2492
+ if (typeof opt.outerBorder === "string") {
2493
+ warn("table/invalid-outer-border", "addTable `outerBorder` option must be an object. Ex: `{outerBorder: {type:'solid'}}`");
2494
+ opt.outerBorder = void 0;
2495
+ }
2496
+ opt.outerBorder = normalizeOuterBorder(opt.outerBorder);
2343
2497
  opt.autoPage = typeof opt.autoPage === "boolean" ? opt.autoPage : false;
2344
2498
  opt.autoPagePlaceholder = typeof opt.autoPagePlaceholder === "boolean" ? opt.autoPagePlaceholder : false;
2345
2499
  opt.autoPageRepeatHeader = typeof opt.autoPageRepeatHeader === "boolean" ? opt.autoPageRepeatHeader : false;
@@ -2381,7 +2535,7 @@ function addTableDefinition(target, tableRows, options, slideLayout, presLayout,
2381
2535
  }
2382
2536
  } else if (opt.w) {} else opt.w = Math.floor((presLayout._sizeW || presLayout.width) / EMU_PER_INCH - arrTableMargin[1] - arrTableMargin[3]);
2383
2537
  if (opt.fitColumns === "shrink") {
2384
- const availWin = (presLayout._sizeW || presLayout.width) / EMU_PER_INCH - getSmartParseNumber(opt.x, "X", presLayout) / EMU_PER_INCH - arrTableMargin[3];
2538
+ const availWin = (presLayout._sizeW || presLayout.width) / EMU_PER_INCH - getSmartParseNumber(opt.x, "X", presLayout) / EMU_PER_INCH - arrTableMargin[1];
2385
2539
  if (availWin > 0) {
2386
2540
  if (Array.isArray(opt.colW)) {
2387
2541
  const sumIn = opt.colW.reduce((p, n) => p + (Number.isFinite(n) ? n : 0), 0);
@@ -2763,6 +2917,24 @@ var SlideBuilder = class {
2763
2917
  return this;
2764
2918
  }
2765
2919
  /**
2920
+ * Add an embedded 3D model (PowerPoint's Insert ▸ 3D Models) to the Slide.
2921
+ *
2922
+ * The `.glb` bytes ship inside the `.pptx`, so PowerPoint 2019+ renders the model live and lets
2923
+ * the viewer orbit it. Everything else — older PowerPoint, thumbnails, PDF export — draws the
2924
+ * `preview` picture instead; without one a neutral gray placeholder is embedded.
2925
+ *
2926
+ * Most models also need `meterPerModelUnit`: ts-pptx does not parse the `.glb`, so it cannot
2927
+ * measure the bounding box PowerPoint would have scaled the scene by. Set it to
2928
+ * `1 / <largest bounding-box dimension in model units>`.
2929
+ * @param {Model3dProps} options - 3D model options
2930
+ * @return {SlideBuilder} this Slide
2931
+ * @example slide.addModel3d({ path: 'engine.glb', preview: { path: 'engine.png' }, x: 1, y: 1, w: 6, h: 4 })
2932
+ */
2933
+ addModel3d(options) {
2934
+ addModel3dDefinition(this, options);
2935
+ return this;
2936
+ }
2937
+ /**
2766
2938
  * Add speaker notes to Slide
2767
2939
  * @param {string | NotesProps | NotesProps[]} notes - notes text, or rich runs with inline
2768
2940
  * formatting / hyperlinks. A plain string is the single-run case; pass run objects to add
@@ -3045,23 +3217,75 @@ function genXmlColorSelection(props) {
3045
3217
  case "image":
3046
3218
  outText += genXmlImageFill(typeof props === "string" ? void 0 : props);
3047
3219
  break;
3048
- default:
3049
- outText += "";
3050
- break;
3220
+ default: outText += "";
3051
3221
  }
3052
3222
  }
3053
3223
  return outText;
3054
3224
  }
3055
3225
  //#endregion
3056
3226
  //#region src/gen/drawingml/line.ts
3227
+ /** Every key `BorderProps` defines. Keep in step with the interface in `types/style.ts`. */
3228
+ const BORDER_KEYS = [
3229
+ "type",
3230
+ "dashType",
3231
+ "color",
3232
+ "width",
3233
+ "transparency",
3234
+ "cap"
3235
+ ];
3236
+ /**
3237
+ * Resolve the `a:prstDash/@val` a border should emit.
3238
+ *
3239
+ * `BorderProps.type` is a three-way switch, so on its own it can only say "dashed" and
3240
+ * every dashed border collapses onto `sysDash`. `dashType` names the preset directly and
3241
+ * therefore wins when both are given; `type: 'none'` never reaches here, being decided by
3242
+ * the caller before any dash is chosen.
3243
+ *
3244
+ * A value outside `ST_PresetLineDashVal` would make the part schema-invalid, which
3245
+ * PowerPoint reports as a corrupt file rather than a mis-set option — so an unrecognized
3246
+ * one is reported and dropped back to what `type` implies.
3247
+ * @param {BorderProps} border - the caller's border properties
3248
+ * @returns {string} a value legal for `a:prstDash/@val`
3249
+ */
3250
+ function resolveBorderDash(border) {
3251
+ const fromType = border.type === "dash" ? "sysDash" : "solid";
3252
+ return checkEnumOrWarn(border.dashType, PRESET_LINE_DASHES, "border/invalid-dash-type", "border: dashType") ?? fromType;
3253
+ }
3254
+ /**
3255
+ * Report an authored key that is not part of `BorderProps`, which the library would
3256
+ * otherwise drop without a sound.
3257
+ *
3258
+ * TypeScript's excess-property check already rejects a stray key on a border written
3259
+ * *inline* at the call site. It deliberately does not fire when the border is built as
3260
+ * a variable first (`const b = {...}; addTable(rows, { border: b })`) — a variable may
3261
+ * legitimately be a supertype. That exemption covers precisely the reuse pattern a
3262
+ * shared grid style encourages, so the mistake reaches here typed as valid and the
3263
+ * wrong-named value is discarded during generation. A `.pptx` gives no other signal:
3264
+ * nothing throws, the deck opens, and the border simply renders at its default weight.
3265
+ * @param border - the caller's border properties
3266
+ */
3267
+ function warnUnknownBorderKeys(border) {
3268
+ for (const key in border) {
3269
+ if (!Object.hasOwn(border, key) || BORDER_KEYS.includes(key)) continue;
3270
+ warnOnce("border/unknown-key", `border: unknown option \`${key}\` is ignored — valid keys are ${BORDER_KEYS.join(", ")}. Line thickness is \`width\`, in points.`, {
3271
+ received: key,
3272
+ valid: BORDER_KEYS
3273
+ });
3274
+ }
3275
+ }
3057
3276
  /**
3058
3277
  * Resolve a border's line width in points, falling back to `defaultPt` when `width`
3059
3278
  * is not a usable number.
3279
+ *
3280
+ * Every `BorderProps` the library reads — table cell borders, table-style regions, and
3281
+ * every chart border — resolves its width through here, so this doubles as the one
3282
+ * place that can vet a border's shape for all of them ({@link warnUnknownBorderKeys}).
3060
3283
  * @param {BorderProps} border - border properties (may carry `width`)
3061
3284
  * @param {number} defaultPt - width to use when `width` is not a finite number
3062
3285
  * @returns {number} resolved width in points
3063
3286
  */
3064
3287
  function resolveBorderWidth(border, defaultPt) {
3288
+ warnUnknownBorderKeys(border);
3065
3289
  const val = border.width;
3066
3290
  return typeof val === "number" && !isNaN(val) ? val : defaultPt;
3067
3291
  }
@@ -3467,7 +3691,34 @@ function makeSeriesDataPointsXml(chartType, obj, opts, varyColors) {
3467
3691
  * gridlines, optional axis title -- but PowerPoint requires the child elements in a
3468
3692
  * fixed order that differs per axis, so they stay as three separate builders rather
3469
3693
  * than one parameterized one. Called by {@link ./chart-xml}'s axis region.
3470
- */
3694
+ *
3695
+ * Sub-blocks are shared where they can be shared *exactly*: {@link axisLineSpPr} covers
3696
+ * `<c:spPr>`, which differs between the three only by indent width. `<c:txPr>` is
3697
+ * deliberately left duplicated — its inherited indentation is irregular *within* each copy
3698
+ * as well as between them (the category axis closes `</a:defRPr>` at three spaces, the value
3699
+ * axis at six), so parameterising it would take about as many indent arguments as it has
3700
+ * lines and would hide the quirks rather than share the structure.
3701
+ */
3702
+ /**
3703
+ * The `<c:spPr>` axis-line block, identical in shape on all three axes.
3704
+ *
3705
+ * The only thing that differed between the three copies was how far each was indented — two
3706
+ * spaces on the category and series axes, one on the value axis — and that irregularity is
3707
+ * inherited, not meaningful. It is reproduced here rather than normalised because inter-element
3708
+ * whitespace being inert does not make changing it free: byte-identity is the gate that proves
3709
+ * an emitter refactor changed nothing, and it cannot tell a "harmless" whitespace edit from a
3710
+ * real one. `indent` keeps the quirk visible at the call site.
3711
+ * @param {number} indent - leading spaces on the `<c:spPr>` line; children step out by 2 and 4
3712
+ * @param {number} widthEmu - line width for `a:ln@w`
3713
+ * @param {boolean | undefined} show - false emits `<a:noFill/>` instead of a colour
3714
+ * @param {string | undefined} color - line colour, defaulting to {@link DEF_GRIDLINE_COLOR}
3715
+ * @param {string} dash - `a:prstDash@val`
3716
+ * @return {string} XML `<c:spPr>`
3717
+ */
3718
+ function axisLineSpPr(indent, widthEmu, show, color, dash) {
3719
+ const at = (extra) => " ".repeat(indent + extra);
3720
+ return `${at(0)}<c:spPr>${at(2)}<a:ln w="${widthEmu}" cap="flat">` + (!show ? "<a:noFill/>" : genXmlColorSelection(color || DEF_GRIDLINE_COLOR)) + `${at(4)}<a:prstDash val="${dash}"/>${at(4)}<a:round/>${at(2)}</a:ln>${at(0)}</c:spPr>`;
3721
+ }
3471
3722
  function makeCatAxis(opts, axisId, valAxisId) {
3472
3723
  let strXml = "";
3473
3724
  const usesValueAxisForCategories = opts._type === "scatter" || opts._type === "bubble" || opts._type === "bubble3D";
@@ -3476,7 +3727,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
3476
3727
  else strXml += "<c:" + (opts.catLabelFormatCode ? "dateAx" : "catAx") + ">";
3477
3728
  strXml += " <c:axId val=\"" + axisId + "\"/>";
3478
3729
  strXml += " <c:scaling>";
3479
- strXml += "<c:orientation val=\"" + (opts.catAxisOrientation || (opts.barDir === "col" ? "minMax" : "minMax")) + "\"/>";
3730
+ strXml += "<c:orientation val=\"" + (opts.catAxisOrientation || "minMax") + "\"/>";
3480
3731
  if (opts.catAxisMaxVal || opts.catAxisMaxVal === 0) strXml += `<c:max val="${opts.catAxisMaxVal}"/>`;
3481
3732
  if (opts.catAxisMinVal || opts.catAxisMinVal === 0) strXml += `<c:min val="${opts.catAxisMinVal}"/>`;
3482
3733
  strXml += "</c:scaling>";
@@ -3509,13 +3760,7 @@ function makeCatAxis(opts, axisId, valAxisId) {
3509
3760
  strXml += " <c:minorTickMark val=\"" + (opts.catAxisMinorTickMark || "none") + "\"/>";
3510
3761
  strXml += " <c:tickLblPos val=\"" + (opts.catAxisLabelPos || (opts.barDir === "col" ? "low" : "nextTo")) + "\"/>";
3511
3762
  }
3512
- strXml += " <c:spPr>";
3513
- strXml += ` <a:ln w="${opts.catAxisLineSize ? valToPts(opts.catAxisLineSize) : EMU_PER_POINT}" cap="flat">`;
3514
- strXml += !opts.catAxisLineShow ? "<a:noFill/>" : genXmlColorSelection(opts.catAxisLineColor || DEF_GRIDLINE_COLOR);
3515
- strXml += " <a:prstDash val=\"" + (opts.catAxisLineStyle || "solid") + "\"/>";
3516
- strXml += " <a:round/>";
3517
- strXml += " </a:ln>";
3518
- strXml += " </c:spPr>";
3763
+ strXml += axisLineSpPr(2, opts.catAxisLineSize ? valToPts(opts.catAxisLineSize) : EMU_PER_POINT, opts.catAxisLineShow, opts.catAxisLineColor, opts.catAxisLineStyle || "solid");
3519
3764
  strXml += " <c:txPr>";
3520
3765
  if (opts.catAxisLabelRotate) strXml += `<a:bodyPr rot="${convertRotationDegrees(opts.catAxisLabelRotate)}"/>`;
3521
3766
  else strXml += "<a:bodyPr/>";
@@ -3579,7 +3824,7 @@ function makeValAxis(opts, valAxisId) {
3579
3824
  strXml += " <c:axId val=\"" + valAxisId + "\"/>";
3580
3825
  strXml += " <c:scaling>";
3581
3826
  if (opts.valAxisLogScaleBase) strXml += `<c:logBase val="${opts.valAxisLogScaleBase}"/>`;
3582
- strXml += "<c:orientation val=\"" + (opts.valAxisOrientation || (opts.barDir === "col" ? "minMax" : "minMax")) + "\"/>";
3827
+ strXml += "<c:orientation val=\"" + (opts.valAxisOrientation || "minMax") + "\"/>";
3583
3828
  if (opts.valAxisMaxVal || opts.valAxisMaxVal === 0) strXml += `<c:max val="${opts.valAxisMaxVal}"/>`;
3584
3829
  if (opts.valAxisMinVal || opts.valAxisMinVal === 0) strXml += `<c:min val="${opts.valAxisMinVal}"/>`;
3585
3830
  strXml += " </c:scaling>";
@@ -3606,13 +3851,7 @@ function makeValAxis(opts, valAxisId) {
3606
3851
  strXml += " <c:minorTickMark val=\"" + (opts.valAxisMinorTickMark || "none") + "\"/>";
3607
3852
  strXml += " <c:tickLblPos val=\"" + (opts.valAxisLabelPos || (opts.barDir === "col" ? "nextTo" : "low")) + "\"/>";
3608
3853
  }
3609
- strXml += " <c:spPr>";
3610
- strXml += ` <a:ln w="${opts.valAxisLineSize ? valToPts(opts.valAxisLineSize) : EMU_PER_POINT}" cap="flat">`;
3611
- strXml += !opts.valAxisLineShow ? "<a:noFill/>" : genXmlColorSelection(opts.valAxisLineColor || DEF_GRIDLINE_COLOR);
3612
- strXml += " <a:prstDash val=\"" + (opts.valAxisLineStyle || "solid") + "\"/>";
3613
- strXml += " <a:round/>";
3614
- strXml += " </a:ln>";
3615
- strXml += " </c:spPr>";
3854
+ strXml += axisLineSpPr(1, opts.valAxisLineSize ? valToPts(opts.valAxisLineSize) : EMU_PER_POINT, opts.valAxisLineShow, opts.valAxisLineColor, opts.valAxisLineStyle || "solid");
3616
3855
  strXml += " <c:txPr>";
3617
3856
  strXml += ` <a:bodyPr${opts.valAxisLabelRotate ? " rot=\"" + convertRotationDegrees(opts.valAxisLabelRotate).toString() + "\"" : ""}/>`;
3618
3857
  strXml += " <a:lstStyle/>";
@@ -3647,7 +3886,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
3647
3886
  let strXml = "";
3648
3887
  strXml += "<c:serAx>";
3649
3888
  strXml += " <c:axId val=\"" + axisId + "\"/>";
3650
- strXml += " <c:scaling><c:orientation val=\"" + (opts.serAxisOrientation || (opts.barDir === "col" ? "minMax" : "minMax")) + "\"/></c:scaling>";
3889
+ strXml += " <c:scaling><c:orientation val=\"" + (opts.serAxisOrientation || "minMax") + "\"/></c:scaling>";
3651
3890
  strXml += " <c:delete val=\"" + (opts.serAxisHidden ? "1" : "0") + "\"/>";
3652
3891
  strXml += " <c:axPos val=\"" + (opts.barDir === "col" ? "b" : "l") + "\"/>";
3653
3892
  strXml += opts.serGridLine && opts.serGridLine.style !== "none" ? createGridLineElement(opts.serGridLine) : "";
@@ -3665,13 +3904,7 @@ function makeSerAxis(opts, axisId, valAxisId) {
3665
3904
  strXml += " <c:majorTickMark val=\"out\"/>";
3666
3905
  strXml += " <c:minorTickMark val=\"none\"/>";
3667
3906
  strXml += ` <c:tickLblPos val="${opts.serAxisLabelPos || (opts.barDir === "col" ? "low" : "nextTo")}"/>`;
3668
- strXml += " <c:spPr>";
3669
- strXml += " <a:ln w=\"12700\" cap=\"flat\">";
3670
- strXml += !opts.serAxisLineShow ? "<a:noFill/>" : genXmlColorSelection(opts.serAxisLineColor || DEF_GRIDLINE_COLOR);
3671
- strXml += " <a:prstDash val=\"solid\"/>";
3672
- strXml += " <a:round/>";
3673
- strXml += " </a:ln>";
3674
- strXml += " </c:spPr>";
3907
+ strXml += axisLineSpPr(2, EMU_PER_POINT, opts.serAxisLineShow, opts.serAxisLineColor, "solid");
3675
3908
  strXml += " <c:txPr>";
3676
3909
  strXml += " <a:bodyPr/>";
3677
3910
  strXml += " <a:lstStyle/>";
@@ -5021,7 +5254,8 @@ function chartExLayoutId(type) {
5021
5254
  /**
5022
5255
  * A deterministic `uniqueId` GUID for a `<cx:series>`, derived from the chart's package-global id.
5023
5256
  * PowerPoint stamps a random GUID here; deriving it keeps output byte-stable (the project pins
5024
- * chart determinism — see `docs/backlog.yml` `fork-chart-counter-nondeterminism`). `seriesIdx`
5257
+ * chart determinism — the package-wide id this derives from is assigned at write time in
5258
+ * `package/assemble.ts`, STEP 2). `seriesIdx`
5025
5259
  * distinguishes multiple series within one chart (pareto emits two), varying the 4th GUID group so
5026
5260
  * every series still gets a distinct id.
5027
5261
  */
@@ -5265,9 +5499,15 @@ function makeXmlChartEx(rel) {
5265
5499
  * template — analogous to the default theme/worksheet boilerplate the library already embeds. They
5266
5500
  * reference theme scheme colors, so they adapt to the deck theme without per-chart customization.
5267
5501
  */
5268
- /** The `cs:colorStyle` payload for a chartEx chart's `colors{N}.xml` part (theme-accent cycle). */
5502
+ /**
5503
+ * The `cs:colorStyle` payload for a chartEx chart's `colors{N}.xml` part (theme-accent cycle).
5504
+ * @raw-xml-asset Captured verbatim from PowerPoint; not built here, so not a migration target.
5505
+ */
5269
5506
  const CHARTEX_COLORS_BODY = "<cs:colorStyle xmlns:cs=\"http://schemas.microsoft.com/office/drawing/2012/chartStyle\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" meth=\"cycle\" id=\"10\"><a:schemeClr val=\"accent1\"/><a:schemeClr val=\"accent2\"/><a:schemeClr val=\"accent3\"/><a:schemeClr val=\"accent4\"/><a:schemeClr val=\"accent5\"/><a:schemeClr val=\"accent6\"/><cs:variation/><cs:variation><a:lumMod val=\"60000\"/></cs:variation><cs:variation><a:lumMod val=\"80000\"/><a:lumOff val=\"20000\"/></cs:variation><cs:variation><a:lumMod val=\"80000\"/></cs:variation><cs:variation><a:lumMod val=\"60000\"/><a:lumOff val=\"40000\"/></cs:variation><cs:variation><a:lumMod val=\"50000\"/></cs:variation><cs:variation><a:lumMod val=\"70000\"/><a:lumOff val=\"30000\"/></cs:variation><cs:variation><a:lumMod val=\"70000\"/></cs:variation><cs:variation><a:lumMod val=\"50000\"/><a:lumOff val=\"50000\"/></cs:variation></cs:colorStyle>";
5270
- /** The `cs:chartStyle` payload for a chartEx chart's `style{N}.xml` part (PowerPoint default). */
5507
+ /**
5508
+ * The `cs:chartStyle` payload for a chartEx chart's `style{N}.xml` part (PowerPoint default).
5509
+ * @raw-xml-asset Captured verbatim from PowerPoint; not built here, so not a migration target.
5510
+ */
5271
5511
  const CHARTEX_STYLE_BODY = "<cs:chartStyle xmlns:cs=\"http://schemas.microsoft.com/office/drawing/2012/chartStyle\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" id=\"395\"><cs:axisTitle><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1197\"/></cs:axisTitle><cs:categoryAxis><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr><cs:defRPr sz=\"1197\"/></cs:categoryAxis><cs:chartArea mods=\"allowNoFillOverride allowNoLineOverride\"><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"bg1\"/></a:solidFill><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr><cs:defRPr sz=\"1330\"/></cs:chartArea><cs:dataLabel><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1197\"/></cs:dataLabel><cs:dataLabelCallout><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"dk1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"lt1\"/></a:solidFill><a:ln><a:solidFill><a:schemeClr val=\"dk1\"><a:lumMod val=\"25000\"/><a:lumOff val=\"75000\"/></a:schemeClr></a:solidFill></a:ln></cs:spPr><cs:defRPr sz=\"1197\"/><cs:bodyPr rot=\"0\" spcFirstLastPara=\"1\" vertOverflow=\"clip\" horzOverflow=\"clip\" vert=\"horz\" wrap=\"square\" lIns=\"36576\" tIns=\"18288\" rIns=\"36576\" bIns=\"18288\" anchor=\"ctr\" anchorCtr=\"1\"><a:spAutoFit/></cs:bodyPr></cs:dataLabelCallout><cs:dataPoint><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:fillRef><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill></cs:spPr></cs:dataPoint><cs:dataPoint3D><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:fillRef><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill></cs:spPr></cs:dataPoint3D><cs:dataPointLine><cs:lnRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:lnRef><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"28575\" cap=\"rnd\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:round/></a:ln></cs:spPr></cs:dataPointLine><cs:dataPointMarker><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:fillRef><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:ln w=\"9525\"><a:solidFill><a:schemeClr val=\"lt1\"/></a:solidFill></a:ln></cs:spPr></cs:dataPointMarker><cs:dataPointMarkerLayout symbol=\"circle\" size=\"5\"/><cs:dataPointWireframe><cs:lnRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:lnRef><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"28575\" cap=\"rnd\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:round/></a:ln></cs:spPr></cs:dataPointWireframe><cs:dataTable><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:spPr><a:ln w=\"9525\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill></a:ln></cs:spPr><cs:defRPr sz=\"1197\"/></cs:dataTable><cs:downBar><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"dk1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"dk1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></a:solidFill><a:ln w=\"9525\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></a:solidFill></a:ln></cs:spPr></cs:downBar><cs:dropLine><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"35000\"/><a:lumOff val=\"65000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:dropLine><cs:errorBar><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:errorBar><cs:floor><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef></cs:floor><cs:gridlineMajor><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:gridlineMajor><cs:gridlineMinor><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:gridlineMinor><cs:hiLoLine><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"75000\"/><a:lumOff val=\"25000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:hiLoLine><cs:leaderLine><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"35000\"/><a:lumOff val=\"65000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr></cs:leaderLine><cs:legend><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1197\"/></cs:legend><cs:plotArea mods=\"allowNoFillOverride allowNoLineOverride\"><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef></cs:plotArea><cs:plotArea3D mods=\"allowNoFillOverride allowNoLineOverride\"><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef></cs:plotArea3D><cs:seriesAxis><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill><a:round/></a:ln></cs:spPr><cs:defRPr sz=\"1197\"/></cs:seriesAxis><cs:seriesLine><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"9525\" cap=\"flat\"><a:solidFill><a:srgbClr val=\"D9D9D9\"/></a:solidFill><a:round/></a:ln></cs:spPr></cs:seriesLine><cs:title><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1862\"/></cs:title><cs:trendline><cs:lnRef idx=\"0\"><cs:styleClr val=\"auto\"/></cs:lnRef><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef><cs:spPr><a:ln w=\"19050\" cap=\"rnd\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"sysDash\"/></a:ln></cs:spPr></cs:trendline><cs:trendlineLabel><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1197\"/></cs:trendlineLabel><cs:upBar><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"dk1\"/></cs:fontRef><cs:spPr><a:solidFill><a:schemeClr val=\"lt1\"/></a:solidFill><a:ln w=\"9525\"><a:solidFill><a:schemeClr val=\"tx1\"><a:lumMod val=\"15000\"/><a:lumOff val=\"85000\"/></a:schemeClr></a:solidFill></a:ln></cs:spPr></cs:upBar><cs:valueAxis><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"><a:lumMod val=\"65000\"/><a:lumOff val=\"35000\"/></a:schemeClr></cs:fontRef><cs:defRPr sz=\"1197\"/></cs:valueAxis><cs:wall><cs:lnRef idx=\"0\"/><cs:fillRef idx=\"0\"/><cs:effectRef idx=\"0\"/><cs:fontRef idx=\"minor\"><a:schemeClr val=\"tx1\"/></cs:fontRef></cs:wall></cs:chartStyle>";
5272
5512
  /** Build the `ppt/charts/colors{N}.xml` part body. */
5273
5513
  function makeChartExColorsXml() {
@@ -5279,7 +5519,6 @@ function makeChartExStyleXml() {
5279
5519
  }
5280
5520
  //#endregion
5281
5521
  //#region src/gen/chart/embed-xlsx.ts
5282
- const PACKAGE_REL_NS$2 = "http://schemas.openxmlformats.org/package/2006/relationships";
5283
5522
  function relationship$2(id, type, target) {
5284
5523
  return voidEl("Relationship", {
5285
5524
  Id: id,
@@ -5289,7 +5528,7 @@ function relationship$2(id, type, target) {
5289
5528
  }
5290
5529
  /** `<Relationships>` wrapper shared by the workbook's `.rels` parts (all flat, no indent). */
5291
5530
  function relationships(rels) {
5292
- return el("Relationships", { xmlns: PACKAGE_REL_NS$2 }, rels.map(raw));
5531
+ return el("Relationships", { xmlns: PACKAGE_REL_NS }, rels.map(raw));
5293
5532
  }
5294
5533
  /**
5295
5534
  * Build the chart's embedded Excel workbook as a standalone OPC package and
@@ -5308,22 +5547,22 @@ function buildEmbeddedWorksheet(chartObject) {
5308
5547
  const IS_MULTI_CAT_AXES = (data[0]?.labels?.length ?? 0) > 1;
5309
5548
  zipExcel.add("[Content_Types].xml", XML_DECL + "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"> <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/> <Default Extension=\"xml\" ContentType=\"application/xml\"/> <Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/> <Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/> <Override PartName=\"/xl/theme/theme1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.theme+xml\"/> <Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/> <Override PartName=\"/xl/sharedStrings.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\"/> <Override PartName=\"/xl/tables/table1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml\"/> <Override PartName=\"/docProps/core.xml\" ContentType=\"application/vnd.openxmlformats-package.core-properties+xml\"/> <Override PartName=\"/docProps/app.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.extended-properties+xml\"/></Types>\n");
5310
5549
  zipExcel.add("_rels/.rels", XML_DECL + relationships([
5311
- relationship$2("rId1", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml"),
5312
- relationship$2("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml"),
5313
- relationship$2("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml")
5550
+ relationship$2("rId1", PACKAGE_REL_NS + "/metadata/core-properties", "docProps/core.xml"),
5551
+ relationship$2("rId2", OFFICE_REL + "extended-properties", "docProps/app.xml"),
5552
+ relationship$2("rId3", OFFICE_REL + "officeDocument", "xl/workbook.xml")
5314
5553
  ]) + "\n");
5315
5554
  zipExcel.add("docProps/app.xml", XML_DECL + "<Properties xmlns=\"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties\" xmlns:vt=\"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes\"><Application>Microsoft Macintosh Excel</Application><DocSecurity>0</DocSecurity><ScaleCrop>false</ScaleCrop><HeadingPairs><vt:vector size=\"2\" baseType=\"variant\"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>1</vt:i4></vt:variant></vt:vector></HeadingPairs><TitlesOfParts><vt:vector size=\"1\" baseType=\"lpstr\"><vt:lpstr>Sheet1</vt:lpstr></vt:vector></TitlesOfParts><Company></Company><LinksUpToDate>false</LinksUpToDate><SharedDoc>false</SharedDoc><HyperlinksChanged>false</HyperlinksChanged><AppVersion>16.0300</AppVersion></Properties>\n");
5316
5555
  zipExcel.add("docProps/core.xml", XML_DECL + "<cp:coreProperties xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" xmlns:dcmitype=\"http://purl.org/dc/dcmitype/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><dc:creator>TsPptx</dc:creator><cp:lastModifiedBy>TsPptx</cp:lastModifiedBy><dcterms:created xsi:type=\"dcterms:W3CDTF\">" + (/* @__PURE__ */ new Date()).toISOString() + "</dcterms:created><dcterms:modified xsi:type=\"dcterms:W3CDTF\">" + (/* @__PURE__ */ new Date()).toISOString() + "</dcterms:modified></cp:coreProperties>");
5317
5556
  zipExcel.add("xl/_rels/workbook.xml.rels", XML_DECL + relationships([
5318
- relationship$2("rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml"),
5319
- relationship$2("rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "theme/theme1.xml"),
5320
- relationship$2("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", "worksheets/sheet1.xml"),
5321
- relationship$2("rId4", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", "sharedStrings.xml")
5557
+ relationship$2("rId3", OFFICE_REL + "styles", "styles.xml"),
5558
+ relationship$2("rId2", OFFICE_REL + "theme", "theme/theme1.xml"),
5559
+ relationship$2("rId1", OFFICE_REL + "worksheet", "worksheets/sheet1.xml"),
5560
+ relationship$2("rId4", OFFICE_REL + "sharedStrings", "sharedStrings.xml")
5322
5561
  ]));
5323
5562
  zipExcel.add("xl/styles.xml", XML_DECL + "<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><numFmts count=\"1\"><numFmt numFmtId=\"0\" formatCode=\"General\"/></numFmts><fonts count=\"4\"><font><sz val=\"9\"/><color indexed=\"8\"/><name val=\"Geneva\"/></font><font><sz val=\"9\"/><color indexed=\"8\"/><name val=\"Geneva\"/></font><font><sz val=\"10\"/><color indexed=\"8\"/><name val=\"Geneva\"/></font><font><sz val=\"18\"/><color indexed=\"8\"/><name val=\"Arial\"/></font></fonts><fills count=\"2\"><fill><patternFill patternType=\"none\"/></fill><fill><patternFill patternType=\"gray125\"/></fill></fills><borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders><dxfs count=\"0\"/><tableStyles count=\"0\"/><colors><indexedColors><rgbColor rgb=\"ff000000\"/><rgbColor rgb=\"ffffffff\"/><rgbColor rgb=\"ffff0000\"/><rgbColor rgb=\"ff00ff00\"/><rgbColor rgb=\"ff0000ff\"/><rgbColor rgb=\"ffffff00\"/><rgbColor rgb=\"ffff00ff\"/><rgbColor rgb=\"ff00ffff\"/><rgbColor rgb=\"ff000000\"/><rgbColor rgb=\"ffffffff\"/><rgbColor rgb=\"ff878787\"/><rgbColor rgb=\"fff9f9f9\"/></indexedColors></colors></styleSheet>\n");
5324
5563
  zipExcel.add("xl/theme/theme1.xml", XML_DECL + "<a:theme xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" name=\"Office Theme\"><a:themeElements><a:clrScheme name=\"Office\"><a:dk1><a:sysClr val=\"windowText\" lastClr=\"000000\"/></a:dk1><a:lt1><a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1><a:dk2><a:srgbClr val=\"44546A\"/></a:dk2><a:lt2><a:srgbClr val=\"E7E6E6\"/></a:lt2><a:accent1><a:srgbClr val=\"4472C4\"/></a:accent1><a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2><a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3><a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4><a:accent5><a:srgbClr val=\"5B9BD5\"/></a:accent5><a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6><a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink><a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink></a:clrScheme><a:fontScheme name=\"Office\"><a:majorFont><a:latin typeface=\"Calibri Light\" panose=\"020F0302020204030204\"/><a:ea typeface=\"\"/><a:cs typeface=\"\"/><a:font script=\"Jpan\" typeface=\"Yu Gothic Light\"/><a:font script=\"Hang\" typeface=\"맑은 고딕\"/><a:font script=\"Hans\" typeface=\"DengXian Light\"/><a:font script=\"Hant\" typeface=\"新細明體\"/><a:font script=\"Arab\" typeface=\"Times New Roman\"/><a:font script=\"Hebr\" typeface=\"Times New Roman\"/><a:font script=\"Thai\" typeface=\"Tahoma\"/><a:font script=\"Ethi\" typeface=\"Nyala\"/><a:font script=\"Beng\" typeface=\"Vrinda\"/><a:font script=\"Gujr\" typeface=\"Shruti\"/><a:font script=\"Khmr\" typeface=\"MoolBoran\"/><a:font script=\"Knda\" typeface=\"Tunga\"/><a:font script=\"Guru\" typeface=\"Raavi\"/><a:font script=\"Cans\" typeface=\"Euphemia\"/><a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/><a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/><a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/><a:font script=\"Thaa\" typeface=\"MV Boli\"/><a:font script=\"Deva\" typeface=\"Mangal\"/><a:font script=\"Telu\" typeface=\"Gautami\"/><a:font script=\"Taml\" typeface=\"Latha\"/><a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/><a:font script=\"Orya\" typeface=\"Kalinga\"/><a:font script=\"Mlym\" typeface=\"Kartika\"/><a:font script=\"Laoo\" typeface=\"DokChampa\"/><a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/><a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/><a:font script=\"Viet\" typeface=\"Times New Roman\"/><a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/><a:font script=\"Geor\" typeface=\"Sylfaen\"/></a:majorFont><a:minorFont><a:latin typeface=\"Calibri\" panose=\"020F0502020204030204\"/><a:ea typeface=\"\"/><a:cs typeface=\"\"/><a:font script=\"Jpan\" typeface=\"Yu Gothic\"/><a:font script=\"Hang\" typeface=\"맑은 고딕\"/><a:font script=\"Hans\" typeface=\"DengXian\"/><a:font script=\"Hant\" typeface=\"新細明體\"/><a:font script=\"Arab\" typeface=\"Arial\"/><a:font script=\"Hebr\" typeface=\"Arial\"/><a:font script=\"Thai\" typeface=\"Tahoma\"/><a:font script=\"Ethi\" typeface=\"Nyala\"/><a:font script=\"Beng\" typeface=\"Vrinda\"/><a:font script=\"Gujr\" typeface=\"Shruti\"/><a:font script=\"Khmr\" typeface=\"DaunPenh\"/><a:font script=\"Knda\" typeface=\"Tunga\"/><a:font script=\"Guru\" typeface=\"Raavi\"/><a:font script=\"Cans\" typeface=\"Euphemia\"/><a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/><a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/><a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/><a:font script=\"Thaa\" typeface=\"MV Boli\"/><a:font script=\"Deva\" typeface=\"Mangal\"/><a:font script=\"Telu\" typeface=\"Gautami\"/><a:font script=\"Taml\" typeface=\"Latha\"/><a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/><a:font script=\"Orya\" typeface=\"Kalinga\"/><a:font script=\"Mlym\" typeface=\"Kartika\"/><a:font script=\"Laoo\" typeface=\"DokChampa\"/><a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/><a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/><a:font script=\"Viet\" typeface=\"Arial\"/><a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/><a:font script=\"Geor\" typeface=\"Sylfaen\"/></a:minorFont></a:fontScheme><a:fmtScheme name=\"Office\"><a:fillStyleLst><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"110000\"/><a:satMod val=\"105000\"/><a:tint val=\"67000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"103000\"/><a:tint val=\"73000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"105000\"/><a:satMod val=\"109000\"/><a:tint val=\"81000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:satMod val=\"103000\"/><a:lumMod val=\"102000\"/><a:tint val=\"94000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:satMod val=\"110000\"/><a:lumMod val=\"100000\"/><a:shade val=\"100000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:lumMod val=\"99000\"/><a:satMod val=\"120000\"/><a:shade val=\"78000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w=\"6350\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln><a:ln w=\"12700\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln><a:ln w=\"19050\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:prstDash val=\"solid\"/><a:miter lim=\"800000\"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad=\"57150\" dist=\"19050\" dir=\"5400000\" algn=\"ctr\" rotWithShape=\"0\"><a:srgbClr val=\"000000\"><a:alpha val=\"63000\"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill><a:solidFill><a:schemeClr val=\"phClr\"><a:tint val=\"95000\"/><a:satMod val=\"170000\"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape=\"1\"><a:gsLst><a:gs pos=\"0\"><a:schemeClr val=\"phClr\"><a:tint val=\"93000\"/><a:satMod val=\"150000\"/><a:shade val=\"98000\"/><a:lumMod val=\"102000\"/></a:schemeClr></a:gs><a:gs pos=\"50000\"><a:schemeClr val=\"phClr\"><a:tint val=\"98000\"/><a:satMod val=\"130000\"/><a:shade val=\"90000\"/><a:lumMod val=\"103000\"/></a:schemeClr></a:gs><a:gs pos=\"100000\"><a:schemeClr val=\"phClr\"><a:shade val=\"63000\"/><a:satMod val=\"120000\"/></a:schemeClr></a:gs></a:gsLst><a:lin ang=\"5400000\" scaled=\"0\"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri=\"{05A4C25C-085E-4340-85A3-A5531E510DB2}\"><thm15:themeFamily xmlns:thm15=\"http://schemas.microsoft.com/office/thememl/2012/main\" name=\"Office Theme\" id=\"{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}\" vid=\"{4A3C46E8-61CC-4603-A589-7422A47A8E4A}\"/></a:ext></a:extLst></a:theme>");
5325
5564
  zipExcel.add("xl/workbook.xml", XML_DECL + "<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" mc:Ignorable=\"x15\" xmlns:x15=\"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main\"><fileVersion appName=\"xl\" lastEdited=\"7\" lowestEdited=\"6\" rupBuild=\"10507\"/><workbookPr/><bookViews><workbookView xWindow=\"0\" yWindow=\"500\" windowWidth=\"20960\" windowHeight=\"15960\"/></bookViews><sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets><calcPr calcId=\"0\" concurrentCalc=\"0\"/></workbook>\n");
5326
- zipExcel.add("xl/worksheets/_rels/sheet1.xml.rels", XML_DECL + relationships([relationship$2("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table", "../tables/table1.xml")]) + "\n");
5565
+ zipExcel.add("xl/worksheets/_rels/sheet1.xml.rels", XML_DECL + relationships([relationship$2("rId1", OFFICE_REL + "table", "../tables/table1.xml")]) + "\n");
5327
5566
  zipExcel.add("xl/sharedStrings.xml", buildXlsxSharedStrings(chartObject, data, intBubbleCols, IS_MULTI_CAT_AXES));
5328
5567
  zipExcel.add("xl/tables/table1.xml", buildXlsxTable(chartObject, data, intBubbleCols));
5329
5568
  zipExcel.add("xl/worksheets/sheet1.xml", buildXlsxSheet(chartObject, data, intBubbleCols, IS_MULTI_CAT_AXES));
@@ -5504,7 +5743,7 @@ function buildXlsxSheet(chartObject, data, intBubbleCols, IS_MULTI_CAT_AXES) {
5504
5743
  * @return {string} the chart part's `.rels` XML
5505
5744
  */
5506
5745
  function buildChartRelsXml(embeddingTarget) {
5507
- return XML_DECL + relationships([relationship$2("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package", embeddingTarget)]);
5746
+ return XML_DECL + relationships([relationship$2("rId1", OFFICE_REL + "package", embeddingTarget)]);
5508
5747
  }
5509
5748
  /**
5510
5749
  * Build the `.rels` for a chartEx chart part: the embedded workbook (rId1, via `<cx:externalData>`)
@@ -5516,7 +5755,7 @@ function buildChartRelsXml(embeddingTarget) {
5516
5755
  */
5517
5756
  function buildChartExRelsXml(embeddingTarget, colorsTarget, styleTarget) {
5518
5757
  return XML_DECL + relationships([
5519
- relationship$2("rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package", embeddingTarget),
5758
+ relationship$2("rId1", OFFICE_REL + "package", embeddingTarget),
5520
5759
  relationship$2("rId2", "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle", colorsTarget),
5521
5760
  relationship$2("rId3", "http://schemas.microsoft.com/office/2011/relationships/chartStyle", styleTarget)
5522
5761
  ]);
@@ -5634,6 +5873,63 @@ function encodeSlideMediaRels(layout, runtime, onMediaError = "throw") {
5634
5873
  return imageProms;
5635
5874
  }
5636
5875
  //#endregion
5876
+ //#region src/gen/prepare.ts
5877
+ /**
5878
+ * ts-pptx: the pre-serialization pass
5879
+ *
5880
+ * Everything that must happen to authored slide state *before* any XML is built. Two callers
5881
+ * reach serialization and both need it:
5882
+ *
5883
+ * - `package/assemble.ts` — the normal `write`/`stream`/`writeFile` path, building a
5884
+ * complete `.pptx`.
5885
+ * - `PresentationCore.extractSlides()` — serializes slide bodies only, for splicing into
5886
+ * an already-loaded deck via `Presentation.appendSlides()` (`ts-pptx/read`).
5887
+ *
5888
+ * The second used to reimplement the first inline, kept in step by a comment that said
5889
+ * "exactly as ... does". Nothing failed when they drifted: `extractSlides` would simply emit
5890
+ * slides that did not match what a normal write produces, and no test compares the two.
5891
+ *
5892
+ * Two primitives rather than one combined pass, because the package path has work of its own
5893
+ * that must land *between* them: it de-duplicates identical media across the deck (which
5894
+ * needs the encoded bytes {@link encodeMediaForTargets} produces) and assigns package-unique
5895
+ * chart part filenames, both before any slide content is baked. Exposing the halves lets
5896
+ * `assemble` keep its established order exactly while still sharing the code.
5897
+ *
5898
+ * The order across the two is load-bearing:
5899
+ *
5900
+ * 1. **Media first.** It is the only asynchronous step, and the sync XML pass reads the
5901
+ * `rel.data` it populates.
5902
+ * 2. **Backfill placeholders**, so a slide inherits its layout's placeholder objects before
5903
+ * anything measures or serializes them.
5904
+ * 3. **Bake measured fit last**, because it measures the text step 2 may have just added and
5905
+ * writes the `fontScale` the sync XML pass then reads.
5906
+ *
5907
+ * The two functions take different target sets on purpose: media lives on layouts and the
5908
+ * master as well as on slides, while placeholder backfill and measured fit apply only to
5909
+ * slides. `extractSlides` passes only its slides to both, because it emits no layout or
5910
+ * master parts.
5911
+ */
5912
+ /**
5913
+ * Step 1: load and base64-encode every media rel on the given targets, populating `rel.data`.
5914
+ * @param {ReadonlyArray<PresSlideInternal | SlideLayoutInternal>} targets - media-bearing targets (slides, layouts, master)
5915
+ * @param {RuntimeAdapter} runtime - runtime adapter used to load media
5916
+ * @param {'throw' | 'placeholder'} onMediaError - failure policy for unloadable media
5917
+ */
5918
+ async function encodeMediaForTargets(targets, runtime, onMediaError) {
5919
+ const promises = [];
5920
+ for (const target of targets) promises.push(...encodeSlideMediaRels(target, runtime, onMediaError));
5921
+ await Promise.all(promises);
5922
+ }
5923
+ /**
5924
+ * Steps 2 and 3: backfill inherited layout placeholders, then bake measured text fit.
5925
+ * @param {PresSlideInternal[]} slides - the slides to prepare
5926
+ * @param {FontMetricsRegistry} fontMetrics - registered font metrics; an empty registry skips the fit bake
5927
+ */
5928
+ function bakeSlideContent(slides, fontMetrics) {
5929
+ for (const slide of slides) if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
5930
+ applyMeasuredFit(slides, fontMetrics);
5931
+ }
5932
+ //#endregion
5637
5933
  //#region src/gen/slide/shape-ids.ts
5638
5934
  /**
5639
5935
  * Every object a slide renders, paired with the `<p:cNvPr>` id it is rendered with: top-level
@@ -6352,9 +6648,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
6352
6648
  case "justify":
6353
6649
  paragraphPropXml += " algn=\"just\"";
6354
6650
  break;
6355
- default:
6356
- paragraphPropXml += "";
6357
- break;
6651
+ default: paragraphPropXml += "";
6358
6652
  }
6359
6653
  if (opts.lineSpacing) strXmlLnSpc = el("a:lnSpc", null, raw(voidEl("a:spcPts", { val: clampLineSpacingPts(opts.lineSpacing) })));
6360
6654
  else if (opts.lineSpacingMultiple) strXmlLnSpc = el("a:lnSpc", null, raw(voidEl("a:spcPct", { val: Math.round(opts.lineSpacingMultiple * PERCENT_SCALE) })));
@@ -6366,13 +6660,13 @@ function genXmlParagraphProperties(textObj, isDefault) {
6366
6660
  const isPictureBullet = !!(bulletImage && (bulletImage.path || bulletImage.data));
6367
6661
  if (opts.bullet?.indent) bulletMarL = valToPts(opts.bullet.indent);
6368
6662
  if (opts.bullet.color && !isPictureBullet) strXmlBulletColor = el("a:buClr", null, raw(createColorElement(opts.bullet.color)));
6369
- let bulletSizePct = PERCENT_SCALE;
6663
+ let bulletSizePct;
6370
6664
  if (opts.bullet.size !== void 0) {
6371
6665
  const bulletSize = Number(opts.bullet.size);
6372
6666
  if (isNaN(bulletSize) || bulletSize < 25 || bulletSize > 400) warn("bullet/size-out-of-range", "`bullet.size` must be a percentage between 25 and 400!");
6373
6667
  else bulletSizePct = Math.round(bulletSize * FIXED_PCT_PER_PERCENT);
6374
6668
  }
6375
- const strXmlBulletSize = voidEl("a:buSzPct", { val: bulletSizePct });
6669
+ const strXmlBulletSize = bulletSizePct === void 0 ? "" : voidEl("a:buSzPct", { val: bulletSizePct });
6376
6670
  const strXmlBulletFont = opts.bullet.fontFace ? voidEl("a:buFont", { typeface: opts.bullet.fontFace }) : "";
6377
6671
  const bulletIndentAttrs = () => ` marL="${opts.indentLevel && opts.indentLevel > 0 ? bulletMarL + bulletMarL * opts.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
6378
6672
  if (isPictureBullet) {
@@ -6407,7 +6701,7 @@ function genXmlParagraphProperties(textObj, isDefault) {
6407
6701
  }
6408
6702
  } else if (opts.bullet) {
6409
6703
  paragraphPropXml += ` marL="${opts.indentLevel && opts.indentLevel > 0 ? bulletMarL + bulletMarL * opts.indentLevel : bulletMarL}" indent="-${bulletMarL}"`;
6410
- strXmlBullet = voidEl("a:buSzPct", { val: "100000" }) + buChar("&#x2022;");
6704
+ strXmlBullet = buChar("&#x2022;");
6411
6705
  } else if (!opts.bullet) {
6412
6706
  paragraphPropXml += " indent=\"0\" marL=\"0\"";
6413
6707
  strXmlBullet = voidEl("a:buNone");
@@ -7094,10 +7388,7 @@ function genXmlCustGeom(options, cx, cy, layout) {
7094
7388
  pt(point.x, point.y)
7095
7389
  ].map(raw), BEZ_INDENT));
7096
7390
  break;
7097
- case "quadratic":
7098
- nodes.push(el("a:quadBezTo", null, [pt(point.curve.x1, point.curve.y1), pt(point.x, point.y)].map(raw), BEZ_INDENT));
7099
- break;
7100
- default: break;
7391
+ case "quadratic": nodes.push(el("a:quadBezTo", null, [pt(point.curve.x1, point.curve.y1), pt(point.x, point.y)].map(raw), BEZ_INDENT));
7101
7392
  }
7102
7393
  else if ("close" in point) nodes.push(voidEl("a:close", null, SPACE_BEFORE_SLASH));
7103
7394
  else if (point.moveTo || i === 0) nodes.push(el("a:moveTo", null, raw(pt(point.x, point.y))));
@@ -7186,40 +7477,46 @@ function genXmlCustGeom(options, cx, cy, layout) {
7186
7477
  * ts-pptx: DrawingML image sizing & cropping
7187
7478
  *
7188
7479
  * Build the `<a:srcRect>` (+ `<a:stretch>`) blipFill children for the image
7189
- * sizing modes (`cover` / `contain` / `crop`) and for explicit percentage crops.
7480
+ * sizing modes (`cover` / `contain` / `crop` / `stretch`), for the implicit
7481
+ * aspect-correct placement a vector source gets, and for explicit percentage crops.
7190
7482
  */
7191
7483
  /** Every `<a:srcRect>` below is followed by this same fill directive. */
7192
7484
  const STRETCH = el("a:stretch", null, raw(voidEl("a:fillRect")));
7485
+ /** The `<a:srcRect>` fitting `imgSize` into `boxDim`, followed by the fill directive. */
7486
+ function fitSrcRect(type, imgSize, boxDim) {
7487
+ const { l, r, t, b } = fitSrcRectPercents(type, imgSize, boxDim);
7488
+ return voidEl("a:srcRect", {
7489
+ l,
7490
+ r,
7491
+ t,
7492
+ b
7493
+ }) + STRETCH;
7494
+ }
7193
7495
  const ImageSizingXml = {
7194
7496
  cover: function(imgSize, boxDim) {
7195
- const { l, r, t, b } = fitSrcRectPercents("cover", imgSize, boxDim);
7196
- return voidEl("a:srcRect", {
7197
- l,
7198
- r,
7199
- t,
7200
- b
7201
- }) + STRETCH;
7497
+ return fitSrcRect("cover", imgSize, boxDim);
7202
7498
  },
7203
7499
  contain: function(imgSize, boxDim) {
7204
- const { l, r, t, b } = fitSrcRectPercents("contain", imgSize, boxDim);
7205
- return voidEl("a:srcRect", {
7206
- l,
7207
- r,
7208
- t,
7209
- b
7210
- }) + STRETCH;
7500
+ return fitSrcRect("contain", imgSize, boxDim);
7501
+ },
7502
+ /** No source rectangle at all: the image fills the box, aspect ratio be damned. */
7503
+ stretch: function() {
7504
+ return STRETCH;
7211
7505
  },
7212
7506
  crop: function(imgSize, boxDim) {
7213
7507
  const l = boxDim.x;
7214
7508
  const r = imgSize.w - (boxDim.x + boxDim.w);
7215
7509
  const t = boxDim.y;
7216
7510
  const b = imgSize.h - (boxDim.y + boxDim.h);
7217
- if (l < 0 || r < 0 || t < 0 || b < 0) throw new InvalidOptionError("image/crop-window-overflows", `addImage sizing.type 'crop': crop window overflows image bounds — ${[
7218
- l < 0 && `x (${l < 0 ? -l : 0} past left edge)`,
7219
- r < 0 && `x+w (${-r} past right edge)`,
7220
- t < 0 && `y (${-t} past top edge)`,
7221
- b < 0 && `y+h (${-b} past bottom edge)`
7222
- ].filter(Boolean).join(", ")}. Ensure x≥0, y≥0, x+w≤w, y+h≤h.`);
7511
+ if (l < 0 || r < 0 || t < 0 || b < 0) {
7512
+ const over = [
7513
+ l < 0 && `x (${l < 0 ? -l : 0} past left edge)`,
7514
+ r < 0 && `x+w (${-r} past right edge)`,
7515
+ t < 0 && `y (${-t} past top edge)`,
7516
+ b < 0 && `y+h (${-b} past bottom edge)`
7517
+ ].filter(Boolean).join(", ");
7518
+ throw new InvalidOptionError("image/crop-window-overflows", `addImage sizing.type 'crop': crop window overflows image bounds — ${over}. Ensure x≥0, y≥0, x+w≤w, y+h≤h.`);
7519
+ }
7223
7520
  return voidEl("a:srcRect", {
7224
7521
  l: Math.round(1e5 * (l / imgSize.w)),
7225
7522
  r: Math.round(1e5 * (r / imgSize.w)),
@@ -7229,6 +7526,23 @@ const ImageSizingXml = {
7229
7526
  }
7230
7527
  };
7231
7528
  /**
7529
+ * The blipFill children for a vector source the caller gave no `sizing` for: letterbox it to its
7530
+ * own aspect ratio inside its frame, the way `sizing: 'contain'` would — an SVG carries an
7531
+ * intrinsic ratio, and stretching a glyph into a box that disagrees with it is never the intent.
7532
+ *
7533
+ * Returns `null` when the two ratios already agree, which is the overwhelmingly common case (a
7534
+ * square icon in a square box). The caller then emits the plain `<a:stretch>` it always did, so
7535
+ * this default costs nothing — neither bytes nor a behaviour change — until it actually differs.
7536
+ * @param imgSize - the vector's intrinsic size (viewBox or width/height)
7537
+ * @param boxDim - the picture's displayed frame
7538
+ * @returns the `<a:srcRect>` + `<a:stretch>` children, or `null` if no letterboxing is needed
7539
+ */
7540
+ function genXmlVectorAspectFit(imgSize, boxDim) {
7541
+ const { l, r, t, b } = fitSrcRectPercents("contain", imgSize, boxDim);
7542
+ if (l === 0 && r === 0 && t === 0 && b === 0) return null;
7543
+ return fitSrcRect("contain", imgSize, boxDim);
7544
+ }
7545
+ /**
7232
7546
  * Build an explicit `<a:srcRect>` crop from percentage edge insets (0–100), emitted verbatim.
7233
7547
  * Each inset is the percent of the *source* image trimmed from that edge; values serialize in
7234
7548
  * 1000ths of a percent (DrawingML `ST_Percentage`, where 100% = 100000). Unlike `sizing: 'crop'`
@@ -7265,10 +7579,12 @@ function genXmlImageCrop(crop, objectName) {
7265
7579
  function renderImageObject(slideItemObj, idx, slide, x, y, cx, cy, imgWidth, imgHeight, placeholderObj, locationAttrs, sizing, rounding) {
7266
7580
  let strSlideXml = "";
7267
7581
  slideItemObj.options = slideItemObj.options || {};
7582
+ const mediaRel = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid);
7583
+ const relData = mediaRel?.data;
7584
+ const naturalSize = () => typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
7268
7585
  if (slideItemObj.options._szAuto) {
7269
7586
  const szAuto = slideItemObj.options._szAuto;
7270
- const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
7271
- const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
7587
+ const natural = naturalSize();
7272
7588
  if (natural) {
7273
7589
  if (szAuto.w && szAuto.h) {
7274
7590
  cx = pixelsToEmu(natural.w, 96);
@@ -7299,7 +7615,8 @@ function renderImageObject(slideItemObj, idx, slide, x, y, cx, cy, imgWidth, img
7299
7615
  imgOpts.biLevel ? raw(voidEl("a:biLevel", { thresh: Math.round(imgOpts.biLevel.threshold * PERCENT_SCALE) })) : null
7300
7616
  ];
7301
7617
  strSlideXml += "<p:blipFill>";
7302
- if ((slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.extn === "svg") strSlideXml += el("a:blip", { "r:embed": `rId${(slideItemObj.imageRid ?? 0) - 1}` }, [...blipEffects(" "), raw(el("a:extLst", null, raw(el("a:ext", { uri: "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" }, raw(voidEl("asvg:svgBlip", {
7618
+ const isVector = mediaRel?.extn === "svg";
7619
+ if (isVector) strSlideXml += el("a:blip", { "r:embed": `rId${(slideItemObj.imageRid ?? 0) - 1}` }, [...blipEffects(" "), raw(el("a:extLst", null, raw(el("a:ext", { uri: "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" }, raw(voidEl("asvg:svgBlip", {
7303
7620
  "xmlns:asvg": "http://schemas.microsoft.com/office/drawing/2016/SVG/main",
7304
7621
  "r:embed": `rId${slideItemObj.imageRid}`
7305
7622
  }, { openPrefix: " " })), {
@@ -7323,8 +7640,7 @@ function renderImageObject(slideItemObj, idx, slide, x, y, cx, cy, imgWidth, img
7323
7640
  h: imgHeight
7324
7641
  };
7325
7642
  if (sizing.type === "cover" || sizing.type === "contain") {
7326
- const relData = (slide._relsMedia || []).find((rel) => rel.rId === slideItemObj.imageRid)?.data;
7327
- const natural = typeof relData === "string" ? getImageSizeFromBase64(relData) : null;
7643
+ const natural = naturalSize();
7328
7644
  if (natural) cropSize = natural;
7329
7645
  else warn("image/unmeasurable-natural-size", `sizing '${sizing.type}' could not measure natural dimensions for image "${slideItemObj.options.objectName}"; falling back to displayed aspect ratio (crop may be inexact). Provide a raster image (PNG/JPEG/GIF/BMP/WebP) or an SVG with width/height or a viewBox to enable an aspect-correct crop.`);
7330
7646
  }
@@ -7336,7 +7652,14 @@ function renderImageObject(slideItemObj, idx, slide, x, y, cx, cy, imgWidth, img
7336
7652
  });
7337
7653
  imgWidth = boxW;
7338
7654
  imgHeight = boxH;
7339
- } else strSlideXml += el("a:stretch", null, raw(voidEl("a:fillRect")), { openPrefix: " " });
7655
+ } else {
7656
+ const natural = isVector ? naturalSize() : null;
7657
+ const aspectFit = natural ? genXmlVectorAspectFit(natural, {
7658
+ w: imgWidth,
7659
+ h: imgHeight
7660
+ }) : null;
7661
+ strSlideXml += aspectFit ?? el("a:stretch", null, raw(voidEl("a:fillRect")), { openPrefix: " " });
7662
+ }
7340
7663
  strSlideXml += "</p:blipFill>";
7341
7664
  strSlideXml += "<p:spPr>";
7342
7665
  strSlideXml += el("a:xfrm", locationAttrs, [raw(voidEl("a:off", {
@@ -7410,6 +7733,216 @@ function renderMediaObject(slideItemObj, idx, x, y, cx, cy, locationAttrs) {
7410
7733
  ]);
7411
7734
  }
7412
7735
  //#endregion
7736
+ //#region src/gen/slide/objects/model3d.ts
7737
+ /** The `am3d` namespace, doubling as `a:graphicData@uri` and the `mc:Choice Requires` token's URI. */
7738
+ const AM3D_NS = "http://schemas.microsoft.com/office/drawing/2017/model3d";
7739
+ /** Denominator of every `am3d` rational element (`@n`/`@d`). */
7740
+ const RATIO_DEN = 1e6;
7741
+ /**
7742
+ * `am3d:raster@rName`/`@rVer` — the renderer that produced the cached preview. PowerPoint stamps
7743
+ * its own renderer here; ts-pptx did not rasterize anything, but the attributes are what PowerPoint
7744
+ * writes and the element is where the preview blip lives, so they are reproduced verbatim.
7745
+ */
7746
+ const RASTER = {
7747
+ rName: "Office3DRenderer",
7748
+ rVer: "16.0.8326"
7749
+ };
7750
+ /**
7751
+ * `am3d:objViewport@viewportSz`. Bounding-box derived in PowerPoint's output (a cube and a slab
7752
+ * gave different values); the cube's is emitted, matching the fixed default camera.
7753
+ */
7754
+ const OBJ_VIEWPORT_SZ = 3338805;
7755
+ /** The three point lights PowerPoint writes for every inserted model — key, fill and rim. */
7756
+ const PT_LIGHTS = [
7757
+ {
7758
+ clr: {
7759
+ r: 1e5,
7760
+ g: 75e3,
7761
+ b: 5e4
7762
+ },
7763
+ intensityN: 9765625,
7764
+ pos: {
7765
+ x: 21959998,
7766
+ y: 70920001,
7767
+ z: 16344003
7768
+ }
7769
+ },
7770
+ {
7771
+ clr: {
7772
+ r: 4e4,
7773
+ g: 6e4,
7774
+ b: 95e3
7775
+ },
7776
+ intensityN: 1225e4,
7777
+ pos: {
7778
+ x: -37964106,
7779
+ y: 51130435,
7780
+ z: 57631972
7781
+ }
7782
+ },
7783
+ {
7784
+ clr: {
7785
+ r: 86837,
7786
+ g: 72700,
7787
+ b: 1e5
7788
+ },
7789
+ intensityN: 3125e3,
7790
+ pos: {
7791
+ x: -37739122,
7792
+ y: 58056624,
7793
+ z: -34769649
7794
+ }
7795
+ }
7796
+ ];
7797
+ /** `am3d:ambientLight` — neutral 50% scRGB at half illuminance, constant like the point lights. */
7798
+ const AMBIENT = {
7799
+ clr: {
7800
+ r: 5e4,
7801
+ g: 5e4,
7802
+ b: 5e4
7803
+ },
7804
+ illuminanceN: 5e5
7805
+ };
7806
+ /** `<am3d:clr><a:scrgbClr …/></am3d:clr>` — the colour wrapper both light kinds share. */
7807
+ function lightColor(clr) {
7808
+ return el("am3d:clr", null, raw(voidEl("a:scrgbClr", clr)));
7809
+ }
7810
+ /** The `am3d:camera` block: eye position, up vector, aim point and perspective fov. */
7811
+ function cameraEl(model) {
7812
+ return el("am3d:camera", null, [
7813
+ raw(voidEl("am3d:pos", model.pos)),
7814
+ raw(voidEl("am3d:up", model.up)),
7815
+ raw(voidEl("am3d:lookAt", model.lookAt)),
7816
+ raw(voidEl("am3d:perspective", { fov: model.fov }))
7817
+ ]);
7818
+ }
7819
+ /**
7820
+ * The `am3d:trans` block: model-unit scale plus an identity placement. Only `meterPerModelUnit`
7821
+ * varies — `preTrans`/`scale`/`rot`/`postTrans` are the identity PowerPoint writes for a model
7822
+ * inserted without any subsequent 3D manipulation, and `am3d:rot` is emitted attribute-less.
7823
+ */
7824
+ function transEl(model) {
7825
+ const unit = {
7826
+ n: RATIO_DEN,
7827
+ d: RATIO_DEN
7828
+ };
7829
+ return el("am3d:trans", null, [
7830
+ raw(voidEl("am3d:meterPerModelUnit", {
7831
+ n: model.meterPerModelUnitN,
7832
+ d: RATIO_DEN
7833
+ })),
7834
+ raw(voidEl("am3d:preTrans", {
7835
+ dx: 0,
7836
+ dy: 0,
7837
+ dz: 0
7838
+ })),
7839
+ raw(el("am3d:scale", null, [
7840
+ raw(voidEl("am3d:sx", unit)),
7841
+ raw(voidEl("am3d:sy", unit)),
7842
+ raw(voidEl("am3d:sz", unit))
7843
+ ])),
7844
+ raw(voidEl("am3d:rot")),
7845
+ raw(voidEl("am3d:postTrans", {
7846
+ dx: 0,
7847
+ dy: 0,
7848
+ dz: 0
7849
+ }))
7850
+ ]);
7851
+ }
7852
+ /** The fixed lighting rig: one ambient light and three point lights, in PowerPoint's order. */
7853
+ function lightsXml() {
7854
+ return el("am3d:ambientLight", null, [raw(lightColor(AMBIENT.clr)), raw(voidEl("am3d:illuminance", {
7855
+ n: AMBIENT.illuminanceN,
7856
+ d: RATIO_DEN
7857
+ }))]) + PT_LIGHTS.map((light) => el("am3d:ptLight", { rad: 0 }, [
7858
+ raw(lightColor(light.clr)),
7859
+ raw(voidEl("am3d:intensity", {
7860
+ n: light.intensityN,
7861
+ d: RATIO_DEN
7862
+ })),
7863
+ raw(voidEl("am3d:pos", light.pos))
7864
+ ])).join("");
7865
+ }
7866
+ /** The `mc:Fallback` picture — the cached preview, drawn by everything that is not PowerPoint 2019+. */
7867
+ function fallbackPic(picId, objectName, altText, model, xf) {
7868
+ return el("p:pic", null, [
7869
+ raw(el("p:nvPicPr", null, [
7870
+ raw(cNvPrOpen(picId, objectName, altText) + "/>"),
7871
+ raw(el("p:cNvPicPr", null, raw(voidEl("a:picLocks", {
7872
+ noGrp: "1",
7873
+ noRot: "1",
7874
+ noChangeAspect: "1",
7875
+ noMove: "1",
7876
+ noResize: "1",
7877
+ noEditPoints: "1",
7878
+ noAdjustHandles: "1",
7879
+ noChangeArrowheads: "1",
7880
+ noChangeShapeType: "1",
7881
+ noCrop: "1"
7882
+ })))),
7883
+ raw(voidEl("p:nvPr"))
7884
+ ])),
7885
+ raw(el("p:blipFill", null, [raw(voidEl("a:blip", { "r:embed": `rId${model.previewRid}` })), raw(el("a:stretch", null, raw(voidEl("a:fillRect"))))])),
7886
+ raw(el("p:spPr", null, [raw(el("a:xfrm", null, [raw(voidEl("a:off", {
7887
+ x: xf.x,
7888
+ y: xf.y
7889
+ })), raw(voidEl("a:ext", {
7890
+ cx: xf.cx,
7891
+ cy: xf.cy
7892
+ }))])), raw(el("a:prstGeom", { prst: "rect" }, raw(voidEl("a:avLst"))))]))
7893
+ ]);
7894
+ }
7895
+ /**
7896
+ * Render a `model3d` slide object to its `<mc:AlternateContent>` XML. The `mc:Choice` carries the
7897
+ * real `<p:graphicFrame>` (the `am3d:model3d` graphicData PowerPoint 2019+ renders live); the
7898
+ * `mc:Fallback` carries the cached preview picture. See `gen/define/model3d.ts`.
7899
+ */
7900
+ function renderModel3dObject(slideItemObj, idx, x, y, cx, cy) {
7901
+ const model = slideItemObj.model3d;
7902
+ if (!model) return "";
7903
+ const opts = slideItemObj.options || {};
7904
+ const altText = opts.altText || "";
7905
+ const spPr = el("am3d:spPr", null, [raw(el("a:xfrm", null, [raw(voidEl("a:off", {
7906
+ x: 0,
7907
+ y: 0
7908
+ })), raw(voidEl("a:ext", {
7909
+ cx,
7910
+ cy
7911
+ }))])), raw(el("a:prstGeom", { prst: "rect" }, raw(voidEl("a:avLst"))))]);
7912
+ const model3d = el("am3d:model3d", { "r:embed": `rId${model.modelRid}` }, [
7913
+ raw(spPr),
7914
+ raw(cameraEl(model)),
7915
+ raw(transEl(model)),
7916
+ raw(el("am3d:raster", RASTER, raw(voidEl("am3d:blip", { "r:embed": `rId${model.previewRid}` })))),
7917
+ raw(voidEl("am3d:objViewport", { viewportSz: OBJ_VIEWPORT_SZ })),
7918
+ raw(lightsXml())
7919
+ ]);
7920
+ const graphicFrame = el("p:graphicFrame", null, [
7921
+ raw(el("p:nvGraphicFramePr", null, [
7922
+ raw(cNvPrOpen(idx + 2, opts.objectName, altText) + "/>"),
7923
+ raw(el("p:cNvGraphicFramePr", null, raw(voidEl("a:graphicFrameLocks")))),
7924
+ raw(voidEl("p:nvPr"))
7925
+ ])),
7926
+ raw(el("p:xfrm", null, [raw(voidEl("a:off", {
7927
+ x,
7928
+ y
7929
+ })), raw(voidEl("a:ext", {
7930
+ cx,
7931
+ cy
7932
+ }))])),
7933
+ raw(el("a:graphic", null, raw(el("a:graphicData", { uri: AM3D_NS }, raw(model3d)))))
7934
+ ]);
7935
+ return el("mc:AlternateContent", { "xmlns:mc": MC_NS }, [raw(el("mc:Choice", {
7936
+ "xmlns:am3d": AM3D_NS,
7937
+ Requires: "am3d"
7938
+ }, raw(graphicFrame))), raw(el("mc:Fallback", null, raw(fallbackPic(idx + 2, opts.objectName, altText, model, {
7939
+ x,
7940
+ y,
7941
+ cx,
7942
+ cy
7943
+ }))))]);
7944
+ }
7945
+ //#endregion
7413
7946
  //#region src/gen/slide/objects/ole.ts
7414
7947
  /** VML namespace — declared by an OLE object's `mc:Choice Requires="v"` (no VML content is emitted). */
7415
7948
  const VML_NS = "urn:schemas-microsoft-com:vml";
@@ -7483,13 +8016,64 @@ function renderOleObject(slideItemObj, idx, x, y, cx, cy) {
7483
8016
  //#endregion
7484
8017
  //#region src/gen/drawingml/table-border.ts
7485
8018
  /**
7486
- * Emit the `<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>` border children of an `<a:tcPr>` for a table cell.
7487
- * Shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a merged region's
7488
- * outer edges render with the same border as its origin cell.
7489
- * @param {BorderProps[]} cellBorder - 4-tuple of border props in [top, right, bottom, left] order
7490
- * @return {string} concatenated border element XML, in the LRTB document order PowerPoint expects
8019
+ * One `<a:lnX>` border element. Every side of a table cell — the four edges and the two
8020
+ * diagonals — is a `CT_LineProperties`, so they differ only in element name.
8021
+ * @param {string} name - the element's local name (`lnL`, `lnTlToBr`, …)
8022
+ * @param {BorderProps} border - the resolved border for that side
8023
+ * @return {string} the border element XML
8024
+ */
8025
+ function genBorderLine(name, border) {
8026
+ const cap = createLineCap(border.cap);
8027
+ if (border.type === "none") return el(`a:${name}`, {
8028
+ w: 0,
8029
+ cap,
8030
+ cmpd: "sng",
8031
+ algn: "ctr"
8032
+ }, raw(voidEl("a:noFill")));
8033
+ return el(`a:${name}`, {
8034
+ w: valToPts(resolveBorderWidth(border, 1)),
8035
+ cap,
8036
+ cmpd: "sng",
8037
+ algn: "ctr"
8038
+ }, [
8039
+ genXmlColorSelection({
8040
+ color: border.color ?? "363636",
8041
+ transparency: border.transparency
8042
+ }),
8043
+ voidEl("a:prstDash", { val: resolveBorderDash(border) }),
8044
+ voidEl("a:round"),
8045
+ voidEl("a:headEnd", {
8046
+ type: "none",
8047
+ w: "med",
8048
+ len: "med"
8049
+ }),
8050
+ voidEl("a:tailEnd", {
8051
+ type: "none",
8052
+ w: "med",
8053
+ len: "med"
8054
+ })
8055
+ ].map(raw));
8056
+ }
8057
+ /**
8058
+ * Emit the border children of an `<a:tcPr>` for a table cell: the four edges, then the two
8059
+ * diagonals when the cell asks for them.
8060
+ *
8061
+ * The edges are shared by normal cells and the dummy span (`_hmerge`/`_vmerge`) cells so a
8062
+ * merged region's outer edges render with the same border as its origin cell. The diagonals
8063
+ * are **not**: a diagonal across a merged region is one stroke corner-to-corner, and
8064
+ * repeating it on each covered cell would draw a sawtooth instead. Only the span origin
8065
+ * carries them, which is also where PowerPoint puts them.
8066
+ *
8067
+ * A side may be missing, and missing is not the same as `{type:'none'}`: `none` writes an
8068
+ * explicit `w="0"` rule, which is direct formatting and overrides whatever a built-in table
8069
+ * style would have drawn there, while an omitted side leaves that edge to the style. The
8070
+ * definition step sends every cell here dense; a caller reaching this directly may not.
8071
+ *
8072
+ * @param {BorderProps[]} cellBorder - up to a 4-tuple of border props in [top, right, bottom, left] order
8073
+ * @param {TableCellDiagonals} [diagonal] - the cell's optional corner-to-corner rules
8074
+ * @return {string} concatenated border element XML, in the document order PowerPoint expects
7491
8075
  */
7492
- function genTableCellBorderXml(cellBorder) {
8076
+ function genTableCellBorderXml(cellBorder, diagonal) {
7493
8077
  let strXml = "";
7494
8078
  [
7495
8079
  {
@@ -7511,42 +8095,113 @@ function genTableCellBorderXml(cellBorder) {
7511
8095
  ].forEach((obj) => {
7512
8096
  const border = cellBorder[obj.idx];
7513
8097
  if (!border) return;
7514
- const cap = createLineCap(border.cap);
7515
- if (border.type !== "none") strXml += el(`a:${obj.name}`, {
7516
- w: valToPts(resolveBorderWidth(border, 1)),
7517
- cap,
7518
- cmpd: "sng",
7519
- algn: "ctr"
7520
- }, [
7521
- genXmlColorSelection({
7522
- color: border.color ?? "363636",
7523
- transparency: border.transparency
7524
- }),
7525
- voidEl("a:prstDash", { val: border.type === "dash" ? "sysDash" : "solid" }),
7526
- voidEl("a:round"),
7527
- voidEl("a:headEnd", {
7528
- type: "none",
7529
- w: "med",
7530
- len: "med"
7531
- }),
7532
- voidEl("a:tailEnd", {
7533
- type: "none",
7534
- w: "med",
7535
- len: "med"
7536
- })
7537
- ].map(raw));
7538
- else strXml += el(`a:${obj.name}`, {
7539
- w: 0,
7540
- cap,
7541
- cmpd: "sng",
7542
- algn: "ctr"
7543
- }, raw(voidEl("a:noFill")));
8098
+ strXml += genBorderLine(obj.name, border);
7544
8099
  });
8100
+ if (diagonal?.tlToBr) strXml += genBorderLine("lnTlToBr", diagonal.tlToBr);
8101
+ if (diagonal?.blToTr) strXml += genBorderLine("lnBlToTr", diagonal.blToTr);
7545
8102
  return strXml;
7546
8103
  }
7547
8104
  //#endregion
8105
+ //#region src/gen/drawingml/table-cell3d.ts
8106
+ /**
8107
+ * Vet one enumerated value before it reaches the XML, reporting and dropping anything outside
8108
+ * its `ST_` union. See `ooxml/check-enum.ts` for why the write path drops rather than throws.
8109
+ */
8110
+ function resolveEnum(value, valid, field) {
8111
+ return checkEnumOrWarn(value, valid, "table/invalid-cell3d", `table cell: cell3D \`${field}\``, { field });
8112
+ }
8113
+ /**
8114
+ * A bevel dimension in EMU (`ST_PositiveCoordinate`), from a width in points.
8115
+ * A negative or non-finite value cannot be written, so it falls back to the attribute's
8116
+ * own default of 76200 EMU (6pt) rather than emitting something PowerPoint would reject.
8117
+ */
8118
+ function bevelSizeEmu(points, field) {
8119
+ if (points === void 0 || points === null) return null;
8120
+ if (!Number.isFinite(points) || points < 0) {
8121
+ warnOnce("table/invalid-cell3d", `table cell: cell3D \`${field}\` must be a non-negative number of points; \`${String(points)}\` is ignored.`, {
8122
+ received: points,
8123
+ field
8124
+ });
8125
+ return null;
8126
+ }
8127
+ return valToPts(points);
8128
+ }
8129
+ /**
8130
+ * Emit a cell's `<a:cell3D>`, or `''` when it has none.
8131
+ * @param {TableCell3DProps} [cell3D] - the cell's 3-D options
8132
+ * @return {string} the `a:cell3D` element XML
8133
+ */
8134
+ function genTableCell3DXml(cell3D) {
8135
+ if (!cell3D || typeof cell3D !== "object") return "";
8136
+ const bevel = voidEl("a:bevel", {
8137
+ w: bevelSizeEmu(cell3D.width, "width"),
8138
+ h: bevelSizeEmu(cell3D.height, "height"),
8139
+ prst: resolveEnum(cell3D.preset, BEVEL_PRESETS, "preset")
8140
+ });
8141
+ const rig = resolveEnum(cell3D.lightRig?.rig, LIGHT_RIGS, "lightRig.rig");
8142
+ const dir = resolveEnum(cell3D.lightRig?.dir, LIGHT_RIG_DIRECTIONS, "lightRig.dir");
8143
+ const lightRig = rig && dir ? voidEl("a:lightRig", {
8144
+ rig,
8145
+ dir
8146
+ }) : "";
8147
+ if (cell3D.lightRig && !lightRig) warnOnce("table/invalid-cell3d", "table cell: cell3D `lightRig` needs both `rig` and `dir` (both are required by CT_LightRig); the light rig is dropped and the bevel takes the renderer default.", { received: cell3D.lightRig });
8148
+ return el("a:cell3D", { prstMaterial: resolveEnum(cell3D.material, PRESET_MATERIALS, "material") }, [raw(bevel), lightRig ? raw(lightRig) : null]);
8149
+ }
8150
+ //#endregion
7548
8151
  //#region src/gen/slide/objects/table.ts
7549
8152
  /**
8153
+ * Validate a cell's `horzOverflow` before it reaches the XML, reporting and dropping anything
8154
+ * outside `ST_TextHorzOverflowType`. `undefined` (the common case) emits nothing.
8155
+ */
8156
+ function resolveHorzOverflow(value) {
8157
+ return checkEnumOrWarn(value, TEXT_HORZ_OVERFLOW, "table/invalid-horz-overflow", "table cell: horzOverflow");
8158
+ }
8159
+ /**
8160
+ * Overlay `TableProps.outerBorder` onto one cell's own 4-side border tuple.
8161
+ *
8162
+ * The perimeter is decided per **grid position**, not per authored cell, which is what makes
8163
+ * merges work: a colspan's origin sits at the left of its span and its covered `_hmerge`
8164
+ * cells sit to the right, so a span straddling the last column gets that column's rule on the
8165
+ * covered cell — exactly where PowerPoint defines a merged region's outer edge. A cell in the
8166
+ * table's interior touches no edge and comes back untouched.
8167
+ *
8168
+ * Returns the input array (not a copy) when nothing applies, so the common no-perimeter path
8169
+ * allocates nothing and stays byte-identical. Never mutates: `arrTabRows` holds the caller's
8170
+ * own cell objects and repeated `write()` calls must not accumulate borders.
8171
+ *
8172
+ * @param base - the cell's resolved `[top, right, bottom, left]` borders, or `null` when it has none
8173
+ * @param outer - the normalized perimeter tuple, `undefined` for a side the caller left out
8174
+ * @param at - where this cell sits in the merge grid
8175
+ * @returns the borders to emit, or `null` when there are none and the perimeter adds none
8176
+ */
8177
+ function applyOuterBorder(base, outer, at) {
8178
+ if (!outer) return base;
8179
+ const onEdge = [
8180
+ at.rIdx === 0,
8181
+ at.cIdx === at.lastCol,
8182
+ at.rIdx === at.lastRow,
8183
+ at.cIdx === 0
8184
+ ];
8185
+ const applies = [
8186
+ 0,
8187
+ 1,
8188
+ 2,
8189
+ 3
8190
+ ].filter((idx) => onEdge[idx] && outer[idx]);
8191
+ if (applies.length === 0) return base;
8192
+ const merged = base ? [...base] : [
8193
+ { type: "none" },
8194
+ { type: "none" },
8195
+ { type: "none" },
8196
+ { type: "none" }
8197
+ ];
8198
+ for (const idx of applies) {
8199
+ const side = outer[idx];
8200
+ if (side) merged[idx] = side;
8201
+ }
8202
+ return merged;
8203
+ }
8204
+ /**
7550
8205
  * Render a `table` slide object to its `<p:graphicFrame>` XML (merge-grid, row/col spans, per-cell styling).
7551
8206
  */
7552
8207
  function renderTableObject(slideItemObj, idx, x, y, cx, cy, placeholderObj, itemOpts) {
@@ -7589,8 +8244,15 @@ function renderTableObject(slideItemObj, idx, x, y, cx, cy, placeholderObj, item
7589
8244
  firstCol: objTabOpts.hasFirstColumn ? "1" : null,
7590
8245
  lastCol: objTabOpts.hasLastColumn ? "1" : null
7591
8246
  };
7592
- tblInner = objTabOpts.tableStyle ? el("a:tblPr", tblPrAttrs, raw(el("a:tableStyleId", null, objTabOpts.tableStyle))) : voidEl("a:tblPr", tblPrAttrs);
7593
- }
8247
+ const tblPrChildren = (objTabOpts.tableFill ? genXmlColorSelection(objTabOpts.tableFill) : "") + (objTabOpts.tableStyle ? el("a:tableStyleId", null, objTabOpts.tableStyle) : "");
8248
+ tblInner = tblPrChildren ? el("a:tblPr", tblPrAttrs, raw(tblPrChildren)) : voidEl("a:tblPr", tblPrAttrs);
8249
+ }
8250
+ const outerBorder = !objTabOpts.outerBorder ? void 0 : Array.isArray(objTabOpts.outerBorder) ? objTabOpts.outerBorder : [
8251
+ objTabOpts.outerBorder,
8252
+ objTabOpts.outerBorder,
8253
+ objTabOpts.outerBorder,
8254
+ objTabOpts.outerBorder
8255
+ ];
7594
8256
  {
7595
8257
  const gridColsEmu = resolveTableColWidthsEmu(objTabOpts.colW, cx, intColCnt);
7596
8258
  tblInner += el("a:tblGrid", null, gridColsEmu.map((w) => raw(voidEl("a:gridCol", { w }))));
@@ -7642,8 +8304,14 @@ function renderTableObject(slideItemObj, idx, x, y, cx, cy, placeholderObj, item
7642
8304
  else if (objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH))) intRowH = inch2Emu(Number(objTabOpts.rowH));
7643
8305
  else if (itemOpts.cy || itemOpts.h) intRowH = Math.round((itemOpts.h ? cy : typeof itemOpts.cy === "number" ? itemOpts.cy : 1) / arrTabRows.length);
7644
8306
  const rowCells = [];
7645
- cells.forEach((cellObj) => {
8307
+ cells.forEach((cellObj, cIdx) => {
7646
8308
  const cell = cellObj;
8309
+ const at = {
8310
+ rIdx,
8311
+ cIdx,
8312
+ lastRow: arrTabRows.length - 1,
8313
+ lastCol: cells.length - 1
8314
+ };
7647
8315
  const cellSpanAttrs = {
7648
8316
  rowSpan: cell.options?.rowspan && cell.options.rowspan > 1 ? cell.options.rowspan : void 0,
7649
8317
  gridSpan: cell.options?.colspan && cell.options.colspan > 1 ? cell.options.colspan : void 0,
@@ -7652,11 +8320,11 @@ function renderTableObject(slideItemObj, idx, x, y, cx, cy, placeholderObj, item
7652
8320
  };
7653
8321
  if (cell._hmerge || cell._vmerge) {
7654
8322
  const origin = cell._spanOrigin;
8323
+ const originOpts = origin?.options || {};
7655
8324
  let spanPrXml = "";
8325
+ const originBorder = applyOuterBorder(Array.isArray(originOpts.border) ? originOpts.border : null, outerBorder, at);
8326
+ if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
7656
8327
  if (origin) {
7657
- const originOpts = origin.options || {};
7658
- const originBorder = Array.isArray(originOpts.border) ? originOpts.border : null;
7659
- if (originBorder) spanPrXml += genTableCellBorderXml(originBorder);
7660
8328
  const spanFill = originOpts.fill || "";
7661
8329
  if (spanFill) spanPrXml += genXmlColorSelection(spanFill);
7662
8330
  }
@@ -7694,16 +8362,25 @@ function renderTableObject(slideItemObj, idx, x, y, cx, cy, placeholderObj, item
7694
8362
  cellMargin
7695
8363
  ];
7696
8364
  if (!Array.isArray(cellMargin) || cellMargin.length !== 4 || cellMargin.some((v) => typeof v !== "number" || !isFinite(v))) cellMargin = DEF_CELL_MARGIN_IN;
8365
+ const cellHorzOverflow = resolveHorzOverflow(cellOpts.horzOverflow);
7697
8366
  const tcPrAttrs = {
7698
8367
  marL: marginToEmu(cellMargin[3]),
7699
8368
  marR: marginToEmu(cellMargin[1]),
7700
8369
  marT: marginToEmu(cellMargin[0]),
7701
8370
  marB: marginToEmu(cellMargin[2]),
7702
8371
  anchor: cellValign,
7703
- vert: cellTextDir
8372
+ anchorCtr: cellOpts.anchorCtr ? "1" : null,
8373
+ vert: cellTextDir,
8374
+ horzOverflow: cellHorzOverflow
7704
8375
  };
7705
- const cellBorder = Array.isArray(cellOpts.border) ? cellOpts.border : null;
7706
- rowCells.push(el("a:tc", cellSpanAttrs, [raw(genXmlTextBody(cell)), raw(el("a:tcPr", tcPrAttrs, [cellBorder ? raw(genTableCellBorderXml(cellBorder)) : null, raw(cellFill)], { closePrefix: " " }))], { closePrefix: " " }));
8376
+ const cellBorder = applyOuterBorder(Array.isArray(cellOpts.border) ? cellOpts.border : null, outerBorder, at);
8377
+ const cellDiagonal = cellOpts.diagonal;
8378
+ const cellBorderXml = cellBorder || cellDiagonal ? genTableCellBorderXml(cellBorder ?? [], cellDiagonal) : "";
8379
+ rowCells.push(el("a:tc", cellSpanAttrs, [raw(genXmlTextBody(cell)), raw(el("a:tcPr", tcPrAttrs, [
8380
+ raw(cellBorderXml),
8381
+ raw(genTableCell3DXml(cellOpts.cell3D)),
8382
+ raw(cellFill)
8383
+ ], { closePrefix: " " }))], { closePrefix: " " }));
7707
8384
  });
7708
8385
  tblInner += el("a:tr", { h: intRowH }, rowCells.map(raw));
7709
8386
  });
@@ -7942,7 +8619,6 @@ function renderZoomObject(slideItemObj, idx, x, y, cx, cy) {
7942
8619
  }
7943
8620
  //#endregion
7944
8621
  //#region src/gen/slide/object.ts
7945
- const PACKAGE_REL_NS$1 = "http://schemas.openxmlformats.org/package/2006/relationships";
7946
8622
  /** The MS-2007 `media` rel that pairs with an ECMA audio/video/online rel on the same Target. */
7947
8623
  const MS_MEDIA_REL = "http://schemas.microsoft.com/office/2007/relationships/media";
7948
8624
  /** Slide→chartEx-part relationship type (MS, not the ECMA `.../relationships/chart`). */
@@ -8153,6 +8829,9 @@ function slideObjectToXml(slide) {
8153
8829
  case "zoom":
8154
8830
  strSlideXml += renderZoomObject(slideItemObj, idx, x, y, cx, cy);
8155
8831
  break;
8832
+ case "model3d":
8833
+ strSlideXml += renderModel3dObject(slideItemObj, idx, x, y, cx, cy);
8834
+ break;
8156
8835
  case "group": {
8157
8836
  const groupChildren = slideItemObj._groupObjects || [];
8158
8837
  let innerXml = "";
@@ -8204,9 +8883,7 @@ function slideObjectToXml(slide) {
8204
8883
  strSlideXml += "</p:grpSp>";
8205
8884
  break;
8206
8885
  }
8207
- default:
8208
- strSlideXml += "";
8209
- break;
8886
+ default: strSlideXml += "";
8210
8887
  }
8211
8888
  return strSlideXml;
8212
8889
  };
@@ -8303,12 +8980,12 @@ function slideObjectRelationsToXml(slide, defaultRels) {
8303
8980
  lastRid = Math.max(lastRid, rel.rId);
8304
8981
  if (isHyperlinkRel(rel)) if (rel.data === "slide") rels.push(voidEl("Relationship", {
8305
8982
  Id: `rId${rel.rId}`,
8306
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide",
8983
+ Type: OFFICE_REL + "slide",
8307
8984
  Target: `slide${rel.Target}.xml`
8308
8985
  }));
8309
8986
  else rels.push(voidEl("Relationship", {
8310
8987
  Id: `rId${rel.rId}`,
8311
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
8988
+ Type: OFFICE_REL + "hyperlink",
8312
8989
  Target: rel.Target,
8313
8990
  TargetMode: "External"
8314
8991
  }));
@@ -8317,7 +8994,7 @@ function slideObjectRelationsToXml(slide, defaultRels) {
8317
8994
  lastRid = Math.max(lastRid, rel.rId);
8318
8995
  rels.push(voidEl("Relationship", {
8319
8996
  Id: `rId${rel.rId}`,
8320
- Type: rel.isChartEx ? CHARTEX_REL : "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
8997
+ Type: rel.isChartEx ? CHARTEX_REL : OFFICE_REL + "chart",
8321
8998
  Target: rel.Target
8322
8999
  }));
8323
9000
  });
@@ -8332,10 +9009,11 @@ function slideObjectRelationsToXml(slide, defaultRels) {
8332
9009
  });
8333
9010
  lastRid = Math.max(lastRid, rel.rId);
8334
9011
  if (rel.oleRelType) rels.push(media(rel.oleRelType));
8335
- else if (relType.includes("image")) rels.push(media("http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"));
8336
- else if (relType.includes("audio")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL) : media("http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio"));
8337
- else if (relType.includes("video")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL) : media("http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"));
8338
- else if (relType.includes("online")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL, "External") : media("http://schemas.openxmlformats.org/officeDocument/2006/relationships/video", "External"));
9012
+ else if (rel.model3dRelType) rels.push(media(rel.model3dRelType));
9013
+ else if (relType.includes("image")) rels.push(media(OFFICE_REL + "image"));
9014
+ else if (relType.includes("audio")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL) : media(OFFICE_REL + "audio"));
9015
+ else if (relType.includes("video")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL) : media(OFFICE_REL + "video"));
9016
+ else if (relType.includes("online")) rels.push(hasTarget(relTarget) ? media(MS_MEDIA_REL, "External") : media(OFFICE_REL + "video", "External"));
8339
9017
  });
8340
9018
  defaultRels.forEach((rel, idx) => {
8341
9019
  rels.push(voidEl("Relationship", {
@@ -8344,7 +9022,7 @@ function slideObjectRelationsToXml(slide, defaultRels) {
8344
9022
  Target: rel.target
8345
9023
  }));
8346
9024
  });
8347
- return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS$1 }, rels.map(raw));
9025
+ return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS }, rels.map(raw));
8348
9026
  }
8349
9027
  //#endregion
8350
9028
  //#region src/gen/slide/slide.ts
@@ -8422,7 +9100,6 @@ function getLayoutIdxForSlide(slides, slideLayouts, slideNumber) {
8422
9100
  }
8423
9101
  //#endregion
8424
9102
  //#region src/gen/slide/notes.ts
8425
- const PACKAGE_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
8426
9103
  /**
8427
9104
  * Collect the speaker-notes runs for a slide (flattened across any number of `addNotes()` calls).
8428
9105
  * @param {PresSlideInternal} slide - the slide object
@@ -8906,19 +9583,19 @@ function makeXmlNotesSlide(slide) {
8906
9583
  function makeXmlNotesSlideRel(slide, slideNumber) {
8907
9584
  const hlinkRels = buildNotesSlideRels(slide).map((rel) => voidEl("Relationship", {
8908
9585
  Id: `rId${rel.rId}`,
8909
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
9586
+ Type: OFFICE_REL + "hyperlink",
8910
9587
  Target: rel.Target,
8911
9588
  TargetMode: "External"
8912
9589
  })).join("");
8913
9590
  return XML_DECL + el("Relationships", { xmlns: PACKAGE_REL_NS }, [
8914
9591
  raw(voidEl("Relationship", {
8915
9592
  Id: "rId1",
8916
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster",
9593
+ Type: OFFICE_REL + "notesMaster",
8917
9594
  Target: "../notesMasters/notesMaster1.xml"
8918
9595
  })),
8919
9596
  raw(voidEl("Relationship", {
8920
9597
  Id: "rId2",
8921
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide",
9598
+ Type: OFFICE_REL + "slide",
8922
9599
  Target: `../slides/slide${slideNumber}.xml`
8923
9600
  })),
8924
9601
  raw(hlinkRels)
@@ -8935,7 +9612,7 @@ function makeXmlNotesSlideRel(slide, slideNumber) {
8935
9612
  function makeXmlNotesMasterRel() {
8936
9613
  return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS }, raw(voidEl("Relationship", {
8937
9614
  Id: "rId1",
8938
- Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
9615
+ Type: OFFICE_REL + "theme",
8939
9616
  Target: "../theme/theme2.xml"
8940
9617
  })), {
8941
9618
  childPrefix: "\n ",
@@ -9421,11 +10098,10 @@ function makeXmlCustomProperties(props) {
9421
10098
  * Emit the package root relationships (app/core/presentation, plus custom-props
9422
10099
  * when present).
9423
10100
  */
9424
- const SCHEMA_BASE = "http://schemas.openxmlformats.org/";
9425
10101
  function relationship$1(id, type, target) {
9426
10102
  return voidEl("Relationship", {
9427
10103
  Id: id,
9428
- Type: SCHEMA_BASE + type,
10104
+ Type: type,
9429
10105
  Target: target
9430
10106
  }, { openPrefix: "\n " });
9431
10107
  }
@@ -9434,11 +10110,11 @@ function relationship$1(id, type, target) {
9434
10110
  * @returns XML
9435
10111
  */
9436
10112
  function makeXmlRootRels(hasCustomProps) {
9437
- return XML_DECL + "\r\n" + el("Relationships", { xmlns: "http://schemas.openxmlformats.org/package/2006/relationships" }, [
9438
- raw(relationship$1("rId1", "officeDocument/2006/relationships/extended-properties", "docProps/app.xml")),
9439
- raw(relationship$1("rId2", "package/2006/relationships/metadata/core-properties", "docProps/core.xml")),
9440
- raw(relationship$1("rId3", "officeDocument/2006/relationships/officeDocument", "ppt/presentation.xml")),
9441
- hasCustomProps ? raw(relationship$1("rId4", "officeDocument/2006/relationships/custom-properties", "docProps/custom.xml")) : null
10113
+ return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS }, [
10114
+ raw(relationship$1("rId1", OFFICE_REL + "extended-properties", "docProps/app.xml")),
10115
+ raw(relationship$1("rId2", PACKAGE_REL_NS + "/metadata/core-properties", "docProps/core.xml")),
10116
+ raw(relationship$1("rId3", OFFICE_REL + "officeDocument", "ppt/presentation.xml")),
10117
+ hasCustomProps ? raw(relationship$1("rId4", OFFICE_REL + "custom-properties", "docProps/custom.xml")) : null
9442
10118
  ], { closePrefix: "\n " });
9443
10119
  }
9444
10120
  //#endregion
@@ -9478,13 +10154,13 @@ function presentationFontRelStart(slides) {
9478
10154
  }
9479
10155
  function makeXmlPresentationRels(slides, embeddedFonts) {
9480
10156
  let intRelNum = 1;
9481
- const rels = [relationship(1, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster", "slideMasters/slideMaster1.xml")];
9482
- for (let idx = 1; idx <= slides.length; idx++) rels.push(relationship(++intRelNum, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide", `slides/slide${idx}.xml`));
10157
+ const rels = [relationship(1, OFFICE_REL + "slideMaster", "slideMasters/slideMaster1.xml")];
10158
+ for (let idx = 1; idx <= slides.length; idx++) rels.push(relationship(++intRelNum, OFFICE_REL + "slide", `slides/slide${idx}.xml`));
9483
10159
  intRelNum++;
9484
- rels.push(relationship(intRelNum + 0, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster", "notesMasters/notesMaster1.xml"), relationship(intRelNum + 1, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps", "presProps.xml"), relationship(intRelNum + 2, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps", "viewProps.xml"), relationship(intRelNum + 3, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", "theme/theme1.xml"), relationship(intRelNum + 4, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles", "tableStyles.xml"));
9485
- if ((slides || []).some((slide) => (slide._comments || []).length > 0)) rels.push(relationship(intRelNum + 5, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors", "commentAuthors.xml"));
10160
+ rels.push(relationship(intRelNum + 0, OFFICE_REL + "notesMaster", "notesMasters/notesMaster1.xml"), relationship(intRelNum + 1, OFFICE_REL + "presProps", "presProps.xml"), relationship(intRelNum + 2, OFFICE_REL + "viewProps", "viewProps.xml"), relationship(intRelNum + 3, OFFICE_REL + "theme", "theme/theme1.xml"), relationship(intRelNum + 4, OFFICE_REL + "tableStyles", "tableStyles.xml"));
10161
+ if ((slides || []).some((slide) => (slide._comments || []).length > 0)) rels.push(relationship(intRelNum + 5, OFFICE_REL + "commentAuthors", "commentAuthors.xml"));
9486
10162
  for (const face of flattenEmbeddedFaces(embeddedFonts || [], presentationFontRelStart(slides))) rels.push(relationship(face.rId, FONT_REL_TYPE, `fonts/font${face.partIndex}.fntdata`));
9487
- return XML_DECL + "\r\n" + el("Relationships", { xmlns: "http://schemas.openxmlformats.org/package/2006/relationships" }, rels.map(raw));
10163
+ return XML_DECL + "\r\n" + el("Relationships", { xmlns: PACKAGE_REL_NS }, rels.map(raw));
9488
10164
  }
9489
10165
  //#endregion
9490
10166
  //#region src/gen/pres/presentation.ts
@@ -9614,79 +10290,11 @@ function makeXmlViewProps() {
9614
10290
  * @see: http://openxmldeveloper.org/discussions/formats/f/13/p/2398/8107.aspx
9615
10291
  * @return {string} XML
9616
10292
  */
9617
- function makeXmlTableStyles(tableStyles = []) {
9618
- const attrs = {
10293
+ function makeXmlTableStyles() {
10294
+ return XML_DECL + "\r\n" + voidEl("a:tblStyleLst", {
9619
10295
  "xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
9620
10296
  def: "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"
9621
- };
9622
- if (!tableStyles || tableStyles.length === 0) return XML_DECL + "\r\n" + voidEl("a:tblStyleLst", attrs);
9623
- return XML_DECL + "\r\n" + el("a:tblStyleLst", attrs, tableStyles.map(({ guid, def }) => raw(el("a:tblStyle", {
9624
- styleId: guid,
9625
- styleName: def.name
9626
- }, [
9627
- ["wholeTbl", def.wholeTbl],
9628
- ["band1H", def.band1H],
9629
- ["band2H", def.band2H],
9630
- ["band1V", def.band1V],
9631
- ["band2V", def.band2V],
9632
- ["lastCol", def.lastCol],
9633
- ["firstCol", def.firstCol],
9634
- ["lastRow", def.lastRow],
9635
- ["firstRow", def.firstRow]
9636
- ].map(([name, region]) => region ? raw(genXmlTableStyleRegion(name, region)) : null)))));
9637
- }
9638
- /**
9639
- * Build one `CT_TablePartStyle` region (e.g. `firstRow`, `band1H`) for a custom table style.
9640
- * Emits `tcTxStyle` (text) before `tcStyle` (cell fill/borders) per the schema sequence.
9641
- * @param {string} name - region element name
9642
- * @param {TableStyleRegionProps} region - region styling
9643
- * @return {string} XML
9644
- */
9645
- function genXmlTableStyleRegion(name, region) {
9646
- const children = [region.bold !== void 0 || region.italic !== void 0 || region.color ? raw(el("a:tcTxStyle", {
9647
- b: region.bold ? "on" : null,
9648
- i: region.italic ? "on" : null
9649
- }, [raw(voidEl("a:fontRef", { idx: "minor" })), region.color ? raw(createColorElement(region.color)) : null])) : null, region.border !== void 0 || region.fill !== void 0 ? raw(el("a:tcStyle", null, [region.border !== void 0 ? raw(genXmlTableStyleBorders(region.border)) : null, region.fill !== void 0 ? raw(el("a:fill", null, raw(genXmlColorSelection(region.fill)))) : null])) : null];
9650
- return el(`a:${name}`, null, children);
9651
- }
9652
- /**
9653
- * Build the `tcBdr` border block for a custom table style region.
9654
- * A single `BorderProps` styles all four sides plus the interior grid lines; a
9655
- * TRBL array styles only the four outer sides. Sides are emitted in schema order.
9656
- * @param {BorderProps | BorderProps[]} border - border definition
9657
- * @return {string} XML
9658
- */
9659
- function genXmlTableStyleBorders(border) {
9660
- let sides;
9661
- if (Array.isArray(border)) {
9662
- const [top, right, bottom, left] = border;
9663
- sides = [
9664
- ["left", left],
9665
- ["right", right],
9666
- ["top", top],
9667
- ["bottom", bottom]
9668
- ];
9669
- } else sides = [
9670
- ["left", border],
9671
- ["right", border],
9672
- ["top", border],
9673
- ["bottom", border],
9674
- ["insideH", border],
9675
- ["insideV", border]
9676
- ];
9677
- return el("a:tcBdr", null, sides.map(([side, b]) => {
9678
- if (!b) return null;
9679
- if (b.type === "none") return raw(el(`a:${side}`, null, raw(el("a:ln", null, raw(voidEl("a:noFill"))))));
9680
- return raw(el(`a:${side}`, null, raw(el("a:ln", {
9681
- w: lineWidthToEmu(resolveBorderWidth(b, 1)),
9682
- cap: "flat",
9683
- cmpd: "sng",
9684
- algn: "ctr"
9685
- }, [raw(genXmlColorSelection({
9686
- color: b.color ?? "666666",
9687
- transparency: b.transparency
9688
- })), raw(voidEl("a:prstDash", { val: b.type === "dash" ? "sysDash" : "solid" }))]))));
9689
- }));
10297
+ });
9690
10298
  }
9691
10299
  //#endregion
9692
10300
  //#region src/gen/slide/comments.ts
@@ -10280,18 +10888,14 @@ function createChartMediaRels(slide, zip, chartPromises) {
10280
10888
  async function buildPackageParts(source, props) {
10281
10889
  const pres = source.presentation;
10282
10890
  const arrChartPromises = [];
10283
- let arrMediaPromises = [];
10284
10891
  const zip = new ZipWriter();
10285
10892
  registerTransitionSounds(pres.slides);
10286
10893
  const onMediaError = props.onMediaError ?? "throw";
10287
- pres.slides.forEach((slide) => {
10288
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(slide, source.runtime, onMediaError));
10289
- });
10290
- pres.slideLayouts.forEach((layout) => {
10291
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(layout, source.runtime, onMediaError));
10292
- });
10293
- arrMediaPromises = arrMediaPromises.concat(encodeSlideMediaRels(pres.masterSlide, source.runtime, onMediaError));
10294
- return await Promise.all(arrMediaPromises).then(async () => {
10894
+ return await encodeMediaForTargets([
10895
+ ...pres.slides,
10896
+ ...pres.slideLayouts,
10897
+ pres.masterSlide
10898
+ ], source.runtime, onMediaError).then(async () => {
10295
10899
  const canonicalMediaTargets = /* @__PURE__ */ new Map();
10296
10900
  for (const target of [
10297
10901
  ...pres.slides,
@@ -10317,10 +10921,7 @@ async function buildPackageParts(source, props) {
10317
10921
  rel.fileName = `${chartBase}.xml`;
10318
10922
  rel.Target = `/ppt/charts/${chartBase}.xml`;
10319
10923
  }
10320
- pres.slides.forEach((slide) => {
10321
- if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
10322
- });
10323
- applyMeasuredFit(pres.slides, source.fontMetrics);
10924
+ bakeSlideContent(pres.slides, source.fontMetrics);
10324
10925
  const hasCustomProps = source.customProperties.length > 0;
10325
10926
  zip.add("[Content_Types].xml", makeXmlContTypes(pres.slides, pres.slideLayouts, pres.masterSlide, hasCustomProps, pres.embeddedFonts));
10326
10927
  zip.add("_rels/.rels", makeXmlRootRels(hasCustomProps));
@@ -10333,7 +10934,7 @@ async function buildPackageParts(source, props) {
10333
10934
  zip.add("ppt/theme/theme2.xml", makeXmlTheme(pres));
10334
10935
  zip.add("ppt/presentation.xml", makeXmlPresentation(pres));
10335
10936
  zip.add("ppt/presProps.xml", makeXmlPresProps());
10336
- zip.add("ppt/tableStyles.xml", makeXmlTableStyles(source.tableStyles));
10937
+ zip.add("ppt/tableStyles.xml", makeXmlTableStyles());
10337
10938
  zip.add("ppt/viewProps.xml", makeXmlViewProps());
10338
10939
  pres.slideLayouts.forEach((layout, idx) => {
10339
10940
  zip.add(`ppt/slideLayouts/slideLayout${idx + 1}.xml`, makeXmlLayout(layout));
@@ -10391,60 +10992,20 @@ async function writePackage(source, props) {
10391
10992
  //#endregion
10392
10993
  //#region src/presentation.ts
10393
10994
  /**
10394
- * :: presentation.ts ::
10395
- *
10396
- * TypeScript/ESM library that creates PowerPoint (pptx) presentations
10397
- * https://github.com/gitbrent/PptxGenJS
10398
- *
10399
- * This framework is released under the MIT Public License (MIT)
10995
+ * ts-pptx: the presentation core
10400
10996
  *
10401
- * PptxGenJS (C) 2015-present Brent Ely -- https://github.com/gitbrent
10997
+ * Home of {@link PresentationCore}, the class every entry point subclasses with a runtime
10998
+ * adapter (`index.ts`, `node.ts`, `browser.ts`). See the class doc below for the layout of
10999
+ * its body and where package assembly actually happens.
10402
11000
  *
10403
- * Some code derived from the OfficeGen project:
10404
- * github.com/Ziv-Barber/officegen/ (Copyright 2013 Ziv Barber)
11001
+ * Units: this library takes user coordinates in inches and converts to EMU on output — see
11002
+ * `units.ts` for the conversions and `STANDARD_LAYOUTS` for the built-in slide sizes.
10405
11003
  *
10406
- * Permission is hereby granted, free of charge, to any person obtaining a copy
10407
- * of this software and associated documentation files (the "Software"), to deal
10408
- * in the Software without restriction, including without limitation the rights
10409
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10410
- * copies of the Software, and to permit persons to whom the Software is
10411
- * furnished to do so, subject to the following conditions:
10412
- *
10413
- * The above copyright notice and this permission notice shall be included in all
10414
- * copies or substantial portions of the Software.
10415
- *
10416
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10417
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10418
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
10419
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
10420
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
10421
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
10422
- * SOFTWARE.
11004
+ * Derived from PptxGenJS (C) 2015-present Brent Ely (MIT), which in turn took some code from
11005
+ * the OfficeGen project (Copyright 2013 Ziv Barber). This project is likewise MIT; the full
11006
+ * license text is in `LICENSE` at the repository root.
10423
11007
  */
10424
- /**
10425
- * Units of Measure used in PowerPoint documents
10426
- *
10427
- * PowerPoint (DrawingML) positions and sizes shapes in `EMU`; font sizing is in points.
10428
- * This library takes user coordinates in inches and converts to EMU on output.
10429
- * - 914400 EMUs is 1 inch
10430
- * - 12700 EMUs is 1 point
10431
- * - 72 points is 1 inch
10432
- *
10433
- * @see https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
10434
- */
10435
- /**
10436
- * Object Layouts
10437
- *
10438
- * - 16x9 (10" x 5.625")
10439
- * - 16x10 (10" x 6.25")
10440
- * - 4x3 (10" x 7.5")
10441
- * - Wide (13.33" x 7.5")
10442
- * - [custom] (any size)
10443
- *
10444
- * @see https://docs.microsoft.com/en-us/office/open-xml/structure-of-a-presentationml-document
10445
- * @see https://docs.microsoft.com/en-us/previous-versions/office/developer/office-2010/hh273476(v=office.14)
10446
- */
10447
- const VERSION = "1.0.0";
11008
+ const VERSION = "3.0.0";
10448
11009
  function standardLayoutToPresLayout(layout) {
10449
11010
  return {
10450
11011
  name: layout.name,
@@ -10600,7 +11161,6 @@ var PresentationCore = class {
10600
11161
  }
10601
11162
  /** custom document properties stored in docProps/custom.xml */
10602
11163
  _customProperties;
10603
- _tableStyles;
10604
11164
  /**
10605
11165
  * slide layout definition objects, used for generating slide layout files.
10606
11166
  * `protected` (not `private`) so the browser entry subclass can resolve
@@ -10679,7 +11239,6 @@ var PresentationCore = class {
10679
11239
  this._slides = [];
10680
11240
  this._sections = [];
10681
11241
  this._customProperties = [];
10682
- this._tableStyles = [];
10683
11242
  this._masterSlide = {
10684
11243
  addChart: null,
10685
11244
  addComment: null,
@@ -10746,7 +11305,6 @@ var PresentationCore = class {
10746
11305
  runtime: this._runtime,
10747
11306
  presentation: this.internalPresentation,
10748
11307
  customProperties: this._customProperties,
10749
- tableStyles: this._tableStyles,
10750
11308
  fontMetrics: this._fontMetrics
10751
11309
  };
10752
11310
  }
@@ -10766,15 +11324,8 @@ var PresentationCore = class {
10766
11324
  */
10767
11325
  extractSlides = async (opts = {}) => {
10768
11326
  const onMediaError = opts.onMediaError ?? "throw";
10769
- let mediaPromises = [];
10770
- this._slides.forEach((slide) => {
10771
- mediaPromises = mediaPromises.concat(encodeSlideMediaRels(slide, this._runtime, onMediaError));
10772
- });
10773
- await Promise.all(mediaPromises);
10774
- this._slides.forEach((slide) => {
10775
- if (slide._slideLayout) addPlaceholdersToSlideLayouts(slide);
10776
- });
10777
- applyMeasuredFit(this._slides, this._fontMetrics);
11327
+ await encodeMediaForTargets(this._slides, this._runtime, onMediaError);
11328
+ bakeSlideContent(this._slides, this._fontMetrics);
10778
11329
  const slides = this._slides.map((slide) => {
10779
11330
  const relByRid = new Map(slide._relsMedia.map((rel) => [rel.rId, rel]));
10780
11331
  const avPreviewRids = /* @__PURE__ */ new Set();
@@ -11193,32 +11744,139 @@ var PresentationCore = class {
11193
11744
  if (propsClone.background) addBackgroundDefinition(propsClone.background, newLayout);
11194
11745
  if (newLayout._slideNumberProps && !this._masterSlide._slideNumberProps) this._masterSlide._slideNumberProps = newLayout._slideNumberProps;
11195
11746
  }
11196
- /**
11197
- * Register a reusable custom table style and return its GUID.
11198
- * The style is written to `ppt/tableStyles.xml` and is editable in PowerPoint's
11199
- * Table Styles gallery. Pass the returned GUID as `TableProps.tableStyle`, and use
11200
- * the `has*` flags (`hasHeader`, `hasBandedRows`, …) to activate the matching regions.
11201
- * @param {TableStyleProps} props - custom table style definition (requires `name`)
11202
- * @returns {string} braced GUID to use as `tableStyle`
11203
- * @example
11204
- * const brand = pptx.defineTableStyle({
11205
- * name: 'Brand Banded',
11206
- * firstRow: { fill:'1A2B3C', color:'FFFFFF', bold:true },
11207
- * band1H: { fill:'EAF1F8' },
11208
- * })
11209
- * slide.addTable(rows, { tableStyle: brand, hasHeader:true, hasBandedRows:true })
11210
- */
11211
- defineTableStyle(props) {
11212
- if (!props || typeof props !== "object") throw new InvalidOptionError("table-style/missing-argument", "defineTableStyle() requires a `{ name, ... }` object argument");
11213
- if (!props.name || typeof props.name !== "string") throw new InvalidOptionError("table-style/missing-name", "defineTableStyle() requires a non-empty `name`");
11214
- const guid = `{${getUuid("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx").toUpperCase()}}`;
11215
- this._tableStyles.push({
11216
- guid,
11217
- def: props
11218
- });
11219
- return guid;
11747
+ };
11748
+ //#endregion
11749
+ //#region src/clip.ts
11750
+ const HALF_DISC = {
11751
+ deep: {
11752
+ topEdgeX: .31788,
11753
+ botEdgeX: .31788,
11754
+ tipTopX: .30061,
11755
+ tipTopY: .01078,
11756
+ tipBotX: .30061,
11757
+ tipBotY: .98922,
11758
+ apexY: .5,
11759
+ c1b: {
11760
+ x: .11594,
11761
+ y: .86833
11762
+ },
11763
+ c2b: {
11764
+ x: 0,
11765
+ y: .69392
11766
+ },
11767
+ c1t: {
11768
+ x: 0,
11769
+ y: .30609
11770
+ },
11771
+ c2t: {
11772
+ x: .11594,
11773
+ y: .13167
11774
+ }
11775
+ },
11776
+ shallow: {
11777
+ topEdgeX: .12611,
11778
+ botEdgeX: .11128,
11779
+ tipTopX: .09976,
11780
+ tipTopY: .05448,
11781
+ tipBotX: .09976,
11782
+ tipBotY: .97617,
11783
+ apexY: .51533,
11784
+ c1b: {
11785
+ x: .03522,
11786
+ y: .83247
11787
+ },
11788
+ c2b: {
11789
+ x: 0,
11790
+ y: .67725
11791
+ },
11792
+ c1t: {
11793
+ x: 0,
11794
+ y: .3534
11795
+ },
11796
+ c2t: {
11797
+ x: .03522,
11798
+ y: .19818
11799
+ }
11220
11800
  }
11221
11801
  };
11802
+ /**
11803
+ * Half-disc clip path for a `w × h` box. Authored flat-right; `flat: 'left'` mirrors `x → 1 - x`.
11804
+ */
11805
+ function halfDisc(w, h, flat, preset) {
11806
+ const s = HALF_DISC[preset];
11807
+ const fx = flat === "left" ? (x) => (1 - x) * w : (x) => x * w;
11808
+ const fy = (y) => y * h;
11809
+ return [
11810
+ {
11811
+ x: fx(s.topEdgeX),
11812
+ y: fy(0),
11813
+ moveTo: true
11814
+ },
11815
+ {
11816
+ x: fx(1),
11817
+ y: fy(0)
11818
+ },
11819
+ {
11820
+ x: fx(1),
11821
+ y: fy(1)
11822
+ },
11823
+ {
11824
+ x: fx(s.botEdgeX),
11825
+ y: fy(1)
11826
+ },
11827
+ {
11828
+ x: fx(s.tipBotX),
11829
+ y: fy(s.tipBotY)
11830
+ },
11831
+ {
11832
+ x: fx(0),
11833
+ y: fy(s.apexY),
11834
+ curve: {
11835
+ type: "cubic",
11836
+ x1: fx(s.c1b.x),
11837
+ y1: fy(s.c1b.y),
11838
+ x2: fx(s.c2b.x),
11839
+ y2: fy(s.c2b.y)
11840
+ }
11841
+ },
11842
+ {
11843
+ x: fx(s.tipTopX),
11844
+ y: fy(s.tipTopY),
11845
+ curve: {
11846
+ type: "cubic",
11847
+ x1: fx(s.c1t.x),
11848
+ y1: fy(s.c1t.y),
11849
+ x2: fx(s.c2t.x),
11850
+ y2: fy(s.c2t.y)
11851
+ }
11852
+ },
11853
+ { close: true }
11854
+ ];
11855
+ }
11856
+ /**
11857
+ * Resolve a {@link ClipShape} to a freeform `points` path filling a `w × h` box, ready to hand
11858
+ * to `addImage({ points })`.
11859
+ *
11860
+ * `w`/`h` are the image box's size **in inches** and must match the `w`/`h` the picture is drawn
11861
+ * at — the path is emitted in the box's own coordinate space, not normalized (see the module
11862
+ * note above).
11863
+ * @param {ClipShape} shape - the named silhouette and its options
11864
+ * @param {number} w - image box width (inches)
11865
+ * @param {number} h - image box height (inches)
11866
+ * @returns {GeometryPoint[]} the freeform path, for `addImage({ points })`
11867
+ * @example
11868
+ * const w = 5.22, h = 7.5
11869
+ * slide.addImage({
11870
+ * path: 'cover.jpg', x: 0, y: 0, w, h,
11871
+ * points: clipPath({ kind: 'half-disc', flat: 'right' }, w, h),
11872
+ * sizing: { type: 'cover' }, // center-crop the photo into the "D"
11873
+ * })
11874
+ */
11875
+ function clipPath(shape, w, h) {
11876
+ switch (shape.kind) {
11877
+ case "half-disc": return halfDisc(w, h, shape.flat, shape.preset ?? "deep");
11878
+ }
11879
+ }
11222
11880
  //#endregion
11223
11881
  //#region src/types/text.ts
11224
11882
  /** Factory for a single inline text run. Prevents `as never` casts when building mixed-style run arrays. */
@@ -11233,6 +11891,6 @@ function textRuns(runs) {
11233
11891
  return runs;
11234
11892
  }
11235
11893
  //#endregion
11236
- export { textRuns as n, PresentationCore as r, textRun as t };
11894
+ export { PresentationCore as i, textRuns as n, clipPath as r, textRun as t };
11237
11895
 
11238
- //# sourceMappingURL=text-WRUbOy2r.js.map
11896
+ //# sourceMappingURL=text-120VFSDm.js.map