@openpresentation/opf-pptx 0.11.5 → 0.11.7
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/README.md +22 -8
- package/dist/background.js +15 -13
- package/dist/chart-types.js +5 -1
- package/dist/chartex.js +40 -24
- package/dist/code-provenance.js +1 -1
- package/dist/content-topology.js +295 -0
- package/dist/document-provenance.js +260 -46
- package/dist/furniture-provenance.js +57 -4
- package/dist/index.d.ts +21 -12
- package/dist/index.js +380 -65
- package/dist/logo-provenance.js +173 -0
- package/dist/sections.js +111 -0
- package/dist/watermark-provenance.js +2 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OPF PPTX
|
|
2
2
|
|
|
3
|
-
Version 0.11.5 writes the slide tag run as a theme color reference (`a:schemeClr accent1`) where the deck theme holds the primary there (no API change; the color is unchanged). Version 0.11.4 re-imports quote payloads (each native quote line carries an `OPF_QUOTE_V1` tag) and slide-image payloads from an unchanged export, writes the theme's `a:ea` and `a:cs` only for slots a script font is selected for, and requires core `^0.11.3` (the 70 legacy gallery layout ids and their geometry; install it with renderer 0.11.6 and editor 0.10.4 so export and preview resolve the same core; no API removed). Version 0.11.3 adds the opt-in `toPptx({ chartex: 'native' })` export of the seven chartex chart types and always imports chartex charts (no API removed; the default output is unchanged). 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).
|
|
3
|
+
Version 0.11.7 requires `@openpresentation/opf` ^0.11.4 and exports the design fields natively (cover and section logos, header and footer logos, picture bullets, the accent font), writes every chart's text at the size the preview draws, round-trips the slide content structure, sections, extensions, assets and brand images through PPTX provenance (a fresh export of a plain deck re-imports in its authored form, so read a root payload field before `blocks[0]`), writes slide sections as PowerPoint's section list, and degrades an SVG image to the preview's placeholder instead of aborting; install it with renderer 0.11.9 and editor 0.10.6 so export and preview resolve the same core (no API removed). Version 0.11.6 exports the treemap, histogram, pareto, box-and-whisker, waterfall and funnel charts as native chartex parts by default (`toPptx({chartex: 'auto'})`; `world` stays a clustered column with `chart-data-adapted`; pass `chartex: 'fallback'` for the previous output) and gives chartex text the deck's label colour and font (no API removed). Version 0.11.5 writes the slide tag run as a theme color reference (`a:schemeClr accent1`) where the deck theme holds the primary there (no API change; the color is unchanged). Version 0.11.4 re-imports quote payloads (each native quote line carries an `OPF_QUOTE_V1` tag) and slide-image payloads from an unchanged export, writes the theme's `a:ea` and `a:cs` only for slots a script font is selected for, and requires core `^0.11.3` (the 70 legacy gallery layout ids and their geometry; install it with renderer 0.11.6 and editor 0.10.4 so export and preview resolve the same core; no API removed). Version 0.11.3 adds the opt-in `toPptx({ chartex: 'native' })` export of the seven chartex chart types and always imports chartex charts (no API removed; the default output is unchanged). 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
|
|
|
@@ -55,6 +55,8 @@ await fs.promises.writeFile("quarterly-review.pptx", bytes);
|
|
|
55
55
|
|
|
56
56
|
`toPptx` returns a `Uint8Array` containing a PowerPoint-openable `.pptx`. It does not fetch remote assets. Data URI images and local paths can be embedded directly; hosts that need private asset loading should pass `imageResolver(src, context)`. Set `strictAssets: true` to turn unresolved or remote image assets into structured `OPFPptxError` failures instead of editable placeholder boxes.
|
|
57
57
|
|
|
58
|
+
Embedded picture bytes must be a PNG, JPEG, GIF or WebP. An SVG (or bytes that are no readable image) has no raster for PowerPoint, so, like an unresolved asset, it exports what the preview shows: the "Image unavailable" placeholder (nothing for a watermark, the background colour for a background image) and one `unresolved-asset` diagnostic with `reason: "unsupported-format"`; `strictAssets` throws `unsupported-image-dimensions`. To embed the artwork, return a raster from `imageResolver`; the exporter has no rasterizer of its own, and opf-render's `svgToPng(svg)` is the usual one: have `imageResolver` return `{data: await svgToPng(svgText), mediaType: "image/png"}` for an SVG source (the SVG text is yours to read from the data URI or file).
|
|
59
|
+
|
|
58
60
|
`fromPptx` parses an existing `.pptx` buffer locally and returns an OPF document that validates with `@openpresentation/opf`:
|
|
59
61
|
|
|
60
62
|
```js
|
|
@@ -80,9 +82,10 @@ The first exporter keeps the public API stable while using `pptxgenjs` internall
|
|
|
80
82
|
|
|
81
83
|
- `Slide.title`, `Slide.subtitle`, and `Slide.tag` become editable text boxes, not PowerPoint master placeholders.
|
|
82
84
|
- 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)). 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. The chartex types
|
|
84
|
-
- A chart whose data is one column of values (a histogram or dot plot) has no category column.
|
|
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.
|
|
85
|
+
- 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. The chartex types treemap, histogram, pareto, box & whisker, waterfall and funnel are written as native Office 2016 chartex parts (confirmed in desktop PowerPoint on 2026-09-30; see [Chartex charts](#chartex-charts-ff-22b)); the map (`world`) stays a clustered column chart by default and reports `chart-data-adapted` (`chartex-fallback`) until PowerPoint accepts its regionMap part (`chartex: "native"` writes it anyway, `chartex: "fallback"` writes clustered columns for every chartex id). A pie or doughnut chart, or a one-series chartex construct, plots only its first series and reports `chart-data-adapted` (`series-dropped`) when it has more.
|
|
86
|
+
- A chart whose data is one column of values (a histogram or dot plot) has no category column. A histogram or Pareto chart bins the values itself (PowerPoint's histogram with an explicit automatic bin count, no diagnostic) and re-imports as authored; any other chart type plots the values against their row numbers and reports `chart-data-adapted` (`row-numbers`). With `chartex: "fallback"` a histogram is binned into equal-width bins (Sturges' count, at most 50) and written as a column chart of the counts (`histogram-binned`; the binned counts do not round-trip). Cells parse as in every chart ("12%", "$5" and "1,234" count) and cells that hold no number are skipped, not plotted as 0. 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.
|
|
87
|
+
- 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. A picture's native description is the authored `alt` (never a stand-in such as `preencoded.png` or the file path), and a distinct asset `title` is the picture's native `title`; `fromPptx` reads both back.
|
|
88
|
+
- A chart cell that holds no number (`null`, an empty or non-numeric string, a boolean) is a gap in the native chart: its cache has no point at that row (`c:ptCount` keeps the row count), its workbook cell is blank, and it re-imports as `null`. An actual zero stays zero.
|
|
86
89
|
- 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).
|
|
87
90
|
- The package names only the document's chosen fonts. Chart text (data labels, axes, legend, titles) uses the chart slide's body font in `latin`/`ea`/`cs`; each embedded chart workbook uses the same fonts in its styles and theme; run `pitchFamily` follows the font scheme type (monospace is fixed pitch, serif is roman); and `docProps/app.xml` "Fonts Used" lists the fonts the package actually uses. The theme keeps PptxGenJS's per-script supplements (`THEME_SCRIPT_SUPPLEMENTS`) and empty `ea`/`cs` slots unless a script font was selected for that slot (see [Languages, right-to-left text and script fonts](#languages-right-to-left-text-and-script-fonts)).
|
|
88
91
|
- `checkPptxTypefaces(bytes, {fonts, monospace, themeScripts})` (optional `themeScripts: {major: {ea, cs}, minor: {ea, cs}}` names the East Asian / complex-script families the author selected; a selected theme slot that is empty or different is reported as `theme-script-slot`, an unselected one may stay empty) inventories every `typeface`, workbook font name and "Fonts Used" entry in every XML part, including nested packages, and reports each font outside that policy. `inventoryPptxTypefaces()` returns the raw inventory.
|
|
@@ -128,14 +131,16 @@ The first importer is mechanical and schema-compatible:
|
|
|
128
131
|
- Conditional table styles, merged-cell geometry, cell fills/borders/alignment, unsupported text fills/colors, and internal hyperlink actions are not fully reconstructed. `onDiagnostic` reports unsupported table style references, merges, fonts/colors/fills and links with native frame/cell paths. Table paths use the native graphic-frame and row indexes, including a header row. The shared core 0.6.0 layout sizes rows from their content and reports `text-overflow` when text cannot fit at the minimum size. These checks establish native XML conversion, not visual parity with PowerPoint.
|
|
129
132
|
- Unknown non-text shapes and unsupported graphic frames become editable text fallback blocks instead of failing the import.
|
|
130
133
|
- Catalog references (`design.theme`, `colorScheme`, `fontScheme`, `dimensions`, `background`, slide `layout`), slide ids and authoring metadata (`narrative`, `tone`, `audience`, `purpose`, `language`, `organization`, `speaker`, ...) are stored at export in `OPF_DOCUMENT_V1` / `OPF_SLIDE_V1` customer-data tags. Import restores a reference while the theme colors, theme fonts, slide size, background or slide arrangement it produced are unchanged. After an edit, the observed native values stay and `design-reference-changed` / `layout-reference-changed` name the reference. `toPptx` option `provenance: 'references-only' | false` limits or disables these invisible tags. Slide layout intent (layout id, type, composition, composition hints and the inline layout record) lives in each `OPF_SLIDE_V1` record, so a slide keeps its layout even without the document tag, for example when it is pasted into another deck (FF-29). See [document round trips](docs/document-roundtrip.md) for exactly what is embedded.
|
|
134
|
+
- The slide's content structure is part of `OPF_SLIDE_V1` too (`full` mode): nested `group` blocks, promoted regions (`left`, `top:left`, ...), a root payload (`text`, `items`, `chart`, ...) versus `blocks`, block `id`s and `extensions`, group `composition`, with the reference-pixel box of every leaf from the same composition the export drew. Import matches the native shapes to those boxes and rebuilds the authored form while the slide's arrangement is unchanged, so `slide.type` validates again; a slide whose blocks no longer fit the stored boxes keeps its flat blocks and reports `content-structure-changed` at `slides.N`; a duplicated slide reports `duplicate-block-id` and drops the repeated id. The document tag also stores `filename`, root `extensions`, slide `section` and `extensions`, `design.logo` (deck and slide) and the whole `assets` registry; a `data:` source that is not an exported picture is stored inline up to 256 KiB, so an organization logo or speaker photo given as a data URI round-trips.
|
|
135
|
+
- Slide `section` labels are written as PowerPoint's native section list (`p14:sectionLst` in `ppt/presentation.xml`, whatever the `provenance` option): one section per run of consecutive slides with the same label, runs without a label as `Default Section`, with deterministic ids. Import reads the list back (`Default Section` means no section); it wins over the stored value and over the section text a footer shows (`section-reference-changed` at `slides.N.section`), except that an edited footer line keeps its text while the list still equals the stored value. The native PowerPoint check of the sections pane and save/reopen is a separate gate (`scratchpad/spec-gaps-native/`).
|
|
131
136
|
|
|
132
137
|
There is no AI classification pass in the OSS runtime. Hosts can run optional cleanup or semantic remapping after `fromPptx` returns.
|
|
133
138
|
|
|
134
139
|
## Chartex charts (FF-22b)
|
|
135
140
|
|
|
136
|
-
**
|
|
141
|
+
**Modes.** `toPptx(document, {chartex})` takes `"auto"` (default), `"native"` or `"fallback"`. The native check of 2026-09-30 (desktop PowerPoint, read-only, the program's FF-22b deck set) opened every chartex deck without a repair prompt and reported the native `Chart.ChartType` for treemap (117), histogram (118, by value and by category), pareto (122), box-and-whisker (121), waterfall (119) and funnel (123); for `world` PowerPoint took the `mc:Fallback` (51, clustered column) when the choice required `cx5`. The recheck of the same day showed that PowerPoint accepts the regionMap part when the choice requires `cx4` (the 2016/5/10 region-map namespace; `cx3`, `cx4` and `cx6` reported ChartType 140, `cx5` and `cx8` fell back) but, with no `cx:geoCache`, shows "There was a problem getting the information for your map chart" and draws nothing until it can fetch map data online. Supervisor decision: `"auto"` writes the chartex parts for the six confirmed constructs and keeps `world` on the clustered column chart, which always renders, with `chart-data-adapted` (`chartex-fallback`); `"native"` writes every chartex part, the map included with `Requires="cx4"` and the `chart-map-geodata` diagnostic (provider data is never fabricated); `"fallback"` writes clustered columns only, byte for byte as before FF-22b (`test/fixtures/chartex-fallback-main.json` pins it, with `chartex-fallback` and `histogram-binned`). The same check showed chartex labels drawn in the chart style's theme grey and box lines in black on a dark theme, so every chartex text element and the style part now carry the deck's label colour and font explicitly, and box series carry a line in the label colour. Import of chartex parts is always on.
|
|
137
142
|
|
|
138
|
-
|
|
143
|
+
The seven kept chart types with no ECMA-376 construct are written as Office 2016 chartex parts (`ppt/charts/chartExN.xml`, `cx:chartSpace`, content type `application/vnd.ms-office.chartex+xml`), each with its chart style and colour style parts (`styleN.xml`, `colorsN.xml`) and the embedded workbook the data came from. PptxGenJS cannot write these parts, so the exporter first writes the classic clustered column chart of the same data and then post-processes the package (`src/chartex.js`): the slide's chart frame becomes an `mc:AlternateContent` whose `mc:Choice` (requiring the construct's chartex namespace) references the chartex part and whose `mc:Fallback` keeps the classic chart frame, so a reader without chartex support still shows the data as columns. Both parts share the workbook. `cx:series uniqueId` values are derived from the chart number, so the bytes stay deterministic.
|
|
139
144
|
|
|
140
145
|
| OPF id | `cx:series layoutId` | Data (category-major) | Notes |
|
|
141
146
|
|---|---|---|---|
|
|
@@ -145,7 +150,7 @@ With `chartex: "native"`, the seven kept chart types with no ECMA-376 construct
|
|
|
145
150
|
| `box-and-whisker` | `boxWhisker` | `[Category, S1, S2, ...]`, every series | rows with the same category form one box per series; exclusive quartiles, mean markers, outliers |
|
|
146
151
|
| `waterfall` | `waterfall` | `[Category, Value]`, first series | connector lines; increases and decreases in the first two palette colours; no subtotals |
|
|
147
152
|
| `funnel` | `funnel` | `[Category, Value]`, first series | value data labels |
|
|
148
|
-
| `world` | `regionMap` | `[Region, Value]`, first series | no `cx:geoCache`: PowerPoint
|
|
153
|
+
| `world` | `regionMap` (`chartex: "native"` only; `Requires="cx4"`) | `[Region, Value]`, first series | no `cx:geoCache`: PowerPoint accepts the part (ChartType 140) and must fetch the region shapes from its online map service; until it does it shows "There was a problem getting the information for your map chart" and draws nothing; reported as `chart-map-geodata`. By default (`"auto"`) the map stays a clustered column chart |
|
|
149
154
|
|
|
150
155
|
The chart area, label colour, label font (`latin`/`ea`/`cs` in the chart's body font) and series colours follow the classic charts. Re-import (`fromPptx`) reads the chartex part first: the layoutIds name the OPF id (an owned `paretoLine` is `pareto`), `cx:strDim type="cat"` restores the categories and each `cx:numDim` a series, under the same 100,000-point and 1,000,000-cell bounds as classic chart caches; a lone value column comes back as authored. A chartex part with no OPF construct (sunburst) falls back to the `mc:Fallback` chart, or to a text placeholder when PowerPoint's own text fallback is all there is. Native PowerPoint rendering of each construct is confirmed through the program's bounded native sample, not by this package's tests.
|
|
151
156
|
|
|
@@ -248,6 +253,15 @@ Slide-image treatments export from core's normalized geometry as native DrawingM
|
|
|
248
253
|
|
|
249
254
|
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`.
|
|
250
255
|
|
|
256
|
+
### Brand assets, picture bullets and the accent font (spec-gap closure A)
|
|
257
|
+
|
|
258
|
+
These design fields need the core that composes them (`resolveLogo`, `geometry.logo`, `item.bulletImage`, `fontScheme.accent`; core 0.11.4 or the coordinated source). On core 0.11.3 the export carries none of them and is byte-identical to the previous output. Each one is drawn at the box core composes, which the preview draws too (see core `docs/design-resolution.md`, "Brand assets and layout hints").
|
|
259
|
+
|
|
260
|
+
- **Cover and section logo** (`design.logo`, a slide's own `design.logo`, else the primary organization's `logo`). One native picture named `OPF logo`, after the watermark and before the content, fitted without cropping into core's `geometry.logo` box (56 px high at 720 px, up to four times as wide), anchored at the box's left edge and vertically centered, exactly the preview's `xMinYMid meet`. Its alt text is the asset's `alt`, otherwise `Logo`. A LogoSet picks its variant from the slide background (a dark background takes the light variants); the exporter's dark test is the one that already chooses the text colour. An unresolved or unreadable logo draws the preview's "Image unavailable" panel in the box and reports `unresolved-asset` at the logo's path (`strictAssets` throws). Content slides never get one. The picture carries an `OPF_LOGO_V1` tag (slide, source path, variant, the exact picture properties): an unchanged export consumes the picture on import (it is not content; `design.logo` itself returns from the document tag), and an edited, ambiguous or damaged one stays an ordinary picture and reports `invalid-logo-provenance`. When nothing else restored a logo (an export with `provenance: false`, or with `references-only`, which stores no sources), the consumed picture's own image becomes `design.logo` (the slide's own for a slide-level logo), so the logo is never lost; it is a single image, not the LogoSet. A PPTX with no OPF tags imports the picture as an ordinary image block and invents no `design.logo`.
|
|
261
|
+
- **Header and footer `logo: true`.** A generated image part in the part's box (the icon variant), exported like an `image` part with the furniture picture tag replaced by an `OPF_LOGO_V1` tag with role `furniture`. The slide's furniture manifest lists it under a separate `logos` key (`{kind, zone, drawn}`), never in `parts` or `definitions`, because every released importer validates those two strictly. It re-imports as `logo: true`, never as an `image` data URI; without a resolvable logo core reports `unresolved-content` at `<zone>.logo`, nothing is drawn and `logo: true` still returns (`drawn: false`).
|
|
262
|
+
- **Picture bullets** (`design.listBullet: image`, resolved to the icon logo by core as `item.bulletImage`). Every entry's first line carries `<a:buSzPct val="100000"/><a:buBlip><a:blip r:embed=…/></a:buBlip>` in place of the character bullet, so the marker is the text size, like the preview's square marker. PptxGenJS embeds the icon once per slide (a picture named `OPF bullet image`); packaging removes that picture and keeps the media part and its relationship for the `a:buBlip` elements. Marker indent and text positions are those of the character bullets. An icon that cannot be embedded keeps the character bullets and reports `unresolved-asset` once per slide at the logo's path. Import recognizes `a:buBlip` paragraphs as list paragraphs; `design.listBullet` returns from the stored design hint (a PPTX without tags has no hint). PowerPoint shows a picture bullet at the picture's own aspect ratio; use a square icon for the preview's square marker. The released 0.11.6 importer does not know `a:buBlip` and imports such a list as plain text lines.
|
|
263
|
+
- **`fontScheme.accent`.** Core resolves the accent role for the slide tag (eyebrow) and the quote body, so their runs carry the accent typeface in `a:latin`, `a:ea` and `a:cs` with the catalog's pitch family when the family is known; theme major/minor fonts, every other run and the theme are unchanged. The accent family appears in `docProps/app.xml` "Fonts Used" like any other used font; `checkPptxTypefaces` callers list it in `options.fonts`. `design.fontScheme` (with `accent`) returns from the document tag.
|
|
264
|
+
|
|
251
265
|
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.
|
|
252
266
|
|
|
253
267
|
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.
|
|
@@ -271,7 +285,7 @@ Node conversion lazily loads the pinned open-source Sharp dependency and require
|
|
|
271
285
|
|
|
272
286
|
## Native background fills
|
|
273
287
|
|
|
274
|
-
Fixed solid and linear-gradient backgrounds now export as native slide fills, keeping the background editable without rasterizing slide content. Deck defaults, inline theme overrides and per-slide overrides are resolved before export. Solid opacity, gradient stop colors/positions and combined color/background alpha are preserved. Empty and single-stop gradients follow the SVG preview's transparent/solid behavior; descending stop positions clamp to the preceding stop.
|
|
288
|
+
Fixed solid and linear-gradient backgrounds now export as native slide fills, keeping the background editable without rasterizing slide content. Deck defaults, inline theme overrides and per-slide overrides are resolved before export. Solid opacity, gradient stop colors/positions and combined color/background alpha are preserved. Solid, gradient-stop and pattern colors are ColorRefs, resolved like table fills and run colors: a `var:` variable or a colour-scheme slot or role name (`accent2`, `primary`) paints its colour, and a slot the deck theme holds exactly is written as `a:schemeClr`. The slide's default text contrast follows the resolved colour. Empty and single-stop gradients follow the SVG preview's transparent/solid behavior; descending stop positions clamp to the preceding stop.
|
|
275
289
|
|
|
276
290
|
Diagonal gradients require a coordinate conversion: the preview uses an SVG object-bounding-box gradient, while native unscaled DrawingML angles use slide coordinates. Export converts both the physical gradient direction and stop interval. Tests compare 990 sample positions from serialized SVG/native properties across 33 gradients and three aspect ratios, plus solid opacity, inheritance, native edits and repeated imports/exports. Integer native angles/positions introduce small rounding differences. The mapping follows the [DrawingML linear-gradient angle definition](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.drawing.lineargradientfill?view=openxml-3.0.1).
|
|
277
291
|
|
package/dist/background.js
CHANGED
|
@@ -33,21 +33,23 @@ export const nativePatternPreset = preset => presetPatterns.has(preset) ? preset
|
|
|
33
33
|
// The SVG preview paints the pattern background color and foreground marks,
|
|
34
34
|
// defaulting to white and the slide text color. Other engine-defined presets
|
|
35
35
|
// have no DrawingML equivalent; like the preview, only their background color remains.
|
|
36
|
-
// `
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
36
|
+
// `resolve(reference)` turns a ColorRef (a `var:` variable, a colour-scheme slot or
|
|
37
|
+
// role name) into a hex colour, as for table fills and run colours; literals pass
|
|
38
|
+
// through. `scheme(reference, hex)` names the theme color for an authored slot/role
|
|
39
|
+
// reference whose drawn color the deck theme holds exactly (FF-24); solid, gradient
|
|
40
|
+
// and pattern colors then become a:schemeClr, keeping any background opacity as alpha.
|
|
41
|
+
export function nativeBackgroundFill(background, {width, height}, fallback = 'FFFFFF', foreground = '000000', scheme = () => undefined, resolve = value => value) {
|
|
40
42
|
if (typeof background === 'string' && /^#[\da-f]{3}(?:[\da-f]{3}(?:[\da-f]{2})?)?$/i.test(background)) background = {type: 'solid', color: background};
|
|
41
43
|
if (!background || typeof background !== 'object') return null;
|
|
42
44
|
const opacity = background.opacity ?? 1;
|
|
43
|
-
|
|
45
|
+
const paint = (value, base) => {
|
|
46
|
+
const hex = resolve(value), c = color(hex, base), themeValue = c.alpha === 1 ? scheme(value, c.hex) : undefined;
|
|
47
|
+
if (!themeValue) return colorXml(hex, opacity, base);
|
|
48
|
+
const alpha = Math.round(clamp(opacity) * 100000);
|
|
49
|
+
return `<a:schemeClr val="${themeValue}">${alpha === 100000 ? '' : `<a:alpha val="${alpha}"/>`}</a:schemeClr>`;
|
|
50
|
+
};
|
|
51
|
+
if (background.type === 'solid' || background.type === 'theme') return `<a:solidFill>${background.type === 'theme' ? colorXml(fallback, opacity, fallback) : paint(background.color, fallback)}</a:solidFill>`;
|
|
44
52
|
if (background.type === 'pattern') {
|
|
45
|
-
const paint = (value, base) => {
|
|
46
|
-
const c = color(value, base), themeValue = c.alpha === 1 ? scheme(value, c.hex) : undefined;
|
|
47
|
-
if (!themeValue) return colorXml(value, opacity, base);
|
|
48
|
-
const alpha = Math.round(clamp(opacity) * 100000);
|
|
49
|
-
return `<a:schemeClr val="${themeValue}">${alpha === 100000 ? '' : `<a:alpha val="${alpha}"/>`}</a:schemeClr>`;
|
|
50
|
-
};
|
|
51
53
|
const pattern = background.pattern ?? {}, back = paint(pattern.backgroundColor, 'FFFFFF');
|
|
52
54
|
const preset = nativePatternPreset(pattern.preset);
|
|
53
55
|
if (!preset) return `<a:solidFill>${back}</a:solidFill>`;
|
|
@@ -56,7 +58,7 @@ export function nativeBackgroundFill(background, {width, height}, fallback = 'FF
|
|
|
56
58
|
if (background.type !== 'gradient') return null;
|
|
57
59
|
const stops = background.gradient?.stops ?? [];
|
|
58
60
|
if (!stops.length) return '<a:noFill/>';
|
|
59
|
-
if (stops.length === 1) return `<a:solidFill>${
|
|
61
|
+
if (stops.length === 1) return `<a:solidFill>${paint(stops[0].color, fallback)}</a:solidFill>`;
|
|
60
62
|
const radians = turn(background.gradient?.angle ?? 0) * Math.PI / 180;
|
|
61
63
|
const c = Math.cos(radians), s = Math.sin(radians), span = Math.abs(c) + Math.abs(s);
|
|
62
64
|
const angle = Math.round(turn(Math.atan2(s / height, c / width) * 180 / Math.PI) * 60000) % 21600000;
|
|
@@ -65,7 +67,7 @@ export function nativeBackgroundFill(background, {width, height}, fallback = 'FF
|
|
|
65
67
|
// SVG clamps a descending stop to the preceding position.
|
|
66
68
|
prior = Math.max(prior, stop.position);
|
|
67
69
|
const position = Math.round(((prior - .5) / span + .5) * 100000);
|
|
68
|
-
return `<a:gs pos="${position}">${
|
|
70
|
+
return `<a:gs pos="${position}">${paint(stop.color, fallback)}</a:gs>`;
|
|
69
71
|
}).join('');
|
|
70
72
|
return `<a:gradFill rotWithShape="0"><a:gsLst>${nativeStops}</a:gsLst><a:lin ang="${angle}" scaled="0"/></a:gradFill>`;
|
|
71
73
|
}
|
package/dist/chart-types.js
CHANGED
|
@@ -39,7 +39,11 @@ export const CHART_TYPES = Object.freeze({
|
|
|
39
39
|
'box-and-whisker': chartex('boxWhisker', 'BoxAndWhisker', { requires: 'cx1', series: Infinity, axes: true }),
|
|
40
40
|
waterfall: chartex('waterfall', 'Waterfall', { requires: 'cx1', series: 1, axes: true }),
|
|
41
41
|
funnel: chartex('funnel', 'Funnel', { requires: 'cx2', series: 1, axes: true }),
|
|
42
|
-
|
|
42
|
+
// Native check 2026-09-30: PowerPoint accepts the regionMap part only when the choice requires cx4 (2016/5/10, the
|
|
43
|
+
// region-map extension namespace; cx5 and cx8 fall back), reports ChartType 140, and then draws nothing without a
|
|
44
|
+
// populated cx:geoCache ("There was a problem getting the information for your map chart"), so the map stays on the
|
|
45
|
+
// clustered column fallback in 'auto' mode and is written only with `chartex: 'native'`.
|
|
46
|
+
world: chartex('regionMap', 'Map', { requires: 'cx4', dimension: 'colorVal', series: 1, unconfirmed: true }),
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
function chartex(layoutId, aspose, extra) {
|
package/dist/chartex.js
CHANGED
|
@@ -115,9 +115,20 @@ function layoutProperties(spec, series, hasCategories) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// Text properties for every chartex text element. PowerPoint resolves chartex
|
|
119
|
+
// label colours from the chart style part and each element's own cx:txPr, not
|
|
120
|
+
// from the chartSpace txPr alone (native check 2026-09-30: labels came out in
|
|
121
|
+
// the style's tx1 grey on a dark theme), so every axis, data label set and
|
|
122
|
+
// legend carries the deck's label colour and font explicitly, like the classic
|
|
123
|
+
// chart path writes into every c:txPr.
|
|
124
|
+
function textProperties(labelColor, font, size) {
|
|
125
|
+
const face = escapeXml(font);
|
|
126
|
+
return `<cx:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="${size}">${solidFill(labelColor)}<a:latin typeface="${face}"/><a:ea typeface="${face}"/><a:cs typeface="${face}"/></a:defRPr></a:pPr><a:endParaRPr lang="en-US"/></a:p></cx:txPr>`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function dataLabels(spec, text) {
|
|
130
|
+
if (spec.layoutId === 'treemap') return `<cx:dataLabels pos="ctr">${text}<cx:visibility seriesName="0" categoryName="1" value="0"/></cx:dataLabels>`;
|
|
131
|
+
if (spec.layoutId === 'funnel') return `<cx:dataLabels pos="ctr">${text}<cx:visibility seriesName="0" categoryName="0" value="1"/></cx:dataLabels>`;
|
|
121
132
|
return '';
|
|
122
133
|
}
|
|
123
134
|
|
|
@@ -129,14 +140,15 @@ export function chartexPointColors(layoutId, values, palette) {
|
|
|
129
140
|
return values.map(() => null);
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
function axes(spec, gridColor) {
|
|
143
|
+
function axes(spec, gridColor, text) {
|
|
133
144
|
if (!spec.axes) return '';
|
|
134
145
|
const gridlines = `<cx:majorGridlines><cx:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="${gridColor}"><a:alpha val="70000"/></a:srgbClr></a:solidFill></a:ln></cx:spPr></cx:majorGridlines>`;
|
|
135
146
|
const gapWidth = {clusteredColumn: '0.06', paretoLine: '0.06', boxWhisker: '1', waterfall: '0.5', funnel: '0.06'}[spec.layoutId] ?? '1';
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
147
|
+
// CT_Axis order: scaling, units, majorGridlines, tickLabels, spPr, txPr.
|
|
148
|
+
const category = `<cx:axis id="0"><cx:catScaling gapWidth="${gapWidth}"/><cx:tickLabels/>${text}</cx:axis>`;
|
|
149
|
+
if (spec.layoutId === 'funnel') return `${category}<cx:axis id="1" hidden="1"><cx:valScaling/><cx:tickLabels/>${text}</cx:axis>`;
|
|
150
|
+
const value = `<cx:axis id="1"><cx:valScaling/>${gridlines}<cx:tickLabels/>${text}</cx:axis>`;
|
|
151
|
+
const percentage = spec.layoutId === 'paretoLine' ? `<cx:axis id="2"><cx:valScaling max="1" min="0"/><cx:units unit="percentage"/><cx:tickLabels/>${text}</cx:axis>` : '';
|
|
140
152
|
return `${category}${value}${percentage}`;
|
|
141
153
|
}
|
|
142
154
|
|
|
@@ -147,7 +159,7 @@ function axes(spec, gridColor) {
|
|
|
147
159
|
* layout is PptxGenJS's: Sheet1, headings in row 1, categories in column A and
|
|
148
160
|
* one series per following column.
|
|
149
161
|
*/
|
|
150
|
-
export function chartexPartXml({spec, series, hasCategories, number, workbookRelId, fill, labelColor, gridColor, font, palette}) {
|
|
162
|
+
export function chartexPartXml({spec, series, hasCategories, number, workbookRelId, fill, labelColor, gridColor, font, palette, textSize}) {
|
|
151
163
|
const rows = series[0].labels.length;
|
|
152
164
|
const range = (letter) => `Sheet1!$${letter}$2:$${letter}$${rows + 1}`;
|
|
153
165
|
const categories = hasCategories
|
|
@@ -157,29 +169,30 @@ export function chartexPartXml({spec, series, hasCategories, number, workbookRel
|
|
|
157
169
|
const points = entry.values.map((value, row) => value === null || !Number.isFinite(value) ? '' : `<cx:pt idx="${row}">${numberText(value)}</cx:pt>`).join('');
|
|
158
170
|
return `<cx:data id="${index}">${categories}<cx:numDim type="${spec.dimension}"><cx:f>${range(columnLetters(index + 2))}</cx:f><cx:lvl ptCount="${rows}" formatCode="General">${points}</cx:lvl></cx:numDim></cx:data>`;
|
|
159
171
|
}).join('');
|
|
172
|
+
const text = textProperties(labelColor, font, textSize);
|
|
160
173
|
const plotted = series.map((entry, index) => {
|
|
161
174
|
const color = palette[index % palette.length];
|
|
162
175
|
const letter = columnLetters(index + 2);
|
|
163
176
|
const pointFills = chartexPointColors(spec.layoutId, entry.values, palette)
|
|
164
177
|
.map((pointColor, row) => pointColor ? `<cx:dataPt idx="${row}"><cx:spPr>${solidFill(pointColor)}</cx:spPr></cx:dataPt>` : '').join('');
|
|
178
|
+
// Box whiskers, median lines and mean markers are drawn with the series line; without one PowerPoint used black (native check 2026-09-30).
|
|
179
|
+
const line = spec.layoutId === 'boxWhisker' ? `<a:ln w="9525">${solidFill(labelColor)}</a:ln>` : '';
|
|
165
180
|
return `<cx:series layoutId="${spec.owner ?? spec.layoutId}" uniqueId="${seriesUniqueId(number, index)}">` +
|
|
166
181
|
`<cx:tx><cx:txData><cx:f>Sheet1!$${letter}$1</cx:f><cx:v>${escapeXml(entry.name)}</cx:v></cx:txData></cx:tx>` +
|
|
167
|
-
`<cx:spPr>${solidFill(color)}</cx:spPr>${pointFills}${dataLabels(spec)}<cx:dataId val="${index}"/>${layoutProperties(spec, entry, hasCategories)}` +
|
|
182
|
+
`<cx:spPr>${solidFill(color)}${line}</cx:spPr>${pointFills}${dataLabels(spec, text)}<cx:dataId val="${index}"/>${layoutProperties(spec, entry, hasCategories)}` +
|
|
168
183
|
(spec.axes ? '<cx:axisId val="0"/><cx:axisId val="1"/>' : '') + '</cx:series>';
|
|
169
184
|
});
|
|
170
185
|
if (spec.owner) {
|
|
171
186
|
// The Office Pareto chart: the cumulative-percentage line owned by the binned columns, on the percentage axis.
|
|
172
187
|
plotted.push(`<cx:series layoutId="${spec.layoutId}" ownerIdx="0" uniqueId="${seriesUniqueId(number, series.length)}"><cx:spPr><a:ln w="19050"><a:solidFill><a:srgbClr val="${palette[1 % palette.length]}"/></a:solidFill></a:ln></cx:spPr><cx:axisId val="0"/><cx:axisId val="2"/></cx:series>`);
|
|
173
188
|
}
|
|
174
|
-
const legend = spec.layoutId === 'boxWhisker' && series.length > 1 ?
|
|
189
|
+
const legend = spec.layoutId === 'boxWhisker' && series.length > 1 ? `<cx:legend pos="r" align="ctr" overlay="0">${text}</cx:legend>` : '';
|
|
175
190
|
const alpha = fill.transparency > 0 ? Math.round((100 - fill.transparency) * 1000) : undefined;
|
|
176
|
-
const face = escapeXml(font);
|
|
177
191
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
|
178
192
|
`<cx:chartSpace xmlns:a="${NS.a}" xmlns:r="${NS.r}" xmlns:cx="${NS.cx}">` +
|
|
179
193
|
`<cx:chartData><cx:externalData r:id="${workbookRelId}" cx:autoUpdate="0"/>${data}</cx:chartData>` +
|
|
180
|
-
`<cx:chart><cx:plotArea><cx:plotAreaRegion>${plotted.join('')}</cx:plotAreaRegion>${axes(spec, gridColor)}</cx:plotArea>${legend}</cx:chart>` +
|
|
181
|
-
`<cx:spPr>${solidFill(fill.color, alpha)}<a:ln><a:noFill/></a:ln></cx:spPr
|
|
182
|
-
`<cx:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="900">${solidFill(labelColor)}<a:latin typeface="${face}"/><a:ea typeface="${face}"/><a:cs typeface="${face}"/></a:defRPr></a:pPr><a:endParaRPr lang="en-US"/></a:p></cx:txPr>` +
|
|
194
|
+
`<cx:chart><cx:plotArea><cx:plotAreaRegion>${plotted.join('')}</cx:plotAreaRegion>${axes(spec, gridColor, text)}</cx:plotArea>${legend}</cx:chart>` +
|
|
195
|
+
`<cx:spPr>${solidFill(fill.color, alpha)}<a:ln><a:noFill/></a:ln></cx:spPr>${text}` +
|
|
183
196
|
'</cx:chartSpace>';
|
|
184
197
|
}
|
|
185
198
|
|
|
@@ -201,18 +214,21 @@ const STYLE_ENTRIES = [
|
|
|
201
214
|
'dataPointMarkerLayout', 'dataPointWireframe', 'dataTable', 'downBar', 'dropLine', 'errorBar', 'floor', 'gridlineMajor', 'gridlineMinor', 'hiLoLine',
|
|
202
215
|
'leaderLine', 'legend', 'plotArea', 'plotArea3D', 'seriesAxis', 'seriesLine', 'title', 'trendline', 'trendlineLabel', 'upBar', 'valueAxis', 'wall',
|
|
203
216
|
];
|
|
204
|
-
export function chartStyleXml() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
217
|
+
export function chartStyleXml({labelColor = '000000', gridColor = '000000', font = 'Aptos', textSize} = {}) {
|
|
218
|
+
// Text and chrome colours are the deck's label and border colours (what the classic chart path writes), not the
|
|
219
|
+
// theme's tx1: PowerPoint applies the style part to chartex labels that carry no cx:txPr of their own.
|
|
220
|
+
const textColor = `<a:srgbClr val="${labelColor}"/>`;
|
|
221
|
+
const face = escapeXml(font);
|
|
222
|
+
const faintLine = `<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:srgbClr val="${gridColor}"><a:alpha val="70000"/></a:srgbClr></a:solidFill><a:round/></a:ln>`;
|
|
223
|
+
const entry = (name, {fillIdx = '0', fillColor = '', fontColor = textColor, spPr = '', size = '', mods = ''} = {}) =>
|
|
224
|
+
`<cs:${name}${mods ? ` mods="${mods}"` : ''}><cs:lnRef idx="0"/><cs:fillRef idx="${fillIdx}">${fillColor}</cs:fillRef><cs:effectRef idx="0"/><cs:fontRef idx="minor">${fontColor}</cs:fontRef>${spPr ? `<cs:spPr>${spPr}</cs:spPr>` : ''}${size ? `<cs:defRPr sz="${size}" kern="1200">${solidFill(labelColor)}<a:latin typeface="${face}"/><a:ea typeface="${face}"/><a:cs typeface="${face}"/></cs:defRPr>` : ''}</cs:${name}>`;
|
|
209
225
|
const entries = STYLE_ENTRIES.map(name => {
|
|
210
226
|
switch (name) {
|
|
211
227
|
case 'dataPointMarkerLayout': return '<cs:dataPointMarkerLayout symbol="circle" size="5"/>';
|
|
212
228
|
case 'axisTitle': return entry(name, {fontColor: textColor, size: '1000'});
|
|
213
|
-
case 'categoryAxis': case 'valueAxis': case 'seriesAxis': return entry(name, {fontColor: textColor, spPr: faintLine, size:
|
|
214
|
-
case 'chartArea': return entry(name, {mods: 'allowNoFillOverride allowNoLineOverride', spPr: `<a:solidFill><a:schemeClr val="bg1"/></a:solidFill>${faintLine}`, size:
|
|
215
|
-
case 'dataLabel': case 'dataLabelCallout': case 'dataTable': case 'legend': case 'trendlineLabel': return entry(name, {fontColor: textColor, size:
|
|
229
|
+
case 'categoryAxis': case 'valueAxis': case 'seriesAxis': return entry(name, {fontColor: textColor, spPr: faintLine, size: textSize});
|
|
230
|
+
case 'chartArea': return entry(name, {mods: 'allowNoFillOverride allowNoLineOverride', spPr: `<a:solidFill><a:schemeClr val="bg1"/></a:solidFill>${faintLine}`, size: textSize});
|
|
231
|
+
case 'dataLabel': case 'dataLabelCallout': case 'dataTable': case 'legend': case 'trendlineLabel': return entry(name, {fontColor: textColor, size: textSize});
|
|
216
232
|
case 'dataPoint': case 'dataPoint3D': case 'dataPointWireframe': case 'upBar': case 'floor': case 'wall':
|
|
217
233
|
return entry(name, {fillIdx: '1', fillColor: '<cs:styleClr val="auto"/>', spPr: '<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'});
|
|
218
234
|
case 'dataPointLine': case 'dataPointMarker': case 'trendline': case 'seriesLine': case 'hiLoLine': case 'dropLine': case 'leaderLine': case 'errorBar':
|
|
@@ -278,7 +294,7 @@ export function attachChartexParts(entries, chartex, parseRelationships) {
|
|
|
278
294
|
const base = `ppt/charts/chartEx${number}.xml`;
|
|
279
295
|
entries[base] = encoder.encode(chartexPartXml({...chart, number, workbookRelId: 'rId1'}));
|
|
280
296
|
entries[`ppt/charts/_rels/chartEx${number}.xml.rels`] = encoder.encode(chartexRelationshipsXml({workbookTarget: workbook.target, number}));
|
|
281
|
-
entries[`ppt/charts/style${number}.xml`] = encoder.encode(chartStyleXml());
|
|
297
|
+
entries[`ppt/charts/style${number}.xml`] = encoder.encode(chartStyleXml({labelColor: chart.labelColor, gridColor: chart.gridColor, font: chart.font, textSize: chart.textSize}));
|
|
282
298
|
entries[`ppt/charts/colors${number}.xml`] = encoder.encode(chartColorStyleXml());
|
|
283
299
|
overrides.push(`<Override PartName="/${base}" ContentType="${CHARTEX_CONTENT_TYPES.chart}"/>`,
|
|
284
300
|
`<Override PartName="/ppt/charts/style${number}.xml" ContentType="${CHARTEX_CONTENT_TYPES.style}"/>`,
|
package/dist/code-provenance.js
CHANGED
|
@@ -86,7 +86,7 @@ export function nativeShapeParagraphs(xml, rootElement = 'p:sld') {
|
|
|
86
86
|
}
|
|
87
87
|
if (child['a:pPr'] !== undefined) {
|
|
88
88
|
level = Number(child[':@']?.lvl ?? 0);
|
|
89
|
-
bullet = child['a:pPr'].some(node=>node['a:buChar'] !== undefined || node['a:buAutoNum'] !== undefined);
|
|
89
|
+
bullet = child['a:pPr'].some(node=>node['a:buChar'] !== undefined || node['a:buBlip'] !== undefined || node['a:buAutoNum'] !== undefined);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return {text,maxFontSize,bullet,level,fields};
|