@extend-ai/react-pptx 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -273,6 +273,8 @@ interface ChartNode extends BaseSlideNode {
273
273
  * fields above remain available for search and fallbacks.
274
274
  */
275
275
  chartXml?: string;
276
+ /** Chart artwork at the dimensions of the slide frame. */
277
+ chartSvg?: string;
276
278
  /** Companion Microsoft chart style part (`style*.xml`), when present. */
277
279
  chartStyleXml?: string;
278
280
  /** Companion Microsoft chart color style part (`colors*.xml`), when present. */
package/dist/index.js CHANGED
@@ -42,6 +42,92 @@ function throwIfAborted(signal) {
42
42
  }
43
43
  }
44
44
 
45
+ // ../../node_modules/.pnpm/@extend-ai+office-fonts@https+++github.com+extend-hq+rust-office+releases+download+office-fon_7r4y6vimjxvcmpreabk5ezevri/node_modules/@extend-ai/office-fonts/families.js
46
+ var families = [
47
+ { id: "carlito", family: "Carlito", original: "Calibri", load: () => import('./carlito-data-LDQ4YMUR.js') },
48
+ { id: "arimo", family: "Arimo", original: "Arial", load: () => import('./arimo-data-4NQ7UA57.js') },
49
+ { id: "tinos", family: "Tinos", original: "Times New Roman", load: () => import('./tinos-data-IPPHZ4Y6.js') },
50
+ { id: "cousine", family: "Cousine", original: "Courier New", load: () => import('./cousine-data-IPO23HJ5.js') },
51
+ { id: "intos", family: "Intos", original: "Aptos", load: () => import('./intos-data-55TEPAXO.js') },
52
+ { id: "intos-narrow", family: "Intos Narrow", original: "Aptos Narrow", load: () => import('./intos-narrow-data-FHJNOFRV.js') }
53
+ ];
54
+
55
+ // ../../node_modules/.pnpm/@extend-ai+office-fonts@https+++github.com+extend-hq+rust-office+releases+download+office-fon_7r4y6vimjxvcmpreabk5ezevri/node_modules/@extend-ai/office-fonts/runtime.js
56
+ var registry = /* @__PURE__ */ Symbol.for("@extend-ai/office-fonts/loads/v2");
57
+ var loads = globalThis[registry] ??= /* @__PURE__ */ new WeakMap();
58
+ var normalize = (family) => family.trim().replace(/^["']|["']$/g, "").toLowerCase();
59
+ var primary = (family) => family.split(",")[0];
60
+ var lookup = new Map(families.flatMap((family) => [family.original, family.family].map((name) => [normalize(name), family])));
61
+ var variants = [
62
+ { style: "normal", weight: "400", suffix: "" },
63
+ { style: "normal", weight: "700", suffix: " Bold" },
64
+ { style: "italic", weight: "400", suffix: " Italic" },
65
+ { style: "italic", weight: "700", suffix: " Bold Italic" }
66
+ ];
67
+ function matches(face, family, variant) {
68
+ const weight = face.weight === "normal" ? "400" : face.weight === "bold" ? "700" : face.weight;
69
+ const range = weight.split(/\s+/).map(Number);
70
+ return normalize(face.family) === normalize(family) && face.style === variant.style && Number(variant.weight) >= range[0] && Number(variant.weight) <= (range[1] ?? range[0]);
71
+ }
72
+ async function loadOfficeFonts(target = globalThis.document, requestedFamilies = ["Calibri"]) {
73
+ const FontFaceClass = target?.defaultView?.FontFace ?? globalThis.FontFace;
74
+ if (!target?.fonts || !FontFaceClass) return;
75
+ const fonts = target.fonts;
76
+ let entries = loads.get(fonts);
77
+ if (!entries) loads.set(fonts, entries = /* @__PURE__ */ new Map());
78
+ const selected = new Set([...requestedFamilies].map((name) => normalize(primary(name))));
79
+ await Promise.all([...selected].map(async (key) => {
80
+ const definition = lookup.get(key);
81
+ if (!definition) return;
82
+ const previous = entries.get(key);
83
+ if (previous && (!previous.ready || previous.faces.every((face) => fonts.has(face)))) return previous.promise;
84
+ const family = key === normalize(definition.original) ? definition.original : definition.family;
85
+ const state = { ready: false, faces: [], promise: void 0 };
86
+ state.promise = (async () => {
87
+ const added = [];
88
+ try {
89
+ const results = await Promise.allSettled(variants.map(async (variant, index) => {
90
+ const existing = [...fonts].filter((face2) => matches(face2, family, variant) && face2.status !== "error");
91
+ if (existing.length) {
92
+ const loaded = await Promise.allSettled(existing.map((face2) => face2.load()));
93
+ const available = loaded.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
94
+ if (available.length) return available;
95
+ }
96
+ let face;
97
+ if (family === definition.original) {
98
+ try {
99
+ face = await new FontFaceClass(family, `local("${family}${variant.suffix}")`, variant).load();
100
+ } catch {
101
+ face = void 0;
102
+ }
103
+ }
104
+ if (!face) {
105
+ const { faces: assets } = await definition.load();
106
+ face = new FontFaceClass(family, `url("${assets[index].source}")`, variant);
107
+ fonts.add(face);
108
+ added.push(face);
109
+ await face.load();
110
+ } else {
111
+ fonts.add(face);
112
+ added.push(face);
113
+ }
114
+ return [face];
115
+ }));
116
+ const failure = results.find((result) => result.status === "rejected");
117
+ if (failure) throw failure.reason;
118
+ state.faces = results.flatMap((result) => result.value);
119
+ state.ready = true;
120
+ } catch (error) {
121
+ for (const face of added) fonts.delete(face);
122
+ entries.delete(key);
123
+ throw error;
124
+ }
125
+ })();
126
+ entries.set(key, state);
127
+ return state.promise;
128
+ }));
129
+ }
130
+
45
131
  // src/fonts.ts
46
132
  var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
47
133
  "serif",
@@ -58,17 +144,18 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
58
144
  "fangsong"
59
145
  ]);
60
146
  var OFFICE_FONT_FALLBACKS = {
61
- aptos: ["Aptos", "Segoe UI", "Arial", "sans-serif"],
147
+ aptos: ["Aptos", "Intos", "Segoe UI", "Arial", "sans-serif"],
148
+ "aptos narrow": ["Aptos Narrow", "Intos Narrow", "Arial", "sans-serif"],
62
149
  "aptos display": ["Aptos Display", "Segoe UI", "Arial", "sans-serif"],
63
150
  calibri: ["Calibri", "Carlito", "Arial", "sans-serif"],
64
151
  "calibri light": ["Calibri Light", "Carlito", "Arial", "sans-serif"],
65
152
  cambria: ["Cambria", "Caladea", "Georgia", "serif"],
66
153
  "cambria math": ["Cambria Math", "STIX Two Math", "STIXGeneral", "math"],
67
- arial: ["Arial", "Liberation Sans", "Helvetica", "sans-serif"],
154
+ arial: ["Arial", "Arimo", "Liberation Sans", "Helvetica", "sans-serif"],
68
155
  "arial narrow": ["Arial Narrow", "Liberation Sans Narrow", "Arial", "sans-serif"],
69
156
  "arial unicode ms": ["Arial Unicode MS", "Noto Sans", "Segoe UI Symbol", "sans-serif"],
70
- "times new roman": ["Times New Roman", "Liberation Serif", "Times", "serif"],
71
- "courier new": ["Courier New", "Liberation Mono", "Courier", "monospace"],
157
+ "times new roman": ["Times New Roman", "Tinos", "Liberation Serif", "Times", "serif"],
158
+ "courier new": ["Courier New", "Cousine", "Liberation Mono", "Courier", "monospace"],
72
159
  consolas: ["Consolas", "Cascadia Mono", "Liberation Mono", "monospace"],
73
160
  "century gothic": ["Century Gothic", "Avenir Next", "Futura", "Arial", "sans-serif"],
74
161
  "franklin gothic medium": ["Franklin Gothic Medium", "Arial", "sans-serif"],
@@ -142,9 +229,9 @@ function serializeFamily(family) {
142
229
  if (GENERIC_FAMILIES.has(family.toLowerCase())) return family;
143
230
  return `"${family.replaceAll('"', '\\"')}"`;
144
231
  }
145
- function uniqueFamilies(families) {
232
+ function uniqueFamilies(families2) {
146
233
  const seen = /* @__PURE__ */ new Set();
147
- return families.filter((family) => {
234
+ return families2.filter((family) => {
148
235
  const key = normalizeFamily(family).toLowerCase();
149
236
  if (!key || seen.has(key)) return false;
150
237
  seen.add(key);
@@ -153,14 +240,14 @@ function uniqueFamilies(families) {
153
240
  }
154
241
  function resolvePptxFontFamily(fontFamily2, options = {}) {
155
242
  const requested = parseFamilyList(fontFamily2);
156
- const primary = requested[0] ?? "";
157
- const configured = options.fallbacks?.[primary] ?? options.fallbacks?.[primary.toLowerCase()];
243
+ const primary2 = requested[0] ?? "";
244
+ const configured = options.fallbacks?.[primary2] ?? options.fallbacks?.[primary2.toLowerCase()];
158
245
  const configuredList = typeof configured === "string" ? [configured] : configured ?? [];
159
- const office = options.useOfficeFallbacks === false ? [] : OFFICE_FONT_FALLBACKS[primary.toLowerCase()] ?? [];
160
- const lower = primary.toLowerCase();
246
+ const office = options.useOfficeFallbacks === false ? [] : OFFICE_FONT_FALLBACKS[primary2.toLowerCase()] ?? [];
247
+ const lower = primary2.toLowerCase();
161
248
  const generic = /serif|times|cambria|mincho|ming|song|batang|david|garamond|palatino/.test(lower) ? ["Noto Serif", "Noto Serif CJK SC", "serif"] : [...UNIVERSAL_SANS_FALLBACKS];
162
249
  return uniqueFamilies([
163
- ...primary ? [primary] : [],
250
+ ...primary2 ? [primary2] : [],
164
251
  ...configuredList,
165
252
  ...office,
166
253
  ...requested.slice(1),
@@ -269,6 +356,27 @@ var PptxFontManager = class {
269
356
  message: `The supplied font \u201C${family}\u201D could not be loaded; a compatible fallback will be used.`
270
357
  });
271
358
  });
359
+ if (this.options.useOfficeFallbacks !== false) {
360
+ const families2 = /* @__PURE__ */ new Set(["Calibri"]);
361
+ const seen = /* @__PURE__ */ new Set();
362
+ const collect = (value) => {
363
+ if (!value || typeof value !== "object" || seen.has(value)) return;
364
+ seen.add(value);
365
+ for (const [key, child] of Object.entries(value)) {
366
+ if (/fontFamily$/i.test(key) && typeof child === "string") families2.add(child);
367
+ else if (child && typeof child === "object") collect(child);
368
+ }
369
+ };
370
+ collect(presentation.slides);
371
+ collect(presentation.masters);
372
+ collect(presentation.layouts);
373
+ for (const theme of presentation.themes) {
374
+ for (const family of [...Object.values(theme.majorFonts ?? {}), ...Object.values(theme.minorFonts ?? {})]) {
375
+ if (family) families2.add(family);
376
+ }
377
+ }
378
+ await loadOfficeFonts(document, families2).catch(() => void 0);
379
+ }
272
380
  if (document.fonts && this.options.waitForFonts !== false) {
273
381
  await timeout(
274
382
  Promise.resolve(document.fonts.ready).then(() => void 0),
@@ -278,28 +386,28 @@ var PptxFontManager = class {
278
386
  this.options.onFontsReady?.();
279
387
  }
280
388
  applyTo(root, slideIndex) {
281
- const elements = [
389
+ const elements2 = [
282
390
  root,
283
391
  ...root.querySelectorAll('[style*="font-family"], [font-family]')
284
392
  ];
285
- for (const element of elements) {
393
+ for (const element of elements2) {
286
394
  const raw = (element instanceof HTMLElement || element instanceof SVGElement ? element.style.fontFamily : "") || element.getAttribute("font-family");
287
395
  if (!raw) continue;
288
- const primary = parseFamilyList(raw)[0];
289
- if (!primary || primary.startsWith("+") || GENERIC_FAMILIES.has(primary.toLowerCase()))
396
+ const primary2 = parseFamilyList(raw)[0];
397
+ if (!primary2 || primary2.startsWith("+") || GENERIC_FAMILIES.has(primary2.toLowerCase()))
290
398
  continue;
291
399
  if (element instanceof HTMLElement || element instanceof SVGElement) {
292
400
  element.style.fontFamily = resolvePptxFontFamily(raw, this.options);
293
401
  }
294
- if (this.options.reportMissingFonts === false || this.loadedFamilies.has(primary.toLowerCase()) || browserHasFont(primary)) {
402
+ if (this.options.reportMissingFonts === false || this.loadedFamilies.has(primary2.toLowerCase()) || browserHasFont(primary2)) {
295
403
  continue;
296
404
  }
297
405
  this.warn({
298
406
  code: "missing-font",
299
407
  severity: "warning",
300
- feature: primary,
408
+ feature: primary2,
301
409
  ...slideIndex === void 0 ? {} : { slideIndex },
302
- message: `Font \u201C${primary}\u201D is not available in this browser; the configured fallback stack is being used.`
410
+ message: `Font \u201C${primary2}\u201D is not available in this browser; the configured fallback stack is being used.`
303
411
  });
304
412
  }
305
413
  }
@@ -3377,11 +3485,11 @@ function getCategoryLabels(chart) {
3377
3485
  return series.categories.every((value) => normalizeCategoryLabel(value).length === 0);
3378
3486
  }) || !hasAnyExplicitCategory;
3379
3487
  return Array.from({ length: categoryCount }, (_, categoryIndex) => {
3380
- const primary = primaryCategories[categoryIndex];
3381
- if (primary != null) {
3382
- const normalizedPrimary = normalizeCategoryLabel(primary);
3488
+ const primary2 = primaryCategories[categoryIndex];
3489
+ if (primary2 != null) {
3490
+ const normalizedPrimary = normalizeCategoryLabel(primary2);
3383
3491
  if (normalizedPrimary.length > 0) {
3384
- return formatCategoryLabel(chart, primary, normalizedPrimary);
3492
+ return formatCategoryLabel(chart, primary2, normalizedPrimary);
3385
3493
  }
3386
3494
  }
3387
3495
  const fallback = chart.series.map((series) => series.categories[categoryIndex]).find((value) => normalizeCategoryLabel(value).length > 0);
@@ -8457,6 +8565,64 @@ var MemoChartSvg = React2.memo(function MemoChartSvg2({
8457
8565
  );
8458
8566
  }, (prev, next) => prev.chart === next.chart && prev.onChartElementDoubleClick === next.onChartElementDoubleClick && prev.onChartElementPointerDown === next.onChartElementPointerDown && prev.palette === next.palette && prev.rect.height === next.rect.height && prev.rect.width === next.rect.width && prev.rect.left === next.rect.left && prev.rect.top === next.rect.top && prev.selectedChartElement === next.selectedChartElement);
8459
8567
 
8568
+ // src/charts/shared-chart-svg.ts
8569
+ var svgNamespace = "http://www.w3.org/2000/svg";
8570
+ var elements = new Set(
8571
+ "svg g rect circle ellipse path polygon polyline line text tspan defs clipPath linearGradient radialGradient stop pattern filter feDropShadow".split(
8572
+ " "
8573
+ )
8574
+ );
8575
+ var attributes = new Set(
8576
+ "width height viewBox x y x1 y1 x2 y2 cx cy rx ry r d points transform id dx dy fill fill-opacity fill-rule stroke stroke-opacity stroke-width stroke-dasharray stroke-linecap stroke-linejoin clip-path filter opacity font-family font-size font-weight font-style text-anchor textLength lengthAdjust offset stop-color stop-opacity gradientUnits gradientTransform fx fy fr patternUnits patternContentUnits shape-rendering stdDeviation flood-color flood-opacity overflow data-chart-kind data-chart-series data-chart-point data-chart-x data-chart-y data-docx-series data-docx-point".split(" ")
8577
+ );
8578
+ var paints = /* @__PURE__ */ new Set(["fill", "stroke", "filter", "clip-path", "stop-color", "flood-color"]);
8579
+ var sequence = 0;
8580
+ function mountSharedChart(container, markup, label) {
8581
+ if (markup.includes("<!") || markup.includes("<?")) return false;
8582
+ const svg = new DOMParser().parseFromString(markup, "image/svg+xml").documentElement;
8583
+ if (svg.localName !== "svg") return false;
8584
+ const prefix = `rpv-chart-${++sequence}-`;
8585
+ for (const element of [svg, ...Array.from(svg.querySelectorAll("*"))]) {
8586
+ if (element.namespaceURI !== svgNamespace || !elements.has(element.localName)) return false;
8587
+ for (const attribute of Array.from(element.attributes)) {
8588
+ const { name, value } = attribute;
8589
+ if (name === "xmlns" && element === svg && value === svgNamespace) continue;
8590
+ if (attribute.namespaceURI || !attributes.has(name)) return false;
8591
+ if (name === "id") {
8592
+ if (!/^[a-zA-Z_][\w.-]*$/.test(value)) return false;
8593
+ element.id = prefix + value;
8594
+ } else if (paints.has(name)) {
8595
+ const reference = /^url\(#([\w.-]+)\)$/.exec(value);
8596
+ if (reference) element.setAttribute(name, `url(#${prefix}${reference[1]})`);
8597
+ else if (!/^(?:#[\da-fA-F]{3,8}|[a-zA-Z]+|rgba?\([\d.% ,]+\))$/.test(value)) return false;
8598
+ }
8599
+ }
8600
+ const series = element.getAttribute("data-chart-series");
8601
+ if (series === null) continue;
8602
+ element.setAttribute("data-xlsx-chart-series-index", series);
8603
+ const point = element.getAttribute("data-chart-point");
8604
+ if (point !== null) element.setAttribute("data-xlsx-chart-point-index", point);
8605
+ const kind = element.getAttribute("data-chart-kind");
8606
+ if (kind === "bar") element.setAttribute("data-xlsx-chart-selection-mode", "seriesFirst");
8607
+ if (kind === "legendEntry") element.setAttribute("data-xlsx-chart-element-kind", "legendEntry");
8608
+ if (kind === "point") {
8609
+ const hit = svg.ownerDocument.createElementNS(svgNamespace, "circle");
8610
+ hit.setAttribute("cx", element.getAttribute("data-chart-x") ?? "0");
8611
+ hit.setAttribute("cy", element.getAttribute("data-chart-y") ?? "0");
8612
+ hit.setAttribute("r", "5");
8613
+ hit.setAttribute("fill", "transparent");
8614
+ element.append(hit);
8615
+ }
8616
+ element.setAttribute("cursor", "pointer");
8617
+ }
8618
+ const view = container.ownerDocument.importNode(svg, true);
8619
+ view.style.cssText = "width:100%;height:100%;display:block;";
8620
+ view.setAttribute("role", "img");
8621
+ view.setAttribute("aria-label", label);
8622
+ container.replaceChildren(view);
8623
+ return true;
8624
+ }
8625
+
8460
8626
  // src/charts/chart-host.tsx
8461
8627
  var CHART_HOST_PALETTE = {
8462
8628
  border: "#e4e4e7",
@@ -8469,6 +8635,9 @@ function chartNeedsRegionMapAtlas(chart) {
8469
8635
  }
8470
8636
  function renderChartInto(container, node, theme) {
8471
8637
  try {
8638
+ if (node.chartSvg && mountSharedChart(container, node.chartSvg, node.title ?? node.name ?? "Chart")) {
8639
+ return true;
8640
+ }
8472
8641
  const themePalette = buildThemePaletteFromPresentationTheme(theme);
8473
8642
  const chart = buildPptxChart(node, themePalette);
8474
8643
  const render = () => {
@@ -9446,10 +9615,10 @@ function runFontFamily(run) {
9446
9615
  run.complexScriptFontFamily,
9447
9616
  run.symbolFontFamily
9448
9617
  ];
9449
- const families = ordered.filter(
9618
+ const families2 = ordered.filter(
9450
9619
  (family, index) => Boolean(family) && ordered.indexOf(family) === index
9451
9620
  );
9452
- return families.length > 0 ? families.join(", ") : void 0;
9621
+ return families2.length > 0 ? families2.join(", ") : void 0;
9453
9622
  }
9454
9623
  function createTextRunElement(run, paragraph, fontScale) {
9455
9624
  const href = safeHyperlink(run.hyperlink);
@@ -12596,12 +12765,12 @@ async function inlineResources(root, signal) {
12596
12765
  }
12597
12766
  return pending;
12598
12767
  };
12599
- const elements = [root, ...root.querySelectorAll("*")];
12768
+ const elements2 = [root, ...root.querySelectorAll("*")];
12600
12769
  await Promise.all(
12601
- elements.flatMap((element) => {
12770
+ elements2.flatMap((element) => {
12602
12771
  const attributeNames = ["src", "poster", ...element.localName === "image" ? ["href"] : []];
12603
- const attributes = attributeNames.map((name) => ({ name, value: element.getAttribute(name) })).filter((entry) => Boolean(entry.value));
12604
- const attributeTasks = attributes.map(async ({ name, value }) => {
12772
+ const attributes2 = attributeNames.map((name) => ({ name, value: element.getAttribute(name) })).filter((entry) => Boolean(entry.value));
12773
+ const attributeTasks = attributes2.map(async ({ name, value }) => {
12605
12774
  element.setAttribute(name, await inline(value));
12606
12775
  });
12607
12776
  const styleTasks = Array.from(