| 1 | > See [`executor-base.md`](./executor-base.md) for the Shape-first page authority and [`shared-standards-core.md`](./shared-standards-core.md) for the mandatory SVG foundation. |
| 2 | |
| 3 | # Native Data Interface |
| 4 | |
| 5 | Sole conditional interface for preset pattern fills and PowerPoint-native chart/table replacement eligibility, markers, metadata schemas, and export activation. Load only when either feature is selected for the authored SVG. |
| 6 | |
| 7 | ## 1. Pattern Fill — `<pattern>` with PPTX preset annotation |
| 8 | |
| 9 | `<pattern>` requests one fixed DrawingML preset; the converter does not render |
| 10 | the tile's arbitrary geometry. Use this interface only when that preset mapping |
| 11 | is intended. |
| 12 | |
| 13 | `data-pptx-pattern="<preset>"` is the generated default for selecting the |
| 14 | intended preset from the enum below. The converter retains an `ltUpDiag` |
| 15 | fallback when the annotation is absent; the checker reports that fallback as a |
| 16 | non-blocking fidelity warning. Invalid explicit preset names remain errors |
| 17 | because they violate the closed OOXML enum. |
| 18 | |
| 19 | Pattern colors may come from importer metadata (`data-pptx-fg` / |
| 20 | `data-pptx-bg`) or from the pattern's child paint. Without metadata, the first |
| 21 | child `<rect>` fill becomes the background and the first stroke (or other fill) |
| 22 | becomes the foreground. A missing background defaults to white; a missing |
| 23 | foreground means no native pattern fill can be emitted. The child geometry |
| 24 | itself is never used as a repeatable tile. |
| 25 | |
| 26 | **Valid `data-pptx-pattern` values** (OOXML `ST_PresetPatternVal` — closed enum, anything outside makes PowerPoint open with "needs to be repaired"): |
| 27 | |
| 28 | | Category | Values | |
| 29 | |---|---| |
| 30 | | Grids | `smGrid` · `lgGrid` · `dotGrid` *(no `ltGrid` — common typo)* | |
| 31 | | Diagonal lines | `ltUpDiag` · `ltDnDiag` · `dkUpDiag` · `dkDnDiag` · `wdUpDiag` · `wdDnDiag` · `dashUpDiag` · `dashDnDiag` · `diagCross` | |
| 32 | | Horizontal / vertical lines | `horz` · `vert` · `ltHorz` · `ltVert` · `dkHorz` · `dkVert` · `narHorz` · `narVert` · `dashHorz` · `dashVert` · `cross` | |
| 33 | | Percent fills | `pct5` · `pct10` · `pct20` · `pct25` · `pct30` · `pct40` · `pct50` · `pct60` · `pct70` · `pct75` · `pct80` · `pct90` | |
| 34 | | Checks & confetti | `smCheck` · `lgCheck` · `smConfetti` · `lgConfetti` | |
| 35 | | Decorative | `horzBrick` · `diagBrick` · `weave` · `plaid` · `trellis` · `zigZag` · `wave` · `sphere` · `divot` · `shingle` · `solidDmnd` · `openDmnd` · `dotDmnd` | |
| 36 | |
| 37 | `svg_quality_checker.py` warns when a referenced pattern lacks the annotation; |
| 38 | it errors when the pattern uses `patternTransform` or names a preset outside |
| 39 | this enum. |
| 40 | |
| 41 | ## 2. PowerPoint-Native Chart / Table Replacement Markers (Opt-in) |
| 42 | |
| 43 | [`executor-base.md`](./executor-base.md) remains the single Shape-first authoring authority: the complete visible SVG fallback is required regardless of native eligibility. This section only adds dormant replacement metadata to independently selected objects and defines how export may activate it. |
| 44 | |
| 45 | **Hard rule — selected-object authoring**: write the marker and JSON metadata in |
| 46 | the same edit for every supported chart and pure text-grid table; both are |
| 47 | native-ready by default. An unactivated marker changes no export, so never skip |
| 48 | an eligible object because the current request did not ask for native output. |
| 49 | The Default route reads the exact semantic object key from §IX |
| 50 | `Native-ready: <object-key>=yes|no; ...`; Quick assigns the same page-local |
| 51 | `kebab-case` key in active context before drawing. Use that key as the marker |
| 52 | group `id` and metadata `name`. A catalog `family/key`, family name, §VII row, |
| 53 | numeric content, or another ready object on the page never implies eligibility. |
| 54 | A `<object-key>=no` entry and unlisted incidental microvisuals stay on the SVG |
| 55 | fallback route. Canonical rectangular merged text cells may use the narrow |
| 56 | `row_span` / `col_span` contract below; graphical cells stay unmarked. The |
| 57 | marker group supplies visible fallback children for browser rendering and JSON |
| 58 | metadata for `svg_to_pptx` native export. |
| 59 | |
| 60 | A legacy bare `Native-ready: yes|no` maps only to the page's sole eligible |
| 61 | object. Zero or multiple eligible objects make it ambiguous and require |
| 62 | upstream repair. |
| 63 | |
| 64 | **MUST — atomic authoring**: treat one object's visible SVG fallback, parent |
| 65 | `data-pptx-replace-with` marker, and single JSON `<metadata>` child as one |
| 66 | authoring unit. Write all three while that object's data is in context. Do not |
| 67 | defer the marker or metadata to `verify-charts`, the final quality gate, or |
| 68 | export. |
| 69 | |
| 70 | **Hard rule — activation is the opt-in, dormant unless exported with `--native-charts-and-tables`**: A marker only declares that a group is eligible for PowerPoint-native Chart/Table replacement. Normal `svg_to_pptx.py` runs keep the fallback SVG children and convert them into independently editable DrawingML shapes. Pass `--native-charts-and-tables` only when the data source and chart/table-specific object model matter more than cross-renderer layout fidelity: it emits the PowerPoint Chart/Table object and skips the fallback children to avoid duplicates. Native styling preserves the core palette, text, axis, grid, and background colors where possible, but it is still a PowerPoint Chart/Table object rather than a pixel-identical SVG drawing. |
| 71 | |
| 72 | The native route is deliberately data-object-first and may be lossy: marker-local labels, callouts, KPIs, guide lines, custom split/bin semantics, or styling that is absent from the payload may disappear or normalize. Export warns about this route-level risk and any narrower issue it can detect. Loss of visual parity is not grounds to remove an active marker that the emitter can otherwise convert; use the default SVG-fallback export when exact authored artwork matters more than a native data source and object-specific controls. |
| 73 | |
| 74 | | Replacement marker | Native output | Required metadata | |
| 75 | |---|---|---| |
| 76 | | `<g data-pptx-replace-with="table">` | `<p:graphicFrame>` with `<a:tbl>` | bounds + `columns` or `rows` | |
| 77 | | `<g data-pptx-replace-with="chart">` | `<p:graphicFrame>` with `c:chart` / `cx:chart` + chart part + embedded workbook | bounds + `type`, plus chart data | |
| 78 | |
| 79 | **Metadata placement**: Put JSON in one child |
| 80 | `<metadata type="application/json">`. The parent group's |
| 81 | `data-pptx-replace-with` value selects the table or chart schema, so the |
| 82 | metadata child does not repeat an object-kind attribute. Attribute JSON |
| 83 | (`data-pptx-json="..."`) remains read-compatible but is harder to XML-escape |
| 84 | correctly and is not canonical authoring. |
| 85 | |
| 86 | **Bounds**: Provide `x`, `y`, `width`, and `height` in metadata, or as |
| 87 | `data-pptx-x` / `data-pptx-y` / `data-pptx-width` / `data-pptx-height` on the |
| 88 | marker group. If any bound is omitted, the exporter infers the object frame |
| 89 | from the visible fallback geometry; this keeps SVG fallback and native object |
| 90 | placement aligned. Complete explicit bounds are absolute slide coordinates; |
| 91 | marker/ancestor `translate` and `scale` transforms apply only when at least one |
| 92 | bound is inferred. `x`, `y`, `width`, and `height` must be finite and resolve |
| 93 | inside PowerPoint's 32-bit DrawingML coordinate range; `width` and `height` |
| 94 | must resolve to at least one EMU. Native table frames must additionally resolve |
| 95 | to at least one EMU per resolved row and column. |
| 96 | |
| 97 | **Classic plot-area layout**: supported classic charts accept root `plot_area`; |
| 98 | ChartEx rejects it. It contains only finite `x`, `y`, `width`, `height` in |
| 99 | absolute slide px and forms a positive rectangle inside the chart frame. Export |
| 100 | writes `c:manualLayout`; omission keeps automatic layout. |
| 101 | |
| 102 | **Validation**: `svg_quality_checker.py` validates replacement marker kind, JSON |
| 103 | metadata, bounds/fallback availability, table rows/columns, supported chart |
| 104 | type, chart data shape, and any imported fallback baseline before export. |
| 105 | |
| 106 | Imported marker freshness, fallback classification, provenance, and legacy |
| 107 | read compatibility are operational import concerns. Keep generated authoring |
| 108 | free of those attributes; use the exact behavior and field index in |
| 109 | [`conversion.md`](../scripts/docs/conversion.md#native-table-and-chart-import-claims). |
| 110 | |
| 111 | ```xml |
| 112 | <g id="p03-revenue-chart" data-pptx-replace-with="chart"> |
| 113 | <metadata type="application/json"> |
| 114 | { |
| 115 | "x": 120, "y": 150, "width": 520, "height": 320, |
| 116 | "name": "p03-revenue-chart", |
| 117 | "type": "column", |
| 118 | "title": "Revenue by Segment", |
| 119 | "categories": ["Q1", "Q2", "Q3"], |
| 120 | "series": [ |
| 121 | {"name": "Cloud", "values": [12, 15, 19]}, |
| 122 | {"name": "Services", "values": [8, 9, 11]} |
| 123 | ] |
| 124 | } |
| 125 | </metadata> |
| 126 | <!-- Visible SVG fallback for live preview / non-native export goes here. --> |
| 127 | </g> |
| 128 | ``` |
| 129 | |
| 130 | **Hard rule — transcribe the authored object**: metadata is the native object's |
| 131 | source of truth and must describe the same data and visible chart/table chrome |
| 132 | as the fallback drawn in that marker group. |
| 133 | |
| 134 | | Object | Required projection from the visible fallback | |
| 135 | |---|---| |
| 136 | | Chart | Every category/point and series value, the actual x/y/size data where applicable, visible point colors and labels, line/area treatment needed by the schema, visible title/axis/legend chrome, companion text, bounds, and fallback typography/style values that native export cannot infer unambiguously | |
| 137 | | Table | Every resolved row/column cell, header/summary line, rectangular span, required cell style, alignment, bounds, and visible typography | |
| 138 | |
| 139 | Do not simplify the SVG artwork to match the native object model. When the |
| 140 | closed payload cannot represent an object without losing required data or cell |
| 141 | topology, Default returns the native-ready decision upstream and Quick revises |
| 142 | that per-object decision before drawing; only the resolved non-native object |
| 143 | stays unmarked on its complete SVG fallback. Never silently ignore an explicit |
| 144 | `<object-key>=yes` declaration. |
| 145 | |
| 146 | **Per-page verification**: enumerate every `<object-key>=yes` on the current |
| 147 | page and confirm a one-to-one key match. Each key identifies one parent marker |
| 148 | and exactly one JSON metadata child; `<object-key>=no` and unlisted incidental |
| 149 | objects have no marker. Finding one marker somewhere on a page is insufficient. |
| 150 | |
| 151 | ```bash |
| 152 | rg -n 'data-pptx-replace-with="(chart|table)"|<metadata type="application/json">' <project_path>/svg_output/<current_page>.svg |
| 153 | ``` |
| 154 | |
| 155 | **Table schema**: Native tables are rectangular DrawingML grids. Use `columns` |
| 156 | for the optional header row and `rows` for body rows; shorter rows are padded |
| 157 | with blank cells unless `strict_grid: true` is set. Tables may contain at most |
| 158 | 1000 resolved rows and 1000 resolved columns. Use `column_widths` and |
| 159 | `row_heights` as relative weights. Weight lists must match the resolved grid, |
| 160 | contain finite non-negative numbers, and include at least one positive value. |
| 161 | If present, `header_rows` must be an integer from `0` through the resolved row |
| 162 | count. Write `strict_grid`, `style.band_row`, and cell `bold` as JSON booleans. |
| 163 | Cell objects accept `text`, `fill`, `color`, |
| 164 | `align`, `valign`, `bold`, `font_size`, `padding`, `border_color`, and |
| 165 | `border_width`, plus optional `lang`; the same `padding`, `border_color`, |
| 166 | `border_width`, and `lang` keys may also live under `style` as table defaults. |
| 167 | For multi-paragraph text, replace cell `text` with a non-empty `paragraphs` |
| 168 | list. Each entry is either a string or an object containing optional |
| 169 | `align: "l|ctr|r"` and exactly one of `text` or non-empty `runs`; empty |
| 170 | paragraph strings are preserved, and cell `text` / `paragraphs` are mutually |
| 171 | exclusive. Each run is an object with required string `text` and optional JSON |
| 172 | boolean `bold`, `italic`, `underline`, and `strike`, plus optional `color`, |
| 173 | `font_size`, one-typeface `font_family`, `lang`, and `alt_lang`. Unknown fields, |
| 174 | wrong types, empty run lists, multi-typeface `font_family`, and unsupported |
| 175 | colors fail fast. PPTX import requires exact physical row/grid topology and |
| 176 | normalizes source presentation-only run XML outside this closed schema only |
| 177 | when it contains no non-empty `rPr` / `defRPr` / `endParaRPr` `effectLst` or |
| 178 | `effectDag`. A table-cell run effect follows the blocking effect contract above |
| 179 | instead of entering either the native payload or an effect-free fallback. |
| 180 | Relationship-bearing text, extensions, structural line breaks, fields, tabs, |
| 181 | bullets, malformed run topology, and unsupported text-body structure remain |
| 182 | fallback-only. |
| 183 | Per-side cell borders use `borders.left|right|top|bottom`, where each value is |
| 184 | either `{ "style": "none" }` or |
| 185 | `{ "style": "solid", "color": "#RRGGBB", "width": <positive-px> }`. |
| 186 | Per-side borders are cell-only; legacy uniform `border_color` / `border_width` |
| 187 | remain supported as defaults that an individual side may override. |
| 188 | When `lang` is absent, export derives `zh-CN` for CJK text and `en-US` |
| 189 | otherwise. `style.band_row: false` disables both `<a:tblPr bandRow>` and |
| 190 | materialized alternating row fills. Native table typography mirrors the |
| 191 | visible SVG fallback: put `style.font_family` and `style.font_size` on the |
| 192 | marker from the table text already drawn, then use `style.header_font_size` or |
| 193 | per-cell `font_size` only when the fallback visibly differs. If the fallback |
| 194 | has no explicit table font, Default uses the deck body family and declared body |
| 195 | anchor from `spec_lock.md`; Quick uses its active-context body family and size. |
| 196 | |
| 197 | **Hard rule — table metadata is the native source of truth**: Every row, |
| 198 | summary line, value, and cell-level style that must survive |
| 199 | `--native-charts-and-tables` must be present in `columns` / `rows`. SVG fallback text is |
| 200 | discarded during native export. `svg_quality_checker.py` warns when visible |
| 201 | fallback `<text>` inside a native table marker does not appear in metadata. |
| 202 | For numeric or currency columns, use cell objects with `align: "r"`; SVG |
| 203 | `text-anchor="end"` does not carry into the native table. |
| 204 | |
| 205 | **Merged table cells — canonical rectangular contract only**: Put positive JSON |
| 206 | integer `row_span` / `col_span` values on the merge anchor and keep every |
| 207 | covered grid cell blank. Spans must stay within the resolved rectangular grid |
| 208 | and may not overlap. The exporter emits the canonical DrawingML topology |
| 209 | (`rowSpan` on the top edge, `gridSpan` on the left edge, `hMerge` / `vMerge` on |
| 210 | covered cells). CamelCase aliases, raw OOXML merge fields, top-level merge lists, |
| 211 | nonblank covered cells, invalid spans, and overlaps fail fast. The PPTX importer |
| 212 | activates native reconstruction only for that same explicit rectangular topology |
| 213 | with empty merge-slave text bodies; other merge encodings remain fallback-only |
| 214 | with `unsupported-merge-topology`. |
| 215 | |
| 216 | **Category chart schema**: `column`, `bar`, `line`, `area`, `pie`, |
| 217 | `doughnut`, `pieOfPie`, `barOfPie`, and `radar` use `categories` plus |
| 218 | `series[].values`. Pie-family charts (`pie`, `doughnut`, `pieOfPie`, and |
| 219 | `barOfPie`) must have exactly one series; the exporter assigns per-category |
| 220 | slice colors so single-series charts do not collapse into one solid color. |
| 221 | Root `hole_size` is doughnut-only, integer `10..90`, default `75`; |
| 222 | no pie-family rotation or angle field exists. |
| 223 | Column and bar charts may set per-point colors with `series[].point_colors` |
| 224 | or `series[].pointColors`; the list must match `series[].values` length. |
| 225 | Classic category charts may set native PowerPoint data labels with |
| 226 | `data_labels`. Use `data_labels: true` for default value labels, or an object |
| 227 | with `show_value`, `position`, `number_format`, `font_size`, `font_family`, |
| 228 | `bold`, `color`, and optional per-point `colors`. Supported label positions |
| 229 | depend on chart type: clustered column/bar labels may use `outside_end`, |
| 230 | `inside_end`, `inside_base`, or `center`; stacked / percent-stacked column/bar |
| 231 | labels may use `inside_end`, `inside_base`, or `center`; line labels may use |
| 232 | `above`, `center`, or `best_fit`; area labels do not emit a native label |
| 233 | position. To label only selected data points, use `data_labels.points` with |
| 234 | zero-based `idx` plus optional per-point `position`, `number_format`, |
| 235 | `font_size`, `font_family`, `bold`, and `color`. |
| 236 | |
| 237 | **Combo chart schema**: `combo` uses shared `categories` plus either `plots[]` |
| 238 | or typed `series[]`. Each plot supports `type: "column" | "line" | "area"`, |
| 239 | its own `series`, and optional `axis: "secondary"` for a right-side value axis. |
| 240 | When primary and secondary plots genuinely use different category caches, |
| 241 | `plots[]` may also carry its own `categories` and `category_numeric`; the |
| 242 | workbook writer allocates independent category/value ranges. Typed `series[]` |
| 243 | continues to require the shared top-level categories. |
| 244 | Imported `plots[]` may carry `series_indices` so the verified source identity |
| 245 | where each `c:idx` equals its `c:order` survives when physical plot order differs |
| 246 | from legend order. If one plot supplies it, every plot must supply a same-length |
| 247 | list of unique non-negative JSON integers, and the combined values must form one |
| 248 | contiguous `0..N-1` range. Sources whose `idx` and `order` differ stay |
| 249 | fallback-only; typed `series[]` does not accept this plot-scoped field. |
| 250 | Typed `series[]` accepts the same `type` and `axis` fields per series, and |
| 251 | adjacent compatible series are grouped into the same PowerPoint plot. Area |
| 252 | series may set `fill_opacity` / `fillOpacity` as a `0..1` SVG opacity value |
| 253 | when the SVG fallback uses a transparent area fill under an opaque line. A line plot with `area_fill: true` |
| 254 | is exported as a PowerPoint area chart under the hood; `fill_opacity` only sets |
| 255 | the fill style and does not trigger conversion by itself. Combo export layers |
| 256 | area plots below columns and lines while preserving the original series indices. |
| 257 | Line and area series may set `line_width` / `lineWidth` in SVG px units to |
| 258 | match fallback `stroke-width`. |
| 259 | |
| 260 | **Narrow classic-axis schema**: `axes` is a closed object with the roles |
| 261 | `category`, `value`, `secondary_category`, and `secondary_value`. Each role may |
| 262 | set only `kind` (`text`, `date`, or `value`, as appropriate), `position`, |
| 263 | `visible`, `label_position` (`next_to`, `none`, `low`, or `high`), |
| 264 | `number_format`, `minimum`, `maximum`, `major_unit`, `reverse`, and |
| 265 | `major_gridlines`. `major_unit` applies to value axes only. PPTX date-axis |
| 266 | **import** is deliberately narrow: numeric Excel date serials are accepted for |
| 267 | area charts and OHLC stock charts; arbitrary date-axis source families are not. |
| 268 | This contract is not a full `AxisSpec`: logarithmic scales, minor units/gridlines, |
| 269 | crossing values, display units, tick skipping, and other unlisted OOXML semantics |
| 270 | remain unsupported and fail closed on import. |
| 271 | Single-plot `bar` accepts `category` at left/right and `value` at bottom/top; |
| 272 | `pie`, `doughnut`, and `pieOfPie` / `barOfPie` reject `axes`. |
| 273 | |
| 274 | **Narrow XY-axis schema**: `scatter` and `bubble` may use a closed `axes` object |
| 275 | with only `x` and `y` roles. Both roles have `kind: "value"`; `x.position` |
| 276 | is `bottom` or `top`, while `y.position` is `left` or `right`. Each accepts the |
| 277 | same closed fields above, and `major_unit` is valid on both value axes. PPTX |
| 278 | import requires the plot to reference exactly two mutually cross-linked |
| 279 | `c:valAx` nodes and separately enforces the closed field/topology gates. The |
| 280 | native writer emits and the importer reads back every field in this closed |
| 281 | contract. Scatter import derives the effective `scatter_style` from a uniform |
| 282 | per-series line/marker/smooth state; unsupported or nonuniform states remain |
| 283 | fallback-only. The normalized SVG fallback newly consumes only |
| 284 | `axes.x.major_gridlines` and `axes.y.major_gridlines`; the other fields do not |
| 285 | imply full visual-axis parity. |
| 286 | |
| 287 | **XY chart schema**: `scatter` and `bubble` use `series[].x` + `series[].y`; |
| 288 | `bubble` also requires one `series[].size` / `series[].sizes` value per point. |
| 289 | `series[].points` is also accepted as `[x, y]` / `[x, y, size]` tuples or |
| 290 | `{x, y, size}` objects. |
| 291 | |
| 292 | **Chart typography**: Metadata sizes use the same px-style unit as SVG text |
| 293 | (`1px = 0.75pt`). `style.font_family` and the role-specific |
| 294 | `title_font_size`, `subtitle_font_size`, `axis_font_size`, |
| 295 | `axis_title_font_size`, `legend_font_size`, and `note_font_size` fields are |
| 296 | required only when the native object must preserve typography that cannot be |
| 297 | inferred unambiguously from the visible fallback. |
| 298 | |
| 299 | **Chart chrome metadata**: Metadata MUST match fallback chrome. For classic |
| 300 | charts, a string or unbounded-object `title` becomes native `c:title`; |
| 301 | `subtitle` is line two. A title object with complete `x`, `y`, |
| 302 | `width`, and `height` becomes a companion editable text box at those absolute |
| 303 | slide-px bounds; partial bounds or `subtitle` fail. Use `name`, not |
| 304 | `title`, for object naming. `title`, `subtitle`, and axis-title objects may set |
| 305 | `text`, `font_size`, `font_family`, and `color`. The checker rejects title/axis |
| 306 | text absent from the fallback; export omits it with a warning. ChartEx keeps an empty `<cx:title>` and |
| 307 | emits title/subtitle as companion editable text boxes. Axis |
| 308 | titles are optional and explicit: use `axis_titles` with |
| 309 | `category`, `value`, `x`, `y`, or `secondary_value` keys, or the root aliases |
| 310 | `category_axis_title`, `value_axis_title`, `x_axis_title`, `y_axis_title`, and |
| 311 | `secondary_value_axis_title`; do not add semantic axis titles that are not |
| 312 | visible in the fallback. Set `show_value_axis_labels: false` when the fallback |
| 313 | keeps category labels but omits numeric value-axis tick labels, such as a radar |
| 314 | chart without radial coordinates. Native legends are metadata-controlled: use |
| 315 | `show_legend: true` and `legend_position` only when the fallback's legend is |
| 316 | meant to be replaced by PowerPoint's native legend. |
| 317 | Companion text such as `caption`, `source`, `note`, `notes`, `footnote`, and |
| 318 | `footnotes` is exported as editable PPT text boxes next to the native chart. A |
| 319 | companion entry may be a string or an object with `text`, `x`, `y`, `width`, |
| 320 | `height`, `font_size`, `color`, `align`, and `bold`; explicit bounds are |
| 321 | recommended so the native export matches the SVG fallback placement. Explicit |
| 322 | companion bounds are slide coordinates, not local coordinates inside a |
| 323 | transformed marker group. Use companion text for chart captions, source notes, |
| 324 | center labels, and freeform annotations; use `data_labels` for values that |
| 325 | belong to chart points. |
| 326 | |
| 327 | **Chart color styling**: For classic native charts, `style.colors` sets series |
| 328 | colors. The exporter also writes explicit chart-area fill, plot-area fill, |
| 329 | axis line, gridline, and label text colors so PowerPoint does not substitute a |
| 330 | white/default-theme chart. If omitted, the exporter infers these colors from |
| 331 | the visible SVG fallback: the largest panel-like `<rect>` becomes the chart |
| 332 | background, fallback text supplies label color, and fallback strokes supply |
| 333 | axis/grid colors. Override any of them explicitly under `style` with |
| 334 | `chart_area_fill`, `plot_area_fill`, `text_color`, `axis_color`, and |
| 335 | `grid_color`; use `"none"` for transparent chart or plot area fill. Generated |
| 336 | payloads default to uppercase `#RRGGBB`. The exporter retains compatibility for |
| 337 | `#RGB`, `rgb(...)` / `rgba(...)`, and common CSS names, normalizing them to |
| 338 | 6-digit OOXML RGB. Bar and column series also disable PowerPoint's negative-value |
| 339 | inversion so negative bars keep the same series fill instead of turning into |
| 340 | white/theme fill. |
| 341 | |
| 342 | For ChartEx native charts, valid payload `style.colors` (or root `colors`) |
| 343 | populate the ChartEx color-style part instead of being replaced by a fixed |
| 344 | accent1–accent6 list. Other ChartEx style semantics remain normalized. |
| 345 | |
| 346 | **PowerPoint chartEx schema**: `treemap`, `sunburst`, `histogram`, `pareto`, |
| 347 | `boxWhisker`, `waterfall`, and `funnel` use Office 2016+ chartEx parts. Use |
| 348 | these input shapes: |
| 349 | |
| 350 | | Type | Required data | |
| 351 | |---|---| |
| 352 | | `treemap`, `sunburst` | `values` plus either `levels` (`levels[level][point]`) or path-style `categories` (`[["Region", "Group", "Leaf"], ...]`) | |
| 353 | | `treemap` display note | Top-level group labels default to `overlapping`; override with `parent_label_layout: "banner" \| "overlapping" \| "none"`. PowerPoint labels only the top level and leaves — intermediate levels group tiles spatially without labels (sunburst shows every ring). | |
| 354 | | `histogram` | `values` | |
| 355 | | `pareto`, `waterfall`, `funnel` | `categories` + `values`; `waterfall` also accepts `subtotals` / `subtotal_indices` point indexes | |
| 356 | | `boxWhisker` | `series[].values`; optional `series[].categories` per value | |
| 357 | |
| 358 | > Note: chartEx files are valid PPTX and editable in PowerPoint; non-Microsoft |
| 359 | > renderers can display a limited subset. |
| 360 | |
| 361 | **Stock chart schema**: `stock` uses numeric Excel date serials in |
| 362 | `categories` or `dates`, plus exactly four series in open / high / low / close |
| 363 | order. Use either `series` with four entries, or top-level `open`, `high`, |
| 364 | `low`, and `close` arrays. PPTX import currently recognizes only canonical OHLC |
| 365 | stock charts with shared numeric date caches, `hiLowLines`, and `upDownBars`. |
| 366 | Safe stock series style may pass the structural gate, but stock series, |
| 367 | `hiLowLines`, and up-down bar local styling can still normalize under the |
| 368 | data-object-first contract. HLC, volume, noncanonical structure, and style XML |
| 369 | outside the safe parsing boundary stay fallback-only. |
| 370 | |
| 371 | **PPTX chart-import boundary**: The importer recognizes conservative classic |
| 372 | single-plot charts plus the verified scatter/bubble XY-axis, column/line/area |
| 373 | combo, area date-axis, canonical OHLC stock, radar, safe `of_pie` `serLines`, |
| 374 | axis/title/legend normalization, and bar/column gap/overlap subsets. Imported |
| 375 | `gapWidth` must be one canonical integer in `0..500`; imported `overlap` must be |
| 376 | one canonical integer in `-100..100`. Both values intentionally normalize to |
| 377 | the native writer contract rather than claiming exact source-style retention. |
| 378 | Malformed, duplicate, or out-of-range values fail closed. |
| 379 | |
| 380 | ChartEx import is closed to seven validated data models: `treemap`, `sunburst`, |
| 381 | `histogram`, `pareto`, `box_whisker`, `waterfall`, and `funnel`. The importer |
| 382 | retains their supported hierarchy/category/value/series/subtotal topology for |
| 383 | native read-back. Numeric cache values must be non-empty and finite, and cache |
| 384 | counts/indexes must be canonical non-negative decimal integers with exact, |
| 385 | contiguous topology; malformed, non-numeric, `NaN`, infinite, sparse, duplicate, |
| 386 | or mismatched caches fail closed. ChartEx style, axis, label, and binning details |
| 387 | outside the payload normalize. Full `AxisSpec`, arbitrary ChartEx families or |
| 388 | presentation fidelity, arbitrary stock variants, and axis/combo/date-axis |
| 389 | semantics outside the closed fields above remain fallback-only. The C4/C5 |
| 390 | import work does not expand the normalized SVG renderer and does not reduce |
| 391 | existing SVG-marker-to-native writer support. |
| 392 | |
| 393 | **Deferred chart types**: Exploded pie / doughnut variants, `map`, `heatmap`, |
| 394 | `bullet`, and `gantt` are intentionally outside the current native-object |
| 395 | support boundary. The exporter fails fast for these types until each mapping is |
| 396 | implemented and validated one by one. |
| 397 | |
| 398 | **Supported chart types**: |
| 399 | |
| 400 | - `column`, `bar`: `clustered`, `stacked`, or `percentStacked` (`grouping`) |
| 401 | - `line`: `standard`, `stacked`, or `percentStacked` (`grouping`); `line` or `lineMarker` (`line_style`, default `line` / no markers) |
| 402 | - `area`: `standard`, `stacked`, or `percentStacked` (`grouping`) |
| 403 | - `pie`: exactly one series, per-slice colors |
| 404 | - `doughnut`: exactly one series, per-slice colors |
| 405 | - `pieOfPie`, `barOfPie`: exactly one series, per-slice colors |
| 406 | - `radar`, `radarMarkers`, `radarFilled` |
| 407 | - `scatter`: `marker` (default), `lineMarker`, `line`, `smoothMarker`, or `smooth` (`scatter_style`) |
| 408 | - `bubble`: x/y/size series |
| 409 | - `combo`: `column`, `line`, and `area` plots, optional secondary value axis |
| 410 | - `treemap`, `sunburst`: hierarchical chartEx charts |
| 411 | - `histogram`, `pareto` |
| 412 | - `boxWhisker` |
| 413 | - `waterfall`, `funnel` |
| 414 | - `stock`: open / high / low / close series |
| 415 | |
| 416 | 3D chart aliases (`3DColumn`, `3DBar`, `3DLine`, `3DArea`, `3DPie`, cone, |
| 417 | cylinder, pyramid variants, and `surface`) are unsupported. |
| 418 | |
| 419 | Native legends are opt-in through `show_legend: true`; `legend_position` |
| 420 | defaults to `bottom` and accepts `top`, `left`, or `right`. |
| 421 | |
| 422 | **Forbidden — replacement marker transforms**: Do not rotate, skew, or matrix-transform table/chart replacement groups. Translate / scale is accepted; complex transforms fail export because PowerPoint-native table/chart frames do not preserve arbitrary SVG transforms. |
| 423 |