返回 ppt-master
native-data-interface.md
根目录 / skills / ppt-master / references / native-data-interface.md
1 > See [`shared-standards-core.md`](./shared-standards-core.md) for the mandatory SVG foundation.
2
3 # Native Data Interface
4
5 Conditional interface for preset pattern fills and PowerPoint-native chart/table replacement metadata. Load when either feature appears in 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 Native PowerPoint tables and Excel-backed charts activate at export time only. Generated pages prepare dormant replacement metadata for independently selected native-ready objects while keeping hand-authored SVG geometry pixel-stable across PowerPoint / Keynote / LibreOffice / WPS.
44
45 **Hard rule — selected-object authoring**: write the marker and JSON metadata in
46 the same edit only for a supported chart or pure text-grid table selected as
47 native-ready under [`executor-chart.md`](./executor-chart.md) §2.2. Default reads
48 `Native-ready: yes` from §IX (legacy §VII fallback only); Quick makes the
49 decision in active context before drawing. Default `no`, a Quick decision not to
50 use the native object model, and incidental microvisuals stay on the SVG
51 fallback route. Canonical rectangular merged text cells may use the narrow
52 `row_span` / `col_span` contract below; graphical cells stay unmarked. The
53 marker group supplies visible SVG fallback children for browser rendering and
54 JSON metadata for `svg_to_pptx` native export.
55
56 **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.
57
58 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.
59
60 | Replacement marker | Native output | Required metadata |
61 |---|---|---|
62 | `<g data-pptx-replace-with="table">` | `<p:graphicFrame>` with `<a:tbl>` | bounds + `columns` or `rows` |
63 | `<g data-pptx-replace-with="chart">` | `<p:graphicFrame>` with `c:chart` / `cx:chart` + chart part + embedded workbook | bounds + `type`, plus chart data |
64
65 **Metadata placement**: Put JSON in one child
66 `<metadata type="application/json">`. The parent group's
67 `data-pptx-replace-with` value selects the table or chart schema, so the
68 metadata child does not repeat an object-kind attribute. Attribute JSON
69 (`data-pptx-json="..."`) remains read-compatible but is harder to XML-escape
70 correctly and is not canonical authoring.
71
72 **Bounds**: Provide `x`, `y`, `width`, and `height` in metadata, or as
73 `data-pptx-x` / `data-pptx-y` / `data-pptx-width` / `data-pptx-height` on the
74 marker group. If any bound is omitted, the exporter infers the object frame
75 from the visible fallback geometry; this keeps SVG fallback and native object
76 placement aligned. Complete explicit bounds are absolute slide coordinates;
77 marker/ancestor `translate` and `scale` transforms apply only when at least one
78 bound is inferred. `x`, `y`, `width`, and `height` must be finite and resolve
79 inside PowerPoint's 32-bit DrawingML coordinate range; `width` and `height`
80 must resolve to at least one EMU. Native table frames must additionally resolve
81 to at least one EMU per resolved row and column.
82
83 **Validation**: `svg_quality_checker.py` validates replacement marker kind, JSON
84 metadata, bounds/fallback availability, table rows/columns, supported chart
85 type, chart data shape, and any imported fallback baseline before export.
86
87 Imported marker freshness, fallback classification, provenance, and legacy
88 read compatibility are operational import concerns. Keep generated authoring
89 free of those attributes; use the exact behavior and field index in
90 [`conversion.md`](../scripts/docs/conversion.md#native-table-and-chart-import-claims).
91
92 ```xml
93 <g id="p03-revenue-chart" data-pptx-replace-with="chart">
94 <metadata type="application/json">
95 {
96 "x": 120, "y": 150, "width": 520, "height": 320,
97 "type": "column",
98 "title": "Revenue by Segment",
99 "categories": ["Q1", "Q2", "Q3"],
100 "series": [
101 {"name": "Cloud", "values": [12, 15, 19]},
102 {"name": "Services", "values": [8, 9, 11]}
103 ]
104 }
105 </metadata>
106 <!-- Visible SVG fallback for live preview / non-native export goes here. -->
107 </g>
108 ```
109
110 **Table schema**: Native tables are rectangular DrawingML grids. Use `columns`
111 for the optional header row and `rows` for body rows; shorter rows are padded
112 with blank cells unless `strict_grid: true` is set. Tables may contain at most
113 1000 resolved rows and 1000 resolved columns. Use `column_widths` and
114 `row_heights` as relative weights. Weight lists must match the resolved grid,
115 contain finite non-negative numbers, and include at least one positive value.
116 If present, `header_rows` must be an integer from `0` through the resolved row
117 count. Write `strict_grid`, `style.band_row`, and cell `bold` as JSON booleans.
118 Cell objects accept `text`, `fill`, `color`,
119 `align`, `valign`, `bold`, `font_size`, `padding`, `border_color`, and
120 `border_width`, plus optional `lang`; the same `padding`, `border_color`,
121 `border_width`, and `lang` keys may also live under `style` as table defaults.
122 For multi-paragraph text, replace cell `text` with a non-empty `paragraphs`
123 list. Each entry is either a string or an object containing optional
124 `align: "l|ctr|r"` and exactly one of `text` or non-empty `runs`; empty
125 paragraph strings are preserved, and cell `text` / `paragraphs` are mutually
126 exclusive. Each run is an object with required string `text` and optional JSON
127 boolean `bold`, `italic`, `underline`, and `strike`, plus optional `color`,
128 `font_size`, one-typeface `font_family`, `lang`, and `alt_lang`. Unknown fields,
129 wrong types, empty run lists, multi-typeface `font_family`, and unsupported
130 colors fail fast. PPTX import requires exact physical row/grid topology and
131 normalizes source presentation-only run XML outside this closed schema only
132 when it contains no non-empty `rPr` / `defRPr` / `endParaRPr` `effectLst` or
133 `effectDag`. A table-cell run effect follows the blocking effect contract above
134 instead of entering either the native payload or an effect-free fallback.
135 Relationship-bearing text, extensions, structural line breaks, fields, tabs,
136 bullets, malformed run topology, and unsupported text-body structure remain
137 fallback-only.
138 Per-side cell borders use `borders.left|right|top|bottom`, where each value is
139 either `{ "style": "none" }` or
140 `{ "style": "solid", "color": "#RRGGBB", "width": <positive-px> }`.
141 Per-side borders are cell-only; legacy uniform `border_color` / `border_width`
142 remain supported as defaults that an individual side may override.
143 When `lang` is absent, export derives `zh-CN` for CJK text and `en-US`
144 otherwise. `style.band_row: false` disables both `<a:tblPr bandRow>` and
145 materialized alternating row fills. Native table typography mirrors the
146 visible SVG fallback: put `style.font_family` and `style.font_size` on the
147 marker from the table text already drawn, then use `style.header_font_size` or
148 per-cell `font_size` only when the fallback visibly differs. If the fallback
149 has no explicit table font, Default uses the deck body family and declared body
150 anchor from `spec_lock.md`; Quick uses its active-context body family and size.
151
152 **Hard rule — table metadata is the native source of truth**: Every row,
153 summary line, value, and cell-level style that must survive
154 `--native-charts-and-tables` must be present in `columns` / `rows`. SVG fallback text is
155 discarded during native export. `svg_quality_checker.py` warns when visible
156 fallback `<text>` inside a native table marker does not appear in metadata.
157 For numeric or currency columns, use cell objects with `align: "r"`; SVG
158 `text-anchor="end"` does not carry into the native table.
159
160 **Merged table cells — canonical rectangular contract only**: Put positive JSON
161 integer `row_span` / `col_span` values on the merge anchor and keep every
162 covered grid cell blank. Spans must stay within the resolved rectangular grid
163 and may not overlap. The exporter emits the canonical DrawingML topology
164 (`rowSpan` on the top edge, `gridSpan` on the left edge, `hMerge` / `vMerge` on
165 covered cells). CamelCase aliases, raw OOXML merge fields, top-level merge lists,
166 nonblank covered cells, invalid spans, and overlaps fail fast. The PPTX importer
167 activates native reconstruction only for that same explicit rectangular topology
168 with empty merge-slave text bodies; other merge encodings remain fallback-only
169 with `unsupported-merge-topology`.
170
171 **Category chart schema**: `column`, `bar`, `line`, `area`, `pie`,
172 `doughnut`, `pieOfPie`, `barOfPie`, and `radar` use `categories` plus
173 `series[].values`. Pie-family charts (`pie`, `doughnut`, `pieOfPie`, and
174 `barOfPie`) must have exactly one series; the exporter assigns per-category
175 slice colors so single-series charts do not collapse into one solid color.
176 Column and bar charts may set per-point colors with `series[].point_colors`
177 or `series[].pointColors`; the list must match `series[].values` length.
178 Classic category charts may set native PowerPoint data labels with
179 `data_labels`. Use `data_labels: true` for default value labels, or an object
180 with `show_value`, `position`, `number_format`, `font_size`, `font_family`,
181 `bold`, `color`, and optional per-point `colors`. Supported label positions
182 depend on chart type: clustered column/bar labels may use `outside_end`,
183 `inside_end`, `inside_base`, or `center`; stacked / percent-stacked column/bar
184 labels may use `inside_end`, `inside_base`, or `center`; line labels may use
185 `above`, `center`, or `best_fit`; area labels do not emit a native label
186 position. To label only selected data points, use `data_labels.points` with
187 zero-based `idx` plus optional per-point `position`, `number_format`,
188 `font_size`, `font_family`, `bold`, and `color`.
189
190 **Combo chart schema**: `combo` uses shared `categories` plus either `plots[]`
191 or typed `series[]`. Each plot supports `type: "column" | "line" | "area"`,
192 its own `series`, and optional `axis: "secondary"` for a right-side value axis.
193 When primary and secondary plots genuinely use different category caches,
194 `plots[]` may also carry its own `categories` and `category_numeric`; the
195 workbook writer allocates independent category/value ranges. Typed `series[]`
196 continues to require the shared top-level categories.
197 Imported `plots[]` may carry `series_indices` so the verified source identity
198 where each `c:idx` equals its `c:order` survives when physical plot order differs
199 from legend order. If one plot supplies it, every plot must supply a same-length
200 list of unique non-negative JSON integers, and the combined values must form one
201 contiguous `0..N-1` range. Sources whose `idx` and `order` differ stay
202 fallback-only; typed `series[]` does not accept this plot-scoped field.
203 Typed `series[]` accepts the same `type` and `axis` fields per series, and
204 adjacent compatible series are grouped into the same PowerPoint plot. Area
205 series may set `fill_opacity` / `fillOpacity` as a `0..1` SVG opacity value
206 when the SVG fallback uses a transparent area fill under an opaque line. A line plot with `area_fill: true`
207 is exported as a PowerPoint area chart under the hood; `fill_opacity` only sets
208 the fill style and does not trigger conversion by itself. Combo export layers
209 area plots below columns and lines while preserving the original series indices.
210 Line and area series may set `line_width` / `lineWidth` in SVG px units to
211 match fallback `stroke-width`.
212
213 **Narrow classic-axis schema**: `axes` is a closed object with the roles
214 `category`, `value`, `secondary_category`, and `secondary_value`. Each role may
215 set only `kind` (`text`, `date`, or `value`, as appropriate), `position`,
216 `visible`, `label_position` (`next_to`, `none`, `low`, or `high`),
217 `number_format`, `minimum`, `maximum`, `major_unit`, `reverse`, and
218 `major_gridlines`. `major_unit` applies to value axes only. PPTX date-axis
219 **import** is deliberately narrow: numeric Excel date serials are accepted for
220 area charts and OHLC stock charts; arbitrary date-axis source families are not.
221 This contract is not a full `AxisSpec`: logarithmic scales, minor units/gridlines,
222 crossing values, display units, tick skipping, and other unlisted OOXML semantics
223 remain unsupported and fail closed on import.
224
225 **Narrow XY-axis schema**: `scatter` and `bubble` may use a closed `axes` object
226 with only `x` and `y` roles. Both roles have `kind: "value"`; `x.position`
227 is `bottom` or `top`, while `y.position` is `left` or `right`. Each accepts the
228 same closed fields above, and `major_unit` is valid on both value axes. PPTX
229 import requires the plot to reference exactly two mutually cross-linked
230 `c:valAx` nodes and separately enforces the closed field/topology gates. The
231 native writer emits and the importer reads back every field in this closed
232 contract. Scatter import derives the effective `scatter_style` from a uniform
233 per-series line/marker/smooth state; unsupported or nonuniform states remain
234 fallback-only. The normalized SVG fallback newly consumes only
235 `axes.x.major_gridlines` and `axes.y.major_gridlines`; the other fields do not
236 imply full visual-axis parity.
237
238 **XY chart schema**: `scatter` and `bubble` use `series[].x` + `series[].y`;
239 `bubble` also requires one `series[].size` / `series[].sizes` value per point.
240 `series[].points` is also accepted as `[x, y]` / `[x, y, size]` tuples or
241 `{x, y, size}` objects.
242
243 **Chart typography**: Metadata sizes use the same px-style unit as SVG text
244 (`1px = 0.75pt`). `style.font_family` and the role-specific
245 `title_font_size`, `subtitle_font_size`, `axis_font_size`,
246 `axis_title_font_size`, `legend_font_size`, and `note_font_size` fields are
247 required only when the native object must preserve typography that cannot be
248 inferred unambiguously from the visible fallback.
249
250 **Chart chrome metadata**: Text that is visually part of the chart must be in
251 metadata, not only in SVG fallback children; metadata MUST still match visible
252 fallback chrome. `title` becomes the native chart title on classic charts; it
253 is not an object name, so use `name` for semantic object naming. `subtitle`
254 becomes the second rich-text line of that classic chart title. `title`,
255 `subtitle`, and axis-title values may be strings or objects with `text`,
256 `font_size`, `font_family`, and `color` when the fallback uses local role
257 typography. `svg_quality_checker.py` rejects `title`, `subtitle`, or axis-title
258 metadata whose text is not visible inside the replacement marker's fallback. Direct
259 `--native-charts-and-tables` export keeps the chart native but omits that inconsistent
260 chrome with a warning. chartEx keeps PowerPoint's empty `<cx:title>` and emits
261 the title / subtitle as companion editable text boxes until chartEx rich titles
262 are validated. Axis
263 titles are optional and explicit: use `axis_titles` with
264 `category`, `value`, `x`, `y`, or `secondary_value` keys, or the root aliases
265 `category_axis_title`, `value_axis_title`, `x_axis_title`, `y_axis_title`, and
266 `secondary_value_axis_title`; do not add semantic axis titles that are not
267 visible in the fallback. Set `show_value_axis_labels: false` when the fallback
268 keeps category labels but omits numeric value-axis tick labels, such as a radar
269 chart without radial coordinates. Native legends are metadata-controlled: use
270 `show_legend: true` and `legend_position` only when the fallback's legend is
271 meant to be replaced by PowerPoint's native legend.
272 Companion text such as `caption`, `source`, `note`, `notes`, `footnote`, and
273 `footnotes` is exported as editable PPT text boxes next to the native chart. A
274 companion entry may be a string or an object with `text`, `x`, `y`, `width`,
275 `height`, `font_size`, `color`, `align`, and `bold`; explicit bounds are
276 recommended so the native export matches the SVG fallback placement. Explicit
277 companion bounds are slide coordinates, not local coordinates inside a
278 transformed marker group. Use companion text for chart captions, source notes,
279 center labels, and freeform annotations; use `data_labels` for values that
280 belong to chart points.
281
282 **Chart color styling**: For classic native charts, `style.colors` sets series
283 colors. The exporter also writes explicit chart-area fill, plot-area fill,
284 axis line, gridline, and label text colors so PowerPoint does not substitute a
285 white/default-theme chart. If omitted, the exporter infers these colors from
286 the visible SVG fallback: the largest panel-like `<rect>` becomes the chart
287 background, fallback text supplies label color, and fallback strokes supply
288 axis/grid colors. Override any of them explicitly under `style` with
289 `chart_area_fill`, `plot_area_fill`, `text_color`, `axis_color`, and
290 `grid_color`; use `"none"` for transparent chart or plot area fill. Generated
291 payloads default to uppercase `#RRGGBB`. The exporter retains compatibility for
292 `#RGB`, `rgb(...)` / `rgba(...)`, and common CSS names, normalizing them to
293 6-digit OOXML RGB. Bar and column series also disable PowerPoint's negative-value
294 inversion so negative bars keep the same series fill instead of turning into
295 white/theme fill.
296
297 For ChartEx native charts, valid payload `style.colors` (or root `colors`)
298 populate the ChartEx color-style part instead of being replaced by a fixed
299 accent1–accent6 list. Other ChartEx style semantics remain normalized.
300
301 **PowerPoint chartEx schema**: `treemap`, `sunburst`, `histogram`, `pareto`,
302 `boxWhisker`, `waterfall`, and `funnel` use Office 2016+ chartEx parts. Use
303 these input shapes:
304
305 | Type | Required data |
306 |---|---|
307 | `treemap`, `sunburst` | `values` plus either `levels` (`levels[level][point]`) or path-style `categories` (`[["Region", "Group", "Leaf"], ...]`) |
308 | `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). |
309 | `histogram` | `values` |
310 | `pareto`, `waterfall`, `funnel` | `categories` + `values`; `waterfall` also accepts `subtotals` / `subtotal_indices` point indexes |
311 | `boxWhisker` | `series[].values`; optional `series[].categories` per value |
312
313 > Note: chartEx files are valid PPTX and editable in PowerPoint; non-Microsoft
314 > renderers can display a limited subset.
315
316 **Stock chart schema**: `stock` uses numeric Excel date serials in
317 `categories` or `dates`, plus exactly four series in open / high / low / close
318 order. Use either `series` with four entries, or top-level `open`, `high`,
319 `low`, and `close` arrays. PPTX import currently recognizes only canonical OHLC
320 stock charts with shared numeric date caches, `hiLowLines`, and `upDownBars`.
321 Safe stock series style may pass the structural gate, but stock series,
322 `hiLowLines`, and up-down bar local styling can still normalize under the
323 data-object-first contract. HLC, volume, noncanonical structure, and style XML
324 outside the safe parsing boundary stay fallback-only.
325
326 **PPTX chart-import boundary**: The importer recognizes conservative classic
327 single-plot charts plus the verified scatter/bubble XY-axis, column/line/area
328 combo, area date-axis, canonical OHLC stock, radar, safe `of_pie` `serLines`,
329 axis/title/legend normalization, and bar/column gap/overlap subsets. Imported
330 `gapWidth` must be one canonical integer in `0..500`; imported `overlap` must be
331 one canonical integer in `-100..100`. Both values intentionally normalize to
332 the native writer contract rather than claiming exact source-style retention.
333 Malformed, duplicate, or out-of-range values fail closed.
334
335 ChartEx import is closed to seven validated data models: `treemap`, `sunburst`,
336 `histogram`, `pareto`, `box_whisker`, `waterfall`, and `funnel`. The importer
337 retains their supported hierarchy/category/value/series/subtotal topology for
338 native read-back. Numeric cache values must be non-empty and finite, and cache
339 counts/indexes must be canonical non-negative decimal integers with exact,
340 contiguous topology; malformed, non-numeric, `NaN`, infinite, sparse, duplicate,
341 or mismatched caches fail closed. ChartEx style, axis, label, and binning details
342 outside the payload normalize. Full `AxisSpec`, arbitrary ChartEx families or
343 presentation fidelity, arbitrary stock variants, and axis/combo/date-axis
344 semantics outside the closed fields above remain fallback-only. The C4/C5
345 import work does not expand the normalized SVG renderer and does not reduce
346 existing SVG-marker-to-native writer support.
347
348 **Deferred chart types**: Exploded pie / doughnut variants, `map`, `heatmap`,
349 `bullet`, and `gantt` are intentionally outside the current native-object
350 support boundary. The exporter fails fast for these types until each mapping is
351 implemented and validated one by one.
352
353 **Supported chart types**:
354
355 - `column`, `bar`: `clustered`, `stacked`, or `percentStacked` (`grouping`)
356 - `line`: `standard`, `stacked`, or `percentStacked` (`grouping`); `line` or `lineMarker` (`line_style`, default `line` / no markers)
357 - `area`: `standard`, `stacked`, or `percentStacked` (`grouping`)
358 - `pie`: exactly one series, per-slice colors
359 - `doughnut`: exactly one series, per-slice colors
360 - `pieOfPie`, `barOfPie`: exactly one series, per-slice colors
361 - `radar`, `radarMarkers`, `radarFilled`
362 - `scatter`: `marker` (default), `lineMarker`, `line`, `smoothMarker`, or `smooth` (`scatter_style`)
363 - `bubble`: x/y/size series
364 - `combo`: `column`, `line`, and `area` plots, optional secondary value axis
365 - `treemap`, `sunburst`: hierarchical chartEx charts
366 - `histogram`, `pareto`
367 - `boxWhisker`
368 - `waterfall`, `funnel`
369 - `stock`: open / high / low / close series
370
371 3D chart aliases (`3DColumn`, `3DBar`, `3DLine`, `3DArea`, `3DPie`, cone,
372 cylinder, pyramid variants, and `surface`) are unsupported.
373
374 Native legends are opt-in through `show_legend: true`; `legend_position`
375 defaults to `bottom` and accepts `top`, `left`, or `right`.
376
377 **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.
378
378 lines MARKDOWN