@openpresentation/opf-pptx 0.11.0 → 0.11.2
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/DEPENDENCY-NOTES.md +1 -1
- package/README.md +16 -7
- package/dist/chart-types.js +183 -0
- package/dist/chart-workbook.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +228 -95
- package/dist/media-dedupe.js +44 -0
- package/dist/theme-colors.js +92 -6
- package/dist/watermark-provenance.js +146 -0
- package/package.json +3 -3
package/DEPENDENCY-NOTES.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Coordinated `@openpresentation/opf` pin
|
|
4
4
|
|
|
5
|
-
PPTX 0.11.
|
|
5
|
+
PPTX 0.11.2 depends on published [`@openpresentation/opf@^0.11.2`](https://www.npmjs.com/package/@openpresentation/opf) (reference layer: ColorRef, document `variables`, exported `resolveColorRef()`). The optional renderer peer is [`@openpresentation/opf-render@^0.11.0`](https://www.npmjs.com/package/@openpresentation/opf-render) so editor 0.10.0 + render 0.11.0 + pptx 0.11.0 install together. CI checks out OPF [`b8a1faf`](https://github.com/OpenPresentation/opf/commit/b8a1faf24203635bd36b09ea19f44bcd53781f16) (`opf-v0.11.0`) for coordinated source linking. Named content colors resolve through core `resolveColorRef()`; eight-digit hex alpha stays a local export concern because core `normalizeHexColor` drops the alpha byte. Native `schemeClr` / theme `clrScheme` writes remain follow-up work.
|
|
6
6
|
|
|
7
7
|
OPF PPTX 0.5.1 ships the exact, unmodified PptxGenJS 4.0.1 ESM distribution in `vendor/pptxgenjs`, with its MIT license, upstream archive integrity and per-file SHA-256 hashes. Its actual JSZip dependency is declared directly. Ordinary npm installations therefore omit the unused image-size parser without requiring consumer overrides. This removes the affected dependency; it does not patch the parser. Published OPF PPTX 0.5.0 retains the older graph.
|
|
8
8
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OPF PPTX
|
|
2
2
|
|
|
3
|
-
Version 0.11.0 requires core `^0.11.2` (cover slides are centered between header and footer furniture) and the optional `@openpresentation/opf-render` peer `^0.11.0`; install them together so export and preview resolve the same core. Version 0.10.0 required core `^0.11.1` and the peer `^0.10.0`. It adds native slide-number and date fields, socials, slide-image treatments, theme color schemes, script-slot fonts and RTL, re-import of design and metadata references, native underline and current-body formatting on import, and UTC-canonical `zipDate` (see the changelog for the intentional contract changes).
|
|
3
|
+
Version 0.11.2 exports the classic chart types as native chart constructs and writes named and default table colours, text pairs and hyperlink colours as theme references (no API or dependency change; resolved colours are unchanged). Version 0.11.1 exports `design.watermark` as a native picture (it re-imports) and embeds byte-identical media parts once, with no API or dependency change. Version 0.11.0 requires core `^0.11.2` (cover slides are centered between header and footer furniture) and the optional `@openpresentation/opf-render` peer `^0.11.0`; install them together so export and preview resolve the same core. Version 0.10.0 required core `^0.11.1` and the peer `^0.10.0`. It adds native slide-number and date fields, socials, slide-image treatments, theme color schemes, script-slot fonts and RTL, re-import of design and metadata references, native underline and current-body formatting on import, and UTC-canonical `zipDate` (see the changelog for the intentional contract changes).
|
|
4
4
|
|
|
5
5
|
Version 0.9.1 kept core `^0.11.0` and raised the optional `@openpresentation/opf-render` peer to `^0.9.0` so it coexists with editor 0.8.0. ColorRef / `variables` on content colors still hex-resolve through core `resolveColorRef()` before PptxGenJS `srgbClr` export. Unrecognized run colors such as `color:'invalid'` still validate and fall back to the theme text color. Native DrawingML `schemeClr` and theme `clrScheme` writes, and native `p:hf` headers/footers, are not in this release. Import still flattens theme colors to hex. Metric, quote and timeline layout placeholders and the corrected text-bullet contract from 0.8.1 are retained.
|
|
6
6
|
|
|
@@ -80,7 +80,7 @@ The first exporter keeps the public API stable while using `pptxgenjs` internall
|
|
|
80
80
|
|
|
81
81
|
- `Slide.title`, `Slide.subtitle`, and `Slide.tag` become editable text boxes, not PowerPoint master placeholders.
|
|
82
82
|
- Root payloads, `blocks[]`, and promoted region keys become editable slide objects in deterministic regions. Promoted keys use the OPF 3x3 region vocabulary (`top`, `middle`, `bottom`, `left`, `center`, `right`).
|
|
83
|
-
- Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects. Content ColorRef values (hex, scheme slots/roles, and `var:<id>`) resolve through core `resolveColorRef()`. Slot and role names become native `a:schemeClr` references when the exported theme holds that exact color; hex, `var:<id>` and slide-override colors stay `a:srgbClr` (see [Theme color scheme](#theme-color-scheme)).
|
|
83
|
+
- Text, lists, metrics, quotes, timelines, code, tables, and inline-data charts are emitted as editable PowerPoint text, table, and chart objects. Content ColorRef values (hex, scheme slots/roles, and `var:<id>`) resolve through core `resolveColorRef()`. Slot and role names become native `a:schemeClr` references when the exported theme holds that exact color; hex, `var:<id>` and slide-override colors stay `a:srgbClr` (see [Theme color scheme](#theme-color-scheme)). Each classic chart type the core catalog keeps (one per Aspose.Slides `ChartType`: clustered/stacked/100% stacked column and bar, line and stacked line with or without markers, area/stacked/100% area, pie, doughnut, scatter with markers, radar/radar with markers/filled radar) is written as its exact native construct; deprecated core ids export as their replacement; other ids keep the previous best-effort mapping. Chartex types (treemap, histogram, pareto, box & whisker, waterfall, funnel, map) still export as clustered columns and report `chart-data-adapted` (`chartex-fallback`); a pie or doughnut chart plots only its first series and reports `chart-data-adapted` (`series-dropped`) when it has more.
|
|
84
84
|
- A chart whose data is one column of values (a histogram or dot plot) has no category column. It exports as a native chart and reports `chart-data-adapted`: a histogram is binned into equal-width bins (Sturges' count, at most 50) and written as a column chart of the counts, and any other chart type plots the values against their row numbers. PowerPoint's own histogram chart is not exported (histogram is a column chart at this engine). Cells parse as in every chart ("12%", "$5" and "1,234" count) and cells that hold no number are skipped, not plotted as 0. Binned counts do not round-trip: re-importing the file gives a `Bin`/`Frequency` column chart, not the raw values. Chart data that cannot be plotted (no numbers, no rows, or an external data source), an empty table and unsupported content keep a placeholder frame with a plain-language description (never a dump of the source data or URLs) and report `chart-data-unplottable` or `content-placeholder` with a `reason`; content is never dropped without a diagnostic.
|
|
85
85
|
- Image assets are embedded only when supplied as data URIs, local paths, or host-resolved bytes/paths. Remote asset URLs are never fetched by the runtime path.
|
|
86
86
|
- ZIP entries, generated chart/workbook part names, core-property timestamps, and nested chart workbook timestamps are normalized for reproducible bytes. [Export determinism](docs/export-determinism.md) lists the tested time zone, locale, clock and host-font controls and each known variance (WebP conversion, font registries, ICU segmentation, timestamps).
|
|
@@ -113,7 +113,7 @@ The first importer is mechanical and schema-compatible:
|
|
|
113
113
|
- The first slide master's theme `clrScheme` maps to `design.colorScheme`: a catalog id on an exact twelve-slot match, otherwise inline slots. A theme named like a catalog theme maps to `design.theme` when its colors or heading/body fonts corroborate it (see [Theme color scheme](#theme-color-scheme)).
|
|
114
114
|
- Native title/subtitle placeholders retain their roles. On slides without complete OPF heading tags, recognizable text-box positions and sizes provide a fallback. If any complete OPF heading role is recovered, untagged body text stays in `blocks[]` instead of being promoted into an absent heading role. Damaged tags retain visible text through ordinary import and diagnostics.
|
|
115
115
|
- Remaining text boxes map to `blocks[]` as text or list payloads, sorted by OOXML position.
|
|
116
|
-
- PowerPoint tables map to OPF table blocks, embedded images map to data URI image blocks, and cached chart series map to
|
|
116
|
+
- PowerPoint tables map to OPF table blocks, embedded images map to data URI image blocks, and cached chart series map to OPF chart blocks whose `type` is the core chart type id for the native construct (for example a `percentStacked` column chart imports as `100pct-stacked-column-3x`, a filled radar as `filled-radar`; a scatter chart imports as `[Point, X, ...series]`).
|
|
117
117
|
- Chart cache points are placed by their native `c:pt@idx`, with `c:ptCount` retaining trailing gaps. Missing labels and values become `null`; explicitly empty labels and series names stay empty strings, and an actual numeric zero stays zero. Empty numeric cache values remain missing. Malformed or duplicate indices, contradictory counts, competing caches and allocation limits reject with `invalid-chart-cache` and the chart-part/cache path; hierarchical category caches reject with `unsupported-chart-cache` instead of flattening labels. Each cache is limited to 100,000 positions, and combined caches and the emitted table each to 1,000,000 cells. Complete decimal scientific notation in numeric caches is read as a number; exponent overflow or nonzero underflow to zero rejects with `unsupported-chart-cache` and the chart part, series, cache and logical point path. Zero coefficients remain zero. This finite-number boundary does not classify rejected text as invalid OOXML. Malformed exponent strings and other nonblank numeric text retain the existing legacy conversion policy. This cache-only import does not repair embedded worksheets, restore unsupported scatter X/point labels, change exporter/workbook parsing or export/re-export missing-data handling, or establish native chart fidelity.
|
|
118
118
|
- Table imports retain empty rows. A native `firstRow` flag of `1` or `true` maps the first row to column labels; absent/false flags retain every row as data. New exports set this flag from OPF columns. Older exports without the flag retain their labels as the first data row rather than inferring headers.
|
|
119
119
|
- Native table text preserves run/field/break order, significant whitespace, and blank paragraphs. Cells return canonical `{value, style}` objects; `value` retains scalar text or supported rich runs. Covered merge positions are `null`. Explicit normal headers override OPF’s bold header default. Numeric/boolean/null source types cannot be reconstructed from native display text.
|
|
@@ -222,6 +222,8 @@ Slide-image treatments export from core's normalized geometry as native DrawingM
|
|
|
222
222
|
- `recolor` becomes `a:grayscl` or `a:duotone`, followed by `a:alphaModFix` for `opacity`, on the blip only.
|
|
223
223
|
- `overlay` becomes one tagged `OPF slide image overlay slides.N` shape directly above the picture.
|
|
224
224
|
|
|
225
|
+
A deck or slide watermark (`design.watermark`) exports as one native picture named `OPF watermark` per slide, after the slide image and its overlay, if any, and before all content (the preview's paint order, so a background slide image lies beneath the watermark), at the preview's frame: the centered 40% by 40% box at 30%/30% of the slide, fitted without cropping. Its opacity is `a:alphaModFix` (the object form's `opacity`, otherwise the preview default 0.08), and its alt text is the asset's `alt`, otherwise `Watermark`. `design.watermark = false` on a slide exports none. An unchanged export imports back as `design.watermark` (at deck level when every slide carries the same one); an edited picture stays ordinary content and reports `invalid-watermark-provenance`. An unresolved image reports `unresolved-asset` and an object with no `src` reports `watermark-not-exported`.
|
|
226
|
+
|
|
225
227
|
An unchanged export imports every treatment field back. An edited overlay drops only the overlay and reports `invalid-slide-image-provenance`. An edited picture or effect drops the slide image. See core `docs/image-treatments.md` for the vocabulary and the unsupported effects: blur, shadows, soft edges and background removal. PowerPoint's luminance weights for grayscale and duotone are unverified natively.
|
|
226
228
|
|
|
227
229
|
Tests compare SVG/native fit and crop geometry across nine synthetic raster fixtures and cover all eight JPEG orientations. Keynote 14.4 visually preserves proportions for wide/tall fit/crop and displays all eight orientations correctly. This does not establish Microsoft PowerPoint raster parity or WebP support in every Office version.
|
|
@@ -266,15 +268,22 @@ Import reads `a:pattFill` with a DrawingML preset and resolvable foreground/back
|
|
|
266
268
|
|
|
267
269
|
Export writes the deck's resolved color scheme into `ppt/theme/theme1.xml` `a:clrScheme`, named after the scheme. The OPF slots map one to one: `dark1`/`light1`/`dark2`/`light2` to `dk1`/`lt1`/`dk2`/`lt2`, `accent1`-`accent6` to themselves, and `hyperlink`/`followedHyperlink` to `hlink`/`folHlink`. An abstract role fills a slot only when the scheme leaves that slot unset. Previously every export carried the vendored Office palette (`accent1` `4472C4`). When the deck names a catalog `design.theme`, `a:theme` and its `thm15:themeFamily` take that theme's name. The rewrite happens during package normalization; the vendored PptxGenJS bytes are unchanged.
|
|
268
270
|
|
|
269
|
-
Document colors that name a slot or role (`accent2`, `textSecondary`, `surface`, ...) become `a:schemeClr` in runs, table cell fills and text, and table borders. Theme-slot backgrounds (`{type:'theme', slot}` or a slot name) do the same. Slide content reaches the theme through the master color map, so `dark1`, `light1`, `dark2` and `light2` are written as `tx1`, `bg1`, `tx2` and `bg2`. A reference becomes `schemeClr` only when the deck theme slot holds exactly the color the slide resolved. PowerPoint has one theme per master, so a slide
|
|
271
|
+
Document colors that name a slot or role (`accent2`, `textSecondary`, `surface`, ...) become `a:schemeClr` in runs, table cell fills and text, and table borders. Theme-slot backgrounds (`{type:'theme', slot}` or a slot name) do the same. Slide content reaches the theme through the master color map, so `dark1`, `light1`, `dark2` and `light2` are written as `tx1`, `bg1`, `tx2` and `bg2`. A reference becomes `schemeClr` only when the deck theme slot holds exactly the color the slide resolved. PowerPoint has one theme per master, so a slide with its own `design.colorScheme` (or a slide theme that changes any slot) keeps **every** color literal, including values that happen to equal the deck theme, so a theme edit never partially recolors it.
|
|
272
|
+
|
|
273
|
+
Default text follows the theme only where the background does. When a slide's background is an opaque theme reference (`light1`/`light2` or `dark1`/`dark2`, the only slots a theme background accepts), the exporter's default and muted text are written as the paired slot: `tx1`/`tx2` on `bg1`/`bg2`, and `bg1`/`bg2` on `tx1`/`tx2`. This applies only when the resolved color is exactly that deck slot, so a PowerPoint theme switch moves text and background together. It covers headings, body and list text, list markers, metric labels, quote text and footers, header/footer text and placeholders. Text on a literal background, text on a card fill and override slides stay literal.
|
|
274
|
+
|
|
275
|
+
Table chrome follows the theme too (FF-24c). With no theme override on the slide, the default header fill is `accent1`, the body fill is the `surface` slot (`bg2`, or `tx2` on a dark deck), and every cell border is `accent5`. Each is written as `a:schemeClr` when the deck theme slot holds exactly that color. Default cell text pairs with its fill: `tx1` on a `bg2` fill, `bg1` on a `tx2` fill, and light1 or dark1 (`bg1`/`tx1`) on an accent fill, again only when the contrast-selected color is exactly that slot. A named `fill`, `color` or border color on a cell (`accent2`, `surface`, `light2`, `textSecondary`, ...) is a scheme reference, `hyperlink` and `followedHyperlink` included, and a literal on the cell stays literal (default text on a literal fill included). PptxGenJS 4.0.1 cannot write `hlink`/`folHlink`, so those two are written as a reserved literal (`FE01A0`/`FE01A1`, or the next free pair) and rewritten to `a:schemeClr` in the finished slide part; a document that itself uses every reserved value keeps its link colors literal.
|
|
276
|
+
|
|
277
|
+
When the deck background is a theme slot other than `light1`, the slide master gets that background and its title/body/other text styles use the paired text slot. The layout drops its own `bg1` background and inherits the master, so slides added in PowerPoint match the theme. `light1` decks keep the vendored master.
|
|
278
|
+
|
|
279
|
+
These stay `a:srgbClr`:
|
|
270
280
|
|
|
271
281
|
- literal hex colors, even when they equal a scheme slot;
|
|
272
282
|
- `var:<id>` variables;
|
|
273
283
|
- translucent colors;
|
|
274
|
-
- engine-derived chrome:
|
|
275
|
-
- `hyperlink` and `followedHyperlink` run and cell colors, because PptxGenJS 4.0.1 cannot emit `hlink`/`folHlink`. Borders and backgrounds can.
|
|
284
|
+
- other engine-derived chrome: card fills and borders, chart panels, series and labels, timeline connectors and markers, and image and media placeholders, plus default text on a card or a literal background. These colors are contrast-selected or derived from roles, not named by the document, and are not yet theme references.
|
|
276
285
|
|
|
277
|
-
Import reads the first slide master's theme. An exact twelve-slot match with a bundled catalog scheme returns its id; the `clrScheme` name breaks ties. Otherwise the importer returns inline slots, relative to the catalog scheme the `clrScheme` is named after when there is one (`{id:'boost', accent1:'#123456'}`). A theme whose name equals a catalog theme's name maps to `design.theme` only if the package's color scheme or heading/body fonts match that theme; otherwise `theme-unverified` is reported. A missing theme, or slots that are not opaque sRGB/system colors, report `unsupported-theme-colors` on `design.colorScheme`. Slide colors are still imported as resolved hex. Role overrides such as `primary` have no theme slot and are not recovered. `test/theme-colors.mjs` covers all 14 catalog schemes and 4 catalog themes, override, foreign and damaged themes, and the literal-color boundaries. This establishes package structure and round-trip, not PowerPoint rendering.
|
|
286
|
+
Import reads the first slide master's theme. An exact twelve-slot match with a bundled catalog scheme returns its id; the `clrScheme` name breaks ties. Otherwise the importer returns inline slots, relative to the catalog scheme the `clrScheme` is named after when there is one (`{id:'boost', accent1:'#123456'}`). A theme whose name equals a catalog theme's name maps to `design.theme` only if the package's color scheme or heading/body fonts match that theme; otherwise `theme-unverified` is reported. A missing theme, or slots that are not opaque sRGB/system colors, report `unsupported-theme-colors` on `design.colorScheme`. Slide colors are still imported as resolved hex. Role overrides such as `primary` have no theme slot and are not recovered. `test/theme-colors.mjs` covers all 14 catalog schemes and 4 catalog themes, override, foreign and damaged themes, and the literal-color boundaries. `test/table-theme-colors.mjs` covers named and default table chrome on all 14 schemes and four backgrounds, literals, override slides, the link colors, re-import and a theme switch in the package. This establishes package structure and round-trip, not PowerPoint rendering.
|
|
278
287
|
|
|
279
288
|
## JPEG orientation on import
|
|
280
289
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// OPF chart type ids -> native PowerPoint chart constructs (FF-22).
|
|
2
|
+
//
|
|
3
|
+
// The core catalog keeps one chart type per Aspose.Slides ChartType
|
|
4
|
+
// (spec/catalogs/chart-types, OpenPresentation/opf#121) and deprecates the
|
|
5
|
+
// rest with a replacement id. The published core package this exporter
|
|
6
|
+
// depends on predates that metadata, so the mapping is carried here. Kept ids
|
|
7
|
+
// map to the exact Office construct; deprecated ids resolve to their
|
|
8
|
+
// replacement; any other id keeps the legacy substring heuristic.
|
|
9
|
+
|
|
10
|
+
const category = (pptx, extra = {}) => ({ family: 'category', pptx, ...extra });
|
|
11
|
+
|
|
12
|
+
export const CHART_TYPES = Object.freeze({
|
|
13
|
+
column: category('bar', { aspose: 'ClusteredColumn', barDir: 'col', grouping: 'clustered' }),
|
|
14
|
+
'stacked-column-3x': category('bar', { aspose: 'StackedColumn', barDir: 'col', grouping: 'stacked' }),
|
|
15
|
+
'100pct-stacked-column-3x': category('bar', { aspose: 'PercentsStackedColumn', barDir: 'col', grouping: 'percentStacked' }),
|
|
16
|
+
bar: category('bar', { aspose: 'ClusteredBar', barDir: 'bar', grouping: 'clustered' }),
|
|
17
|
+
'stacked-bar-3x': category('bar', { aspose: 'StackedBar', barDir: 'bar', grouping: 'stacked' }),
|
|
18
|
+
'100pct-stacked-bar-3x': category('bar', { aspose: 'PercentsStackedBar', barDir: 'bar', grouping: 'percentStacked' }),
|
|
19
|
+
line: category('line', { aspose: 'Line', grouping: 'standard', markers: false }),
|
|
20
|
+
'line-with-markers': category('line', { aspose: 'LineWithMarkers', grouping: 'standard', markers: true }),
|
|
21
|
+
'stacked-line-3x': category('line', { aspose: 'StackedLine', grouping: 'stacked', markers: false }),
|
|
22
|
+
'stacked-line-with-markers-3x': category('line', { aspose: 'StackedLineWithMarkers', grouping: 'stacked', markers: true }),
|
|
23
|
+
area: category('area', { aspose: 'Area', grouping: 'standard' }),
|
|
24
|
+
'stacked-area-3x': category('area', { aspose: 'StackedArea', grouping: 'stacked' }),
|
|
25
|
+
'100pct-stacked-area-3x': category('area', { aspose: 'PercentsStackedArea', grouping: 'percentStacked' }),
|
|
26
|
+
pie: { family: 'circular', pptx: 'pie', aspose: 'Pie' },
|
|
27
|
+
doughnut: { family: 'circular', pptx: 'doughnut', aspose: 'Doughnut' },
|
|
28
|
+
scatter: { family: 'xy', pptx: 'scatter', aspose: 'ScatterWithMarkers' },
|
|
29
|
+
radar: category('radar', { aspose: 'Radar', radarStyle: 'standard', markers: false }),
|
|
30
|
+
'radar-with-markers': category('radar', { aspose: 'RadarWithMarkers', radarStyle: 'marker', markers: true }),
|
|
31
|
+
'filled-radar': category('radar', { aspose: 'FilledRadar', radarStyle: 'filled', markers: false }),
|
|
32
|
+
treemap: { family: 'chartex', layoutId: 'treemap', aspose: 'Treemap' },
|
|
33
|
+
histogram: { family: 'chartex', layoutId: 'clusteredColumn', aspose: 'Histogram' },
|
|
34
|
+
pareto: { family: 'chartex', layoutId: 'clusteredColumn', aspose: 'ParetoLine' },
|
|
35
|
+
'box-and-whisker': { family: 'chartex', layoutId: 'boxWhisker', aspose: 'BoxAndWhisker' },
|
|
36
|
+
waterfall: { family: 'chartex', layoutId: 'waterfall', aspose: 'Waterfall' },
|
|
37
|
+
funnel: { family: 'chartex', layoutId: 'funnel', aspose: 'Funnel' },
|
|
38
|
+
world: { family: 'chartex', layoutId: 'regionMap', aspose: 'Map' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// A chartex id (treemap, histogram, ...) has no classic construct; the exporter
|
|
42
|
+
// writes it as a clustered column chart and reports chart-data-adapted.
|
|
43
|
+
export const CHARTEX_FALLBACK = CHART_TYPES.column;
|
|
44
|
+
|
|
45
|
+
const variants = (base, target) => Object.fromEntries([base, `${base}-2x`, `${base}-3x`].map((id) => [id, target]));
|
|
46
|
+
|
|
47
|
+
// Deprecated core ids (opf 0.12.0 removes them) -> replacement id.
|
|
48
|
+
export const DEPRECATED_CHART_TYPES = Object.freeze({
|
|
49
|
+
...variants('100pct-bullet-bar', '100pct-stacked-bar-3x'),
|
|
50
|
+
'100pct-progress-bar': '100pct-stacked-bar-3x',
|
|
51
|
+
'100pct-stacked-bar-2x': '100pct-stacked-bar-3x',
|
|
52
|
+
...variants('100pct-bullet-column', '100pct-stacked-column-3x'),
|
|
53
|
+
'100pct-stacked-column-2x': '100pct-stacked-column-3x',
|
|
54
|
+
'100pct-stacked-area-2x': '100pct-stacked-area-3x',
|
|
55
|
+
australia: 'world',
|
|
56
|
+
canada: 'world',
|
|
57
|
+
'united-kingdom': 'world',
|
|
58
|
+
'united-states': 'world',
|
|
59
|
+
'box-and-whisker-2x': 'box-and-whisker',
|
|
60
|
+
'box-and-whisker-3x': 'box-and-whisker',
|
|
61
|
+
...variants('bullet-bar', 'bar'),
|
|
62
|
+
'clustered-bar-2x': 'bar',
|
|
63
|
+
...variants('bullet-column', 'column'),
|
|
64
|
+
'clustered-column': 'column',
|
|
65
|
+
...Object.fromEntries(['', '-2x', '-3x', '-4x', '-5x', '-6x'].map((suffix) => [`dot-plot${suffix}`, 'scatter'])),
|
|
66
|
+
dumbbell: 'scatter',
|
|
67
|
+
'line-2x': 'line',
|
|
68
|
+
'line-3x': 'line',
|
|
69
|
+
'line-with-high-low': 'line',
|
|
70
|
+
...Object.fromEntries(['', '-2x', '-3x', '-4x', '-5x', '-6x'].map((suffix) => [`sparkline${suffix}`, 'line'])),
|
|
71
|
+
'line-with-high-low-and-markers': 'line-with-markers',
|
|
72
|
+
'line-with-markers-2x': 'line-with-markers',
|
|
73
|
+
'line-with-markers-3x': 'line-with-markers',
|
|
74
|
+
'stacked-area-2x': 'stacked-area-3x',
|
|
75
|
+
'stacked-bar-2x': 'stacked-bar-3x',
|
|
76
|
+
'stacked-column-2x': 'stacked-column-3x',
|
|
77
|
+
'stacked-line-2x': 'stacked-line-3x',
|
|
78
|
+
'stacked-line-with-markers-2x': 'stacked-line-with-markers-3x',
|
|
79
|
+
'treemap-2x': 'treemap',
|
|
80
|
+
'treemap-3x': 'treemap',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const ALIASES = Object.freeze({ donut: 'doughnut' });
|
|
84
|
+
|
|
85
|
+
// Resolve an OPF chart type id to {id, spec}. Kept and deprecated ids resolve
|
|
86
|
+
// to a kept id; anything else returns the legacy heuristic with id: null.
|
|
87
|
+
export function resolveChartType(type) {
|
|
88
|
+
const raw = String(type ?? '').trim().toLowerCase();
|
|
89
|
+
const id = ALIASES[raw] ?? DEPRECATED_CHART_TYPES[raw] ?? raw;
|
|
90
|
+
if (Object.hasOwn(CHART_TYPES, id)) return { id, spec: CHART_TYPES[id] };
|
|
91
|
+
return { id: null, spec: legacyChartType(raw) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The pre-FF-22 heuristic for ids outside the core catalog.
|
|
95
|
+
function legacyChartType(normalized) {
|
|
96
|
+
if (normalized.includes('pie')) return CHART_TYPES.pie;
|
|
97
|
+
if (normalized.includes('doughnut') || normalized.includes('donut')) return CHART_TYPES.doughnut;
|
|
98
|
+
if (normalized.includes('area')) return { ...CHART_TYPES.area, legacy: true };
|
|
99
|
+
if (normalized.includes('line')) return { ...CHART_TYPES['line-with-markers'], legacy: true };
|
|
100
|
+
if (normalized.includes('scatter')) return CHART_TYPES.scatter;
|
|
101
|
+
if (normalized.includes('radar')) return { ...CHART_TYPES.radar, legacy: true };
|
|
102
|
+
const grouping = normalized.includes('stacked') ? 'stacked' : 'clustered';
|
|
103
|
+
return category('bar', { barDir: normalized.includes('bar') ? 'bar' : 'col', grouping, legacy: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Native construct -> kept OPF id, for import. `node` is the parsed chart-type
|
|
107
|
+
// element (fast-xml-parser, attributes as plain keys).
|
|
108
|
+
export function chartTypeFromNative(element, node) {
|
|
109
|
+
const attr = (name) => node?.[`c:${name}`]?.val;
|
|
110
|
+
const series = asArray(node?.['c:ser']);
|
|
111
|
+
const markers = !series.length || series.some((ser) => ser?.['c:marker']?.['c:symbol']?.val !== 'none');
|
|
112
|
+
const grouping = attr('grouping') ?? 'standard';
|
|
113
|
+
const stacked = grouping === 'stacked' || grouping === 'percentStacked';
|
|
114
|
+
const percent = grouping === 'percentStacked';
|
|
115
|
+
switch (element) {
|
|
116
|
+
case 'barChart':
|
|
117
|
+
case 'bar3DChart': {
|
|
118
|
+
const direction = attr('barDir') === 'bar' ? 'bar' : 'column';
|
|
119
|
+
if (percent) return `100pct-stacked-${direction}-3x`;
|
|
120
|
+
if (stacked) return `stacked-${direction}-3x`;
|
|
121
|
+
return direction;
|
|
122
|
+
}
|
|
123
|
+
case 'lineChart':
|
|
124
|
+
case 'line3DChart':
|
|
125
|
+
if (stacked) return markers ? 'stacked-line-with-markers-3x' : 'stacked-line-3x';
|
|
126
|
+
return markers ? 'line-with-markers' : 'line';
|
|
127
|
+
case 'areaChart':
|
|
128
|
+
case 'area3DChart':
|
|
129
|
+
return percent ? '100pct-stacked-area-3x' : stacked ? 'stacked-area-3x' : 'area';
|
|
130
|
+
case 'pieChart':
|
|
131
|
+
case 'pie3DChart':
|
|
132
|
+
case 'ofPieChart':
|
|
133
|
+
return 'pie';
|
|
134
|
+
case 'doughnutChart':
|
|
135
|
+
return 'doughnut';
|
|
136
|
+
case 'scatterChart':
|
|
137
|
+
return 'scatter';
|
|
138
|
+
case 'radarChart': {
|
|
139
|
+
const style = attr('radarStyle');
|
|
140
|
+
if (style === 'filled') return 'filled-radar';
|
|
141
|
+
if (style === 'marker' && markers) return 'radar-with-markers';
|
|
142
|
+
return 'radar';
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const NATIVE_CHART_ELEMENTS = Object.freeze([
|
|
150
|
+
'barChart', 'bar3DChart', 'lineChart', 'line3DChart', 'pieChart', 'pie3DChart', 'ofPieChart',
|
|
151
|
+
'doughnutChart', 'areaChart', 'area3DChart', 'scatterChart', 'radarChart',
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
function asArray(value) {
|
|
155
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// PptxGenJS omits or cannot express parts of these constructs. Rewrite only
|
|
159
|
+
// the generated chart-type element so the part states the exact grouping.
|
|
160
|
+
export function applyChartConstruct(xml, spec) {
|
|
161
|
+
// ScatterWithMarkers: the core catalog records scatterStyle "marker" (markers, no connecting line). PptxGenJS writes lineMarker and relies on the series line being noFill.
|
|
162
|
+
if (spec?.family === 'xy') return xml.replace(/<c:scatterStyle val="[^"]*"\/>/, '<c:scatterStyle val="marker"/>');
|
|
163
|
+
if (!spec || spec.family !== 'category') return xml;
|
|
164
|
+
const element = { bar: 'barChart', line: 'lineChart', area: 'areaChart', radar: 'radarChart' }[spec.pptx];
|
|
165
|
+
if (!element || element === 'radarChart') return xml;
|
|
166
|
+
const open = `<c:${element}>`;
|
|
167
|
+
const start = xml.indexOf(open);
|
|
168
|
+
if (start < 0) return xml;
|
|
169
|
+
const end = xml.indexOf(`</c:${element}>`, start);
|
|
170
|
+
let body = xml.slice(start + open.length, end);
|
|
171
|
+
const grouping = `<c:grouping val="${spec.grouping}"/>`;
|
|
172
|
+
if (element === 'barChart') {
|
|
173
|
+
body = body.replace(/<c:grouping val="[^"]*"\/>/, grouping);
|
|
174
|
+
} else {
|
|
175
|
+
body = grouping + body.replace(/<c:grouping val="[^"]*"\/>/g, '');
|
|
176
|
+
// CT_LineSer/CT_AreaSer have no invertIfNegative (bar series only).
|
|
177
|
+
body = body.replace(/<c:invertIfNegative val="[^"]*"\/>/g, '');
|
|
178
|
+
}
|
|
179
|
+
if (spec.grouping === 'stacked' || spec.grouping === 'percentStacked') {
|
|
180
|
+
body = body.replace(/<c:overlap val="[^"]*"\/>/, '<c:overlap val="100"/>');
|
|
181
|
+
}
|
|
182
|
+
return xml.slice(0, start) + open + body + xml.slice(end);
|
|
183
|
+
}
|
package/dist/chart-workbook.js
CHANGED
|
@@ -29,7 +29,7 @@ function workbookContext(entries,chartPart){
|
|
|
29
29
|
if(!/^xl\/(workbook\.xml|_rels\/workbook\.xml\.rels|sharedStrings\.xml|worksheets\/[^/]+\.xml)$/.test(file.name))return false;
|
|
30
30
|
size+=file.originalSize;if(size>16*1024*1024)throw Error('Chart workbook metadata exceeds the limit.');return true;
|
|
31
31
|
}});
|
|
32
|
-
const category=find(chart,'cat')[0],formula=category?.strRef?.f??category?.multiLvlStrRef?.f??category?.numRef?.f;
|
|
32
|
+
const category=find(chart,'cat')[0]??find(chart,'xVal')[0],formula=category?.strRef?.f??category?.multiLvlStrRef?.f??category?.numRef?.f;
|
|
33
33
|
// Infer a heading only for one contiguous vertical category range with a row
|
|
34
34
|
// above it. Other chart/workbook arrangements remain explicitly unrecovered.
|
|
35
35
|
const range=/^(?:'((?:[^']|'')+)'|([^'!]+))!\$?([A-Z]+)\$?(\d+):\$?([A-Z]+)\$?(\d+)$/i.exec(text(formula));
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface ChartDataUnplottableDiagnostic { code: "chart-data-unplottable"
|
|
|
40
40
|
/** Content with no PowerPoint export (an empty table, an unsupported payload) is replaced by a plain-language placeholder frame. */
|
|
41
41
|
export interface ContentPlaceholderDiagnostic { code: "content-placeholder"; path: string; message: string; reason: "table-has-no-rows" | "unsupported-payload" }
|
|
42
42
|
/** The chart data was reshaped to export a native chart: a histogram's values were binned, or a single value column was plotted against row numbers. */
|
|
43
|
+
export interface WatermarkNotExportedDiagnostic { code: "watermark-not-exported"; path: string; message: string }
|
|
43
44
|
export interface ChartDataAdaptedDiagnostic { code: "chart-data-adapted"; path: string; message: string; adaptation: "histogram-binned" | "row-numbers" }
|
|
44
45
|
|
|
45
46
|
export interface ToPptxOptions {
|
|
@@ -57,7 +58,7 @@ export interface ToPptxOptions {
|
|
|
57
58
|
/** Match preview/pagination clearance around supplied vector text outlines; default 1. */
|
|
58
59
|
textRasterPadding?: number;
|
|
59
60
|
/** Layout diagnostics, `media-provenance-omitted` when video data cannot be stored, plus `unresolved-font-scheme` (once per reference path) when a font-scheme id matches no record and the default `aptos` scheme is used as the base. */
|
|
60
|
-
onDiagnostic?: (diagnostic: LayoutDiagnostic | FontSchemeDiagnostic | MediaProvenanceDiagnostic | ChartDataUnplottableDiagnostic | ChartDataAdaptedDiagnostic | ContentPlaceholderDiagnostic) => void;
|
|
61
|
+
onDiagnostic?: (diagnostic: LayoutDiagnostic | FontSchemeDiagnostic | MediaProvenanceDiagnostic | ChartDataUnplottableDiagnostic | ChartDataAdaptedDiagnostic | ContentPlaceholderDiagnostic | WatermarkNotExportedDiagnostic) => void;
|
|
61
62
|
baseDir?: string;
|
|
62
63
|
compressionLevel?: number;
|
|
63
64
|
imageResolver?: (src: string, context: ImageResolverContext) => ImageResolverResult | Promise<ImageResolverResult | null | undefined> | null | undefined;
|