@json-to-office/core-pptx 1.0.0 → 1.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../src/core/structure.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAGV,+BAA+B,EAC/B,qBAAqB,EAGtB,MAAM,UAAU,CAAC;AAQlB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmCrD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,+BAA+B,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,qBAAqB,CAwJvB"}
1
+ {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../src/core/structure.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAGV,+BAA+B,EAC/B,qBAAqB,EAItB,MAAM,UAAU,CAAC;AAQlB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmCrD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,+BAA+B,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,qBAAqB,CA8JvB"}
package/dist/index.js CHANGED
@@ -186,6 +186,39 @@ var init_features = __esm({
186
186
  "table-auto-page",
187
187
  /** Native OOXML charts with an embedded workbook. */
188
188
  "charts",
189
+ /*
190
+ * Chart styling, split finer than `charts` itself.
191
+ *
192
+ * `charts` says a backend can draw a chart from data. It says nothing about
193
+ * whether that backend honours the options the author styled it with, and one
194
+ * coarse feature let a renderer accept a chart, draw it, and quietly ignore
195
+ * half of what was asked for — an ignored axis maximum draws a different
196
+ * chart from the authored one and the file says nothing about it.
197
+ *
198
+ * Each name below is required by the compiler *only when the matching prop
199
+ * was authored*, at that prop's own path, so a default never demands a
200
+ * capability and a refusal always names the line that caused it.
201
+ */
202
+ /** `barGapWidthPct`, `barOverlapPct`. */
203
+ "chart-bar-style",
204
+ /** `firstSliceAng`, `holeSize`. */
205
+ "chart-pie-style",
206
+ /** `lineSmooth`, `lineDataSymbol`, `lineSize`, `lineDataSymbolSize`. */
207
+ "chart-line-style",
208
+ /** `radarStyle` — anything but the `standard` a backend may hardcode. */
209
+ "chart-radar-style",
210
+ /** `showValue`, `showPercent`, `showLabel`, `showSerName`, `dataLabelPosition`. */
211
+ "chart-data-labels",
212
+ /** `dataBorder`: an outline on bars, slices and areas. */
213
+ "chart-data-border",
214
+ /** Value-axis bounds and number format: min, max, major unit, format code. */
215
+ "chart-axis-scale",
216
+ /** Hiding an axis or its line. */
217
+ "chart-axis-visibility",
218
+ /** Axis label rotation and grid lines. */
219
+ "chart-axis-style",
220
+ /** Font family, size, weight and colour on any chart text. */
221
+ "chart-text-style",
189
222
  "solid-fills",
190
223
  "gradient-fills",
191
224
  "pattern-fills",
@@ -938,11 +971,6 @@ async function finalizePackage(zip, generatedAt) {
938
971
  await canonicalizeChartIds(zip);
939
972
  await canonicalizePackage(zip, generatedAt);
940
973
  }
941
- async function finalizePackageBuffer(buffer, options = {}) {
942
- const zip = await readPackage(buffer);
943
- await finalizePackage(zip, resolveGeneratedAt(options.generatedAt));
944
- return writePackage(zip);
945
- }
946
974
  function replaceCoreTimestamp(xml, tag, value) {
947
975
  const expression = new RegExp(
948
976
  `(<dcterms:${tag}\\b[^>]*>)[^<]*(</dcterms:${tag}>)`,
@@ -1348,6 +1376,143 @@ var init_pptxgenjs = __esm({
1348
1376
  }
1349
1377
  });
1350
1378
 
1379
+ // src/renderers/office-open/chartParts.ts
1380
+ import JSZip2 from "jszip";
1381
+ import {
1382
+ CHART_WORKBOOK_CONTENT_TYPE,
1383
+ chartWorkbookParts,
1384
+ chartWorkbookRelsXml,
1385
+ matchChartParts,
1386
+ spliceChartXml
1387
+ } from "@json-to-office/shared/rendering";
1388
+ function spliceInput(element) {
1389
+ return {
1390
+ chartType: element.chartType,
1391
+ series: element.series.map((series) => ({
1392
+ ...series.name !== void 0 ? { name: series.name } : {},
1393
+ labels: series.labels ?? [],
1394
+ values: series.values ?? []
1395
+ })),
1396
+ colors: element.options.colors,
1397
+ ...element.options.barGrouping ? { barGrouping: element.options.barGrouping } : {},
1398
+ // Spliced rather than emitted: see `chartChild`, which cannot pass `axes`
1399
+ // without also inventing the axis ids the plot area references.
1400
+ categoryAxis: axisEdits(element.options.categoryAxis),
1401
+ valueAxis: axisEdits(element.options.valueAxis),
1402
+ // Three the backend has nowhere to put: `ChartSeriesCommon` has no line
1403
+ // width, no data-element outline, and `c:radarStyle` is written from a
1404
+ // literal rather than an option.
1405
+ ...element.options.lineSize !== void 0 ? { lineWidthPoints: element.options.lineSize } : {},
1406
+ ...element.options.dataBorder ? {
1407
+ dataBorder: {
1408
+ widthPoints: element.options.dataBorder.widthPoints,
1409
+ color: element.options.dataBorder.color.hex
1410
+ }
1411
+ } : {},
1412
+ ...element.options.radarStyle ? { radarStyle: element.options.radarStyle } : {},
1413
+ // Fonts: `c:txPr` and `a:defRPr` on four different elements, none of which
1414
+ // the backend exposes an option for.
1415
+ ...textStyle(element.options.titleFont) ? { titleFont: textStyle(element.options.titleFont) } : {},
1416
+ ...textStyle(element.options.legendFont) ? { legendFont: textStyle(element.options.legendFont) } : {},
1417
+ ...textStyle(element.options.dataLabelFont) ? { dataLabelFont: textStyle(element.options.dataLabelFont) } : {}
1418
+ };
1419
+ }
1420
+ function textStyle(font) {
1421
+ if (!font) return void 0;
1422
+ const style = {
1423
+ ...font.fontFamily !== void 0 ? { fontFamily: font.fontFamily } : {},
1424
+ ...font.fontSize !== void 0 ? { fontSize: font.fontSize } : {},
1425
+ ...font.bold !== void 0 ? { bold: font.bold } : {},
1426
+ ...font.color ? { color: font.color.hex } : {}
1427
+ };
1428
+ return Object.keys(style).length > 0 ? style : void 0;
1429
+ }
1430
+ function axisEdits(axis) {
1431
+ const value = axis;
1432
+ return {
1433
+ ...axis.title !== void 0 ? { title: axis.title } : {},
1434
+ ...axis.hidden !== void 0 ? { hidden: axis.hidden } : {},
1435
+ ...axis.showLine !== void 0 ? { lineVisible: axis.showLine } : {},
1436
+ ...axis.labelRotate !== void 0 ? { labelRotation: axis.labelRotate } : {},
1437
+ ...textStyle(axis.labelFont) ? { labelFont: textStyle(axis.labelFont) } : {},
1438
+ ...axis.gridLine ? {
1439
+ gridLine: {
1440
+ ...textStyle(axis.labelFont) ? { labelFont: textStyle(axis.labelFont) } : {},
1441
+ ...axis.gridLine.style !== void 0 ? { style: axis.gridLine.style } : {},
1442
+ ...textStyle(axis.labelFont) ? { labelFont: textStyle(axis.labelFont) } : {},
1443
+ ...axis.gridLine.size !== void 0 ? { size: axis.gridLine.size } : {},
1444
+ ...textStyle(axis.labelFont) ? { labelFont: textStyle(axis.labelFont) } : {},
1445
+ ...axis.gridLine.color ? { color: axis.gridLine.color.hex } : {}
1446
+ }
1447
+ } : {},
1448
+ ...value.minValue !== void 0 ? { min: value.minValue } : {},
1449
+ ...value.maxValue !== void 0 ? { max: value.maxValue } : {},
1450
+ ...value.majorUnit !== void 0 ? { majorUnit: value.majorUnit } : {},
1451
+ ...value.labelFormatCode !== void 0 ? { numberFormat: value.labelFormatCode } : {}
1452
+ };
1453
+ }
1454
+ function declareWorkbookContentType(zip, xml) {
1455
+ if (xml.includes(`Extension="xlsx"`)) return;
1456
+ const patched = xml.replace(
1457
+ '<Default Extension="xml"',
1458
+ `<Default Extension="xlsx" ContentType="${CHART_WORKBOOK_CONTENT_TYPE}"/><Default Extension="xml"`
1459
+ );
1460
+ if (patched === xml) {
1461
+ throw new Error(
1462
+ 'Could not declare the embedded workbook content type: [Content_Types].xml has no `<Default Extension="xml"` to anchor on. The package would ship an .xlsx part no content type covers.'
1463
+ );
1464
+ }
1465
+ zip.file("[Content_Types].xml", patched, { createFolders: false });
1466
+ }
1467
+ async function workbookBytes(chart) {
1468
+ const book = new JSZip2();
1469
+ for (const [path4, content] of chartWorkbookParts(chart.series)) {
1470
+ book.file(path4, content, { createFolders: false });
1471
+ }
1472
+ return book.generateAsync({
1473
+ type: "uint8array",
1474
+ compression: "DEFLATE",
1475
+ compressionOptions: { level: 6 }
1476
+ });
1477
+ }
1478
+ async function spliceChartParts(zip, charts) {
1479
+ if (charts.length === 0) return;
1480
+ const inputs = charts.map(spliceInput);
1481
+ const parts = [];
1482
+ for (const path4 of Object.keys(zip.files)) {
1483
+ const match = path4.match(/^ppt\/charts\/chart(\d+)\.xml$/);
1484
+ if (!match) continue;
1485
+ parts.push([Number(match[1]), await zip.file(path4).async("string")]);
1486
+ }
1487
+ parts.sort(([a], [b]) => a - b);
1488
+ for (const { ordinal, xml, chart } of matchChartParts(parts, inputs)) {
1489
+ const workbook = workbookName(ordinal);
1490
+ zip.file(`ppt/charts/chart${ordinal}.xml`, spliceChartXml(xml, chart), {
1491
+ createFolders: false
1492
+ });
1493
+ zip.file(`ppt/embeddings/${workbook}`, await workbookBytes(chart), {
1494
+ binary: true,
1495
+ createFolders: false
1496
+ });
1497
+ zip.file(
1498
+ `ppt/charts/_rels/chart${ordinal}.xml.rels`,
1499
+ chartWorkbookRelsXml(workbook),
1500
+ { createFolders: false }
1501
+ );
1502
+ }
1503
+ const contentTypes = zip.file("[Content_Types].xml");
1504
+ if (contentTypes) {
1505
+ declareWorkbookContentType(zip, await contentTypes.async("string"));
1506
+ }
1507
+ }
1508
+ var workbookName;
1509
+ var init_chartParts = __esm({
1510
+ "src/renderers/office-open/chartParts.ts"() {
1511
+ "use strict";
1512
+ workbookName = (ordinal) => `Microsoft_Excel_Worksheet${ordinal}.xlsx`;
1513
+ }
1514
+ });
1515
+
1351
1516
  // src/renderers/office-open/emit.ts
1352
1517
  import { assertNever as assertNever2 } from "@json-to-office/shared/rendering";
1353
1518
  function geometryName(geometry) {
@@ -1684,6 +1849,82 @@ function groupChild(element, ctx) {
1684
1849
  }
1685
1850
  return { group };
1686
1851
  }
1852
+ function chartChild(element, ctx) {
1853
+ const { options, transform } = element;
1854
+ const series = element.series;
1855
+ const dataLabels = dataLabelOptions(options);
1856
+ const marker = markerOptions(options);
1857
+ if (element.chartType === "bubble") {
1858
+ throw new Error(
1859
+ `the office-open renderer does not draw bubble charts (${element.path}); use the pptxgenjs renderer for this chart`
1860
+ );
1861
+ }
1862
+ return {
1863
+ // Stated, never left to the backend: `_nextChartId` in
1864
+ // `@office-open/pptx` is module-level and never resets, so an unnamed
1865
+ // chart is numbered differently on every render in the same process.
1866
+ id: ctx.nextId(),
1867
+ ...chartTypeOptions(element),
1868
+ categories: series[0]?.labels ?? [],
1869
+ series: series.map((entry, index) => ({
1870
+ name: entry.name ?? `Series ${index + 1}`,
1871
+ values: entry.values ?? [],
1872
+ // Every series, not just the first: PowerPoint labels each one, and a
1873
+ // chart that labelled only its first series would be a different chart.
1874
+ ...dataLabels ? { dataLabels } : {},
1875
+ ...options.lineSmooth !== void 0 ? { smooth: options.lineSmooth } : {},
1876
+ ...marker ? { marker } : {}
1877
+ })),
1878
+ ...options.title && options.showTitle !== false ? { title: options.title } : {},
1879
+ ...options.showLegend !== void 0 ? { showLegend: options.showLegend } : {},
1880
+ ...chartFamilyOptions(options),
1881
+ ...options.legendPosition ? { legendPosition: options.legendPosition } : {},
1882
+ x: transform.xEmu,
1883
+ y: transform.yEmu,
1884
+ width: transform.widthEmu,
1885
+ height: transform.heightEmu,
1886
+ ...element.altText ? { description: element.altText } : {}
1887
+ };
1888
+ }
1889
+ function chartFamilyOptions(options) {
1890
+ return {
1891
+ ...options.barGapWidthPercent !== void 0 ? { gapWidth: options.barGapWidthPercent } : {},
1892
+ ...options.barOverlapPercent !== void 0 ? { overlap: options.barOverlapPercent } : {},
1893
+ ...options.holeSize !== void 0 ? { holeSize: options.holeSize } : {},
1894
+ ...options.firstSliceAngle !== void 0 ? { firstSliceAngle: options.firstSliceAngle } : {}
1895
+ };
1896
+ }
1897
+ function dataLabelOptions(options) {
1898
+ const authored = options.showValue !== void 0 || options.showPercent !== void 0 || options.showLabel !== void 0 || options.showSeriesName !== void 0 || options.dataLabelPosition !== void 0;
1899
+ if (!authored) return void 0;
1900
+ return {
1901
+ showVal: options.showValue ?? false,
1902
+ showPercent: options.showPercent ?? false,
1903
+ showCatName: options.showLabel ?? false,
1904
+ showSerName: options.showSeriesName ?? false,
1905
+ showBubbleSize: false,
1906
+ showLegendKey: false,
1907
+ ...options.dataLabelPosition ? { position: options.dataLabelPosition } : {}
1908
+ };
1909
+ }
1910
+ function markerOptions(options) {
1911
+ const marker = {
1912
+ ...options.lineDataSymbol ? { symbol: options.lineDataSymbol } : {},
1913
+ ...options.lineDataSymbolSize !== void 0 ? { size: options.lineDataSymbolSize } : {}
1914
+ };
1915
+ return Object.keys(marker).length > 0 ? marker : void 0;
1916
+ }
1917
+ function chartTypeOptions(element) {
1918
+ const horizontal = element.options.barDirection === "bar";
1919
+ switch (element.chartType) {
1920
+ case "bar":
1921
+ return { type: horizontal ? "bar" : "column" };
1922
+ case "bar3D":
1923
+ return { type: horizontal ? "bar" : "column", threeD: true };
1924
+ default:
1925
+ return { type: element.chartType };
1926
+ }
1927
+ }
1687
1928
  function slideChild(element, ctx) {
1688
1929
  switch (element.kind) {
1689
1930
  case "textBox":
@@ -1697,9 +1938,8 @@ function slideChild(element, ctx) {
1697
1938
  case "group":
1698
1939
  return groupChild(element, ctx);
1699
1940
  case "chart":
1700
- throw new Error(
1701
- `the office-open renderer does not emit charts (${element.path})`
1702
- );
1941
+ ctx.charts?.push(element);
1942
+ return { chart: chartChild(element, ctx) };
1703
1943
  default:
1704
1944
  return assertNever2(element, "PptxIrElement");
1705
1945
  }
@@ -1801,26 +2041,30 @@ async function createOfficeOpenPptxRenderer() {
1801
2041
  format: "pptx",
1802
2042
  capabilities: OFFICE_OPEN_CAPABILITIES,
1803
2043
  async render(ir, options) {
1804
- const presentation = await buildPresentationOptions(ir);
2044
+ const charts = [];
2045
+ const presentation = await buildPresentationOptions(ir, charts);
1805
2046
  const bytes = await backend.generatePresentation(presentation, {
1806
2047
  type: "uint8array"
1807
2048
  });
1808
2049
  const raw = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
1809
- if (options?.deterministic === false) return raw;
1810
- return new Uint8Array(
1811
- await finalizePackageBuffer(Buffer.from(raw), {
1812
- generatedAt: options?.generatedAt
1813
- })
1814
- );
2050
+ if (charts.length === 0 && options?.deterministic === false) return raw;
2051
+ const zip = await readPackage(Buffer.from(raw));
2052
+ await spliceChartParts(zip, charts);
2053
+ if (options?.deterministic === false) {
2054
+ return new Uint8Array(await writePackage(zip));
2055
+ }
2056
+ await finalizePackage(zip, resolveGeneratedAt(options?.generatedAt));
2057
+ return new Uint8Array(await writePackage(zip));
1815
2058
  }
1816
2059
  };
1817
2060
  }
1818
- async function buildPresentationOptions(ir) {
2061
+ async function buildPresentationOptions(ir, charts = []) {
1819
2062
  let nextDrawingId = 2;
1820
2063
  const ctx = {
1821
2064
  resources: new Map(ir.resources.map((r) => [r.id, r])),
1822
2065
  resourceBytes: await loadResourceBytes(ir.resources),
1823
- nextId: () => nextDrawingId++
2066
+ nextId: () => nextDrawingId++,
2067
+ charts
1824
2068
  };
1825
2069
  const presentation = {
1826
2070
  size: { width: ir.size.widthEmu, height: ir.size.heightEmu },
@@ -1911,13 +2155,13 @@ var init_office_open = __esm({
1911
2155
  "src/renderers/office-open/index.ts"() {
1912
2156
  "use strict";
1913
2157
  init_finalizePackage();
2158
+ init_chartParts();
1914
2159
  init_features();
1915
2160
  init_emit2();
1916
2161
  OFFICE_OPEN_PPTX_RENDERER_ID = "office-open";
1917
2162
  OFFICE_OPEN_PPTX = "@office-open/pptx";
1918
2163
  UNSUPPORTED = /* @__PURE__ */ new Set([
1919
2164
  "svg",
1920
- "charts",
1921
2165
  "image-transform",
1922
2166
  "image-crop",
1923
2167
  "image-rounding",
@@ -3343,6 +3587,61 @@ var CHART_TYPES = [
3343
3587
  "radar",
3344
3588
  "scatter"
3345
3589
  ];
3590
+ var CHART_STYLE_FEATURES = {
3591
+ showValue: "chart-data-labels",
3592
+ showPercent: "chart-data-labels",
3593
+ showLabel: "chart-data-labels",
3594
+ showSerName: "chart-data-labels",
3595
+ dataLabelPosition: "chart-data-labels",
3596
+ dataBorder: "chart-data-border",
3597
+ catAxisHidden: "chart-axis-visibility",
3598
+ valAxisHidden: "chart-axis-visibility",
3599
+ catAxisLineShow: "chart-axis-visibility",
3600
+ valAxisLineShow: "chart-axis-visibility",
3601
+ catAxisLabelRotate: "chart-axis-style",
3602
+ catGridLine: "chart-axis-style",
3603
+ valGridLine: "chart-axis-style",
3604
+ valAxisMinVal: "chart-axis-scale",
3605
+ valAxisMaxVal: "chart-axis-scale",
3606
+ valAxisMajorUnit: "chart-axis-scale",
3607
+ valAxisLabelFormatCode: "chart-axis-scale",
3608
+ barGapWidthPct: "chart-bar-style",
3609
+ barOverlapPct: "chart-bar-style",
3610
+ firstSliceAng: "chart-pie-style",
3611
+ holeSize: "chart-pie-style",
3612
+ lineSmooth: "chart-line-style",
3613
+ lineDataSymbol: "chart-line-style",
3614
+ lineSize: "chart-line-style",
3615
+ lineDataSymbolSize: "chart-line-style",
3616
+ radarStyle: "chart-radar-style",
3617
+ titleFontSize: "chart-text-style",
3618
+ titleColor: "chart-text-style",
3619
+ titleFontFace: "chart-text-style",
3620
+ titleFontWeight: "chart-text-style",
3621
+ legendFontSize: "chart-text-style",
3622
+ legendFontFace: "chart-text-style",
3623
+ legendFontWeight: "chart-text-style",
3624
+ legendColor: "chart-text-style",
3625
+ catAxisLabelFontSize: "chart-text-style",
3626
+ catAxisLabelColor: "chart-text-style",
3627
+ catAxisLabelFontFace: "chart-text-style",
3628
+ catAxisLabelFontWeight: "chart-text-style",
3629
+ valAxisLabelFontSize: "chart-text-style",
3630
+ valAxisLabelColor: "chart-text-style",
3631
+ valAxisLabelFontFace: "chart-text-style",
3632
+ valAxisLabelFontWeight: "chart-text-style",
3633
+ dataLabelColor: "chart-text-style",
3634
+ dataLabelFontSize: "chart-text-style",
3635
+ dataLabelFontFace: "chart-text-style",
3636
+ dataLabelFontWeight: "chart-text-style",
3637
+ dataLabelFontBold: "chart-text-style"
3638
+ };
3639
+ function requireChartStyleFeatures(props, ctx, path4) {
3640
+ for (const [prop, feature] of Object.entries(CHART_STYLE_FEATURES)) {
3641
+ if (props[prop] === void 0) continue;
3642
+ ctx.features.require(feature, `${path4}.${prop}`);
3643
+ }
3644
+ }
3346
3645
  function compileChart(component, scope) {
3347
3646
  const { ctx, path: path4 } = scope;
3348
3647
  const props = component.props;
@@ -3385,6 +3684,7 @@ function compileChart(component, scope) {
3385
3684
  );
3386
3685
  }
3387
3686
  ctx.features.require("charts", path4);
3687
+ requireChartStyleFeatures(props, ctx, path4);
3388
3688
  return {
3389
3689
  kind: "chart",
3390
3690
  id: scope.id,
@@ -4416,15 +4716,16 @@ function processPresentation(document, options) {
4416
4716
  slideComponents,
4417
4717
  theme
4418
4718
  ).map((component) => remapHyperlinkSlideRefs(component, slideIndexMap));
4419
- const placeholders = child.props.placeholders;
4719
+ const slideProps = child.props ?? {};
4720
+ const placeholders = slideProps.placeholders;
4420
4721
  slides.push({
4421
4722
  components: resolvedComponents,
4422
- background: child.props.background,
4423
- transition: child.props.transition,
4424
- notes: child.props.notes,
4425
- layout: child.props.layout,
4426
- hidden: child.props.hidden,
4427
- template: child.props.template,
4723
+ background: slideProps.background,
4724
+ transition: slideProps.transition,
4725
+ notes: slideProps.notes,
4726
+ layout: slideProps.layout,
4727
+ hidden: slideProps.hidden,
4728
+ template: slideProps.template,
4428
4729
  placeholders: placeholders ? Object.fromEntries(
4429
4730
  Object.entries(placeholders).map(([name, component]) => [
4430
4731
  name,