@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 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 (treemap, histogram, pareto, box & whisker, waterfall, funnel, map) export by default as clustered columns and report `chart-data-adapted` (`chartex-fallback`); with `chartex: "native"` they are written as native Office 2016 chartex parts (see [Chartex charts](#chartex-charts-ff-22b), opt-in pending native PowerPoint confirmation). A pie or doughnut chart (and, natively, a one-series chartex construct) plots only its first series and reports `chart-data-adapted` (`series-dropped`) when it has more.
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`: by default 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), and any other chart type plots the values against their row numbers (`row-numbers`). With `chartex: "native"` 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. 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.
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
- **Opt-in.** `toPptx(document, {chartex: "native"})` enables the native chartex export described here. The default, `chartex: "fallback"`, keeps the clustered column export of the seven chartex ids byte for byte (`test/fixtures/chartex-fallback-main.json` pins it) with the `chartex-fallback` and `histogram-binned` diagnostics, because a malformed chartex part would make PowerPoint offer to repair the whole deck, and the parts are not yet confirmed in native PowerPoint (no Open XML SDK validation either). The default flips once the program's native check of the FF-22b deck set passes. Import of chartex parts is always on.
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
- With `chartex: "native"`, 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.
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 matches the region names and fetches the shapes from Bing Maps when the deck is opened online, and reports `chart-map-geodata`; offline, or where Office map data is disabled, the map shows no regions |
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
 
@@ -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
- // `scheme(reference, hex)` names the theme color for an authored slot/role
37
- // reference whose drawn color the deck theme holds exactly (FF-24); pattern
38
- // colors then become a:schemeClr, keeping any background opacity as alpha.
39
- export function nativeBackgroundFill(background, {width, height}, fallback = 'FFFFFF', foreground = '000000', scheme = () => undefined) {
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
- if (background.type === 'solid' || background.type === 'theme') return `<a:solidFill>${colorXml(background.type === 'theme' ? fallback : background.color, opacity, fallback)}</a:solidFill>`;
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>${colorXml(stops[0].color, opacity, fallback)}</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}">${colorXml(stop.color, opacity, fallback)}</a:gs>`;
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
  }
@@ -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
- world: chartex('regionMap', 'Map', { requires: 'cx5', dimension: 'colorVal', series: 1 }),
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
- function dataLabels(spec) {
119
- if (spec.layoutId === 'treemap') return '<cx:dataLabels pos="inEnd"><cx:visibility seriesName="0" categoryName="1" value="0"/></cx:dataLabels>';
120
- if (spec.layoutId === 'funnel') return '<cx:dataLabels pos="ctr"><cx:visibility seriesName="0" categoryName="0" value="1"/></cx:dataLabels>';
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
- const category = `<cx:axis id="0"><cx:catScaling gapWidth="${gapWidth}"/><cx:tickLabels/></cx:axis>`;
137
- if (spec.layoutId === 'funnel') return `${category}<cx:axis id="1" hidden="1"><cx:valScaling/><cx:tickLabels/></cx:axis>`;
138
- const value = `<cx:axis id="1"><cx:valScaling/>${gridlines}<cx:tickLabels/></cx:axis>`;
139
- const percentage = spec.layoutId === 'paretoLine' ? '<cx:axis id="2"><cx:valScaling max="1" min="0"/><cx:units unit="percentage"/><cx:tickLabels/></cx:axis>' : '';
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 ? '<cx:legend pos="r" align="ctr" overlay="0"/>' : '';
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
- const textColor = '<a:schemeClr val="tx1"><a:lumMod val="65000"/><a:lumOff val="35000"/></a:schemeClr>';
206
- const faintLine = '<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="tx1"><a:lumMod val="15000"/><a:lumOff val="85000"/></a:schemeClr></a:solidFill><a:round/></a:ln>';
207
- const entry = (name, {fillIdx = '0', fillColor = '', fontColor = '<a:schemeClr val="tx1"/>', spPr = '', size = '', mods = ''} = {}) =>
208
- `<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"/>` : ''}</cs:${name}>`;
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: '900'});
214
- case 'chartArea': return entry(name, {mods: 'allowNoFillOverride allowNoLineOverride', spPr: `<a:solidFill><a:schemeClr val="bg1"/></a:solidFill>${faintLine}`, size: '1000'});
215
- case 'dataLabel': case 'dataLabelCallout': case 'dataTable': case 'legend': case 'trendlineLabel': return entry(name, {fontColor: textColor, size: '900'});
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}"/>`,
@@ -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};