| 1 | # Shared SVG Core Standards |
| 2 | |
| 3 | Mandatory reference for every route that authors or regenerates slide visuals through SVG. It owns XML validity, the closed generated-authoring surface, basic converter compatibility, page closure, semantic grouping, shared visual-quality defaults, and fidelity vocabulary. |
| 4 | |
| 5 | **Conditional module routing**: |
| 6 | |
| 7 | | Trigger | Load | |
| 8 | |---|---| |
| 9 | | Default or Quick Generate; otherwise noncanonical/alpha paint, advanced line or text treatment, gradient/filter/effect, transform, freeform/radial geometry, or constructed style | [`svg-effects.md`](./svg-effects.md) | |
| 10 | | A page will use a preset pattern fill or evaluate native chart/table replacement | [`native-data-interface.md`](./native-data-interface.md) before deciding eligibility or emitting metadata | |
| 11 | | `pptx_structure.mode: structured` | [`pptx-structure-interface.md`](./pptx-structure-interface.md) | |
| 12 | |
| 13 | **Default — shared aesthetic baseline (may be overridden by explicit user, installed template / brand, or locked / Quick-resolved visual-style requirements)**: Required / Forbidden technical contracts remain absolute. When a higher authority is silent, build clear hierarchy through typography and leading, alignment, negative space, purposeful imagery / icons, and restrained repetition before decoration. Deliberate tightness, imbalance, off-axis placement, or container-heavy structure remains valid when that authority calls for it. |
| 14 | |
| 15 | | Concern | Shared default | |
| 16 | |---|---| |
| 17 | | Text-block rhythm | Use §4.2 leading. Make the baseline step into a new paragraph visibly larger than the intra-paragraph line step; keep the extra gap between list items smaller than paragraph separation but large enough to scan each item. Repeated peer blocks share one rhythm unless their hierarchy differs. | |
| 18 | | Natural wrapping | Break at semantic phrase or punctuation boundaries where possible. Reflow the text frame or adjust neighboring geometry before using any permitted local size reduction. Let the final line run naturally shorter; avoid mechanically equal lines or a stranded single-character / single-word line when an earlier natural break preserves meaning. | |
| 19 | | Alignment and proximity | Establish shared axes from the current composition. Align related titles, copy, labels, images, and diagram nodes to those edges, centers, or baselines; group related elements more tightly than unrelated groups so spacing carries hierarchy. Break an axis only when the offset performs hierarchy, direction, or tension. | |
| 20 | | Visual weight | Judge weight from area, darkness, saturation, density, stroke, image detail, and elevation together. Distribute it to support the focal path; symmetry is optional, and deliberate imbalance may create direction. | |
| 21 | | Containers | Use a card or panel when it expresses grouping, hierarchy, boundary, capacity, or a distinct material plane. Otherwise prefer spacing, rules, or direct text / geometry; peer containers share treatment unless a semantic difference justifies contrast. | |
| 22 | |
| 23 | **Default — active effects vocabulary (may resolve to no added technique when no visual job is diagnosed)**: Default and Quick Generate run the already-loaded [`svg-effects.md`](./svg-effects.md) §6.1 Visual Job Router before completing each page; apply a compatible technique only for a diagnosed visual job. |
| 24 | |
| 25 | **Fidelity labels**: |
| 26 | |
| 27 | | Label | Meaning | |
| 28 | |---|---| |
| 29 | | `Native-stable` | Generated PPTX uses the corresponding native DrawingML property or object and retains the documented semantics within the technique-specific limits. | |
| 30 | | `Native-normalized` | Export targets an editable DrawingML equivalent, but normalizes the SVG into another structure such as a freeform, run property, or simplified paint/effect. | |
| 31 | | `Approximate` | DrawingML has no exact SVG equivalent; export targets the intended effect through a documented approximation, and material differences require output review. | |
| 32 | | `Bake-required` | The runtime effect is outside the native contract; pre-render it into an image or rebuild it with explicit supported geometry. | |
| 33 | |
| 34 | **Reading rules**: |
| 35 | |
| 36 | - **Required** / **Forbidden** statements are non-negotiable technical boundaries. |
| 37 | - **Conditional** contracts apply only when the corresponding feature is used. |
| 38 | - **Reference — not a constraint** passages expose capabilities and recipes; they do not require every page or visual style to use them. |
| 39 | - The locked `visual_style` controls whether and how strongly a compatible effect is used. It never expands the technical boundary. |
| 40 | |
| 41 | **Hard rule — generated authoring is fail-closed**: `svg_output/` and reusable |
| 42 | template SVGs may use only properties and conditional interfaces explicitly |
| 43 | listed in this file or a triggered module in the routing table above. `svg_quality_checker.py` rejects unknown inline visual |
| 44 | properties and conditional contracts that have no reliable compatibility |
| 45 | mapping; documented fallback forms remain valid and receive warnings. |
| 46 | |
| 47 | **Default — recommended authoring and supported input stay separate (may |
| 48 | preserve supported input)**: generated SVG uses one predictable default |
| 49 | spelling, while converter-supported equivalent spellings remain valid input. |
| 50 | The checker may recommend normalization, but such warnings do not require |
| 51 | modification or block export. Only invalid, unsafe, or unreliably convertible |
| 52 | input is an error; do not remove converter support to enforce a narrower |
| 53 | generation preference. |
| 54 | |
| 55 | **Hard rule — one-way fidelity vocabulary**: the labels above describe the |
| 56 | `svg_output/` → generated PPTX path. They do not promise reconstruction of the |
| 57 | original SVG syntax, `<defs>` graph, `<use>` structure, path commands, or |
| 58 | `<tspan>` layout after PPTX-to-SVG import, nor pixel identity across PowerPoint, |
| 59 | LibreOffice, Keynote, and WPS. |
| 60 | |
| 61 | **Hard rule — capability boundary**: a recipe never expands converter support. |
| 62 | Use only the target elements and syntax documented by each conditional |
| 63 | contract. Unsupported element tags fail preflight; browser-rendered attributes |
| 64 | outside these contracts must not be assumed to have a DrawingML mapping. |
| 65 | |
| 66 | --- |
| 67 | |
| 68 | |
| 69 | ## 1. Required Foundation, Forbidden Features, and Conditional Interfaces |
| 70 | |
| 71 | ### 1.0 Text characters: must be well-formed XML |
| 72 | |
| 73 | SVG is strict XML. Two rules for all text and attribute values: |
| 74 | |
| 75 | | Character category | Required form | Forbidden form | |
| 76 | |---|---|---| |
| 77 | | Typography & symbols (em dash, en dash, ©, ®, →, ·, NBSP, full-width punctuation, emoji…) | **Raw Unicode characters** — write `—` `–` `©` `®` `→` directly | HTML named entities — `—` `–` `©` `®` `→` `·` ` ` `…` `•` etc. | |
| 78 | | XML reserved characters (`&`, `<`, `>`, `"`, `'`) | **XML entities only** — `&` `<` `>` `"` `'` (e.g. `R&D`, `error < 5%`) | Bare `&` `<` `>` (e.g. `R&D`, `error < 5%`) | |
| 79 | |
| 80 | One offending character invalidates the file and aborts export. |
| 81 | |
| 82 | **Structural blacklist** (in addition to the character rules above): |
| 83 | |
| 84 | | Banned Feature | Description | |
| 85 | |----------------|-------------| |
| 86 | | `mask` | Masks | |
| 87 | | `<style>` | Embedded stylesheets | |
| 88 | | `class` | CSS selector attributes | |
| 89 | | External CSS | External stylesheet links | |
| 90 | | `<foreignObject>` | Embedded external content | |
| 91 | | `textPath` | Text along a path | |
| 92 | | `@font-face` | Custom font declarations | |
| 93 | | `<animate*>` / `<set>` | SVG animations | |
| 94 | | `<script>` / event attributes | Scripts and interactivity | |
| 95 | | `<iframe>` | Embedded frames | |
| 96 | |
| 97 | The blacklist above is exhaustive for globally forbidden structural syntax. |
| 98 | It is not a positive allowlist for every browser-rendered property. Features |
| 99 | that require a restricted form are valid only under the conditional contracts |
| 100 | below; unlisted visual properties are unsupported. |
| 101 | |
| 102 | **Hard rule — inline visual-property allowlist**: |
| 103 | |
| 104 | | Property family | Allowed inline `style` properties | |
| 105 | |---|---| |
| 106 | | Paint and line | `fill`, `stroke`, `stroke-width`, `stroke-dasharray`, `stroke-linecap`, `stroke-linejoin`, `fill-opacity`, `stroke-opacity`, `vector-effect` | |
| 107 | | Text | `font-family`, `font-size`, `font-weight`, `font-style`, `text-anchor`, `letter-spacing`, `text-decoration` | |
| 108 | | Alpha and definition paint | `opacity`, `stop-color`, `stop-opacity`, `flood-color`, `flood-opacity` | |
| 109 | | Literal geometry | The element-specific properties in §2.1 | |
| 110 | | Preview-only | `shape-rendering`; it does not change native geometry | |
| 111 | |
| 112 | **Default — ordinary generated paint**: the table allows inline placement of |
| 113 | those property names. New solid paint uses uppercase six-digit `#RRGGBB`; |
| 114 | `fill` / `stroke` may instead use lowercase `none` or an exact local |
| 115 | `url(#id)`. Load [`svg-effects.md`](./svg-effects.md) before authoring any |
| 116 | alternative compatible color spelling, alpha/opacity channel, dash/cap/join, |
| 117 | gradient, filter, or constructed paint/effect. Existing compatible alternatives |
| 118 | remain valid input and receive recommendation warnings rather than errors. |
| 119 | |
| 120 | Conditional properties with a required XML form stay out of inline style: |
| 121 | write `filter="url(#id)"`, `clip-path="url(#id)"`, and |
| 122 | `marker-start` / `marker-end` as direct attributes. `!important`, unknown CSS |
| 123 | properties, blend modes, isolation, and backdrop filters fail quality check. |
| 124 | |
| 125 | The table registers property names, not arbitrary CSS values. Ordinary generated |
| 126 | text uses a non-empty `font-family`, a finite positive unitless-px `font-size`, |
| 127 | `font-weight` of `normal` / `bold` / an integer hundred from `100` through |
| 128 | `900`, `font-style` of `normal` / `italic`, and `text-anchor` of `start` / |
| 129 | `middle` / `end`. Inheritable text declarations belong only on `<svg>`, `<g>`, |
| 130 | `<text>`, or `<tspan>`; `text-anchor` is invalid on `<tspan>`. Load |
| 131 | [`svg-effects.md`](./svg-effects.md) §6.7 before authoring tracking, |
| 132 | underline/strike, text outline/alpha, gradient text, or text filter effects. |
| 133 | Unknown or unmapped declarations fail Checker preflight and native export. |
| 134 | |
| 135 | > **`marker-start` / `marker-end` is conditional** — see §1.1. |
| 136 | > |
| 137 | > **`clipPath` on `<image>` is conditional** — see §1.2. |
| 138 | > |
| 139 | > **Static same-document `<use>` is conditional** — see §1.3. |
| 140 | > |
| 141 | > **Imported native-shape metadata is conditional** — see §1.4. |
| 142 | > |
| 143 | > **Authored native preset fragments are conditional** — see §1.5. |
| 144 | > |
| 145 | > **Inline CSS geometry, simple gradients, filters, and approximate group |
| 146 | > opacity are conditional** — see §2 and [`svg-effects.md`](./svg-effects.md). |
| 147 | > |
| 148 | > **PPT preset patterns and native chart/table/template metadata are |
| 149 | > conditional** — see [`native-data-interface.md`](./native-data-interface.md) and [`pptx-structure-interface.md`](./pptx-structure-interface.md). |
| 150 | |
| 151 | DrawingML has no arbitrary per-pixel alpha-compositing path. Effects that rely |
| 152 | on one, including text-knockout image fills and arbitrary alpha composites, |
| 153 | must be baked into a raster asset before SVG export. |
| 154 | |
| 155 | --- |
| 156 | |
| 157 | ### 1.1 Line-end Markers (Conditional Contract) |
| 158 | |
| 159 | `marker-start` and `marker-end` are supported on `<line>` and `<path>` only |
| 160 | when the referenced marker fits this native-arrow contract: |
| 161 | |
| 162 | | Concern | Required form | |
| 163 | |---|---| |
| 164 | | Reference | Exact local `url(#id)` to a `<marker>` in `<defs>` | |
| 165 | | Orientation | `orient="auto"` or `orient="auto-start-reverse"`; the latter reverses `marker-start` while behaving like `auto` at `marker-end` | |
| 166 | | Shape | One direct shape representing a DrawingML `triangle`, `stealth`, `arrow`, `diamond`, or `oval` line end: a 3-vertex `<polygon>` / closed path (triangle), a simple concave 4-vertex `<polygon>` / closed path (stealth), an open 3-vertex path (arrow), a simple convex 4-vertex `<polygon>` / closed path (diamond), or one `<circle>` / `<ellipse>` (oval) | |
| 167 | | Path grammar | Use one explicit `M`/`L` command per vertex. Triangle, stealth, and diamond paths end in `Z`; arrow paths remain open after the third vertex. Do not use `H`, `V`, curves, or an implicit multi-point `L` command inside a marker path | |
| 168 | | Color parity | Triangle, stealth, diamond, and oval use a fill matching the parent line stroke. The open arrow uses `fill="none"` and a stroke matching the parent line stroke. DrawingML line ends inherit the line color | |
| 169 | |
| 170 | The converter maps these five shapes to their corresponding DrawingML line-end |
| 171 | types. Prefer `<polygon>` for the closed triangle, stealth, and diamond forms; |
| 172 | the open arrow form requires `<path>`. Four-vertex shapes must be simple and |
| 173 | non-degenerate: convex geometry maps to diamond and concave geometry maps to |
| 174 | stealth. Checker and exporter preflight consume this same contract; other |
| 175 | marker shapes have no native mapping and block export instead of being silently |
| 176 | dropped. |
| 177 | |
| 178 | PPTX import compatibility, tolerant recovery, strict-mode rejection, and |
| 179 | diagnostic behavior are indexed in |
| 180 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary). |
| 181 | |
| 182 | --- |
| 183 | |
| 184 | ### 1.2 Image Clipping (Conditional Contract) |
| 185 | |
| 186 | `clip-path` maps natively only on SVG `<image>` (including an exact crop |
| 187 | wrapper's inner image) under this contract. Legacy imported crops may retain |
| 188 | an outer-wrapper clip as compatible input: |
| 189 | |
| 190 | | Concern | Required form | |
| 191 | |---|---| |
| 192 | | SVG-namespace `<clipPath>` defined inside `<defs>` | Converter looks up one exact local id; missing, duplicate, foreign-namespace, or malformed references fail | |
| 193 | | Contains exactly one direct SVG-namespace supported shape child | Multiple shapes are not composited | |
| 194 | | Shape is one of: `<circle>`, `<ellipse>`, `<rect>` (optional rx/ry), `<path>`, `<polygon>` | These map to DrawingML geometry (preset or custom) | |
| 195 | | No `clip-rule` or `fill-rule`, whether direct or in inline `style` | DrawingML picture geometry has no equivalent winding-rule control | |
| 196 | | Used only on `<image>` or a compatible legacy imported crop wrapper | Shapes, groups, text, and generalized nested SVG targets are **forbidden** | |
| 197 | |
| 198 | | SVG clip shape | DrawingML output | |
| 199 | |---|---| |
| 200 | | `<circle>` / `<ellipse>` | Full-frame `<a:prstGeom prst="ellipse"/>`; the child must exactly cover the image frame. A `userSpaceOnUse` circle requires a square physical frame; a normalized `objectBoundingBox` circle may fill any frame | |
| 201 | | `<rect>` / `<rect rx="..."/>` | A plain full-frame rect is a compatible no-op; rounded form maps to full-frame `<a:prstGeom prst="roundRect"/>` with one physical radius adjustment. The rect must exactly cover the image frame and cannot express non-uniform physical corner radii | |
| 202 | | `<path>` / `<polygon>` | `<a:custGeom>` with coordinates mapped into the image frame | |
| 203 | |
| 204 | `clip-path` on shapes, groups, or text is forbidden; author the target geometry |
| 205 | directly instead. Use a path/polygon clip when the intended contour does not |
| 206 | cover the full picture frame. A contour that depends on even-odd or another |
| 207 | explicit winding rule is outside this mapping and must be rebuilt as one |
| 208 | unambiguous visible contour or pre-rendered. |
| 209 | |
| 210 | --- |
| 211 | |
| 212 | ### 1.3 Static Same-Document `<use>` (Conditional Contract) |
| 213 | |
| 214 | **Expansion contract**: Static local reuse is compile-time authoring shorthand. `finalize_svg.py` and |
| 215 | native export replace each qualifying instance with cloned primitive content; |
| 216 | PPTX-to-SVG import emits the resulting primitives and does **not** reconstruct |
| 217 | the original `<use>` / `<symbol>` structure. |
| 218 | |
| 219 | | Concern | Required form | |
| 220 | |---|---| |
| 221 | | Reference syntax | Author new SVG with the SVG 2 form `href="#id"`. Legacy `xlink:href="#id"` remains read-compatible and Live Preview normalizes it to `href`; if both attributes exist, their values MUST match. | |
| 222 | | Referenced target | One of `<symbol>`, `<g>`, `<use>`, `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<path>`, `<polygon>`, `<polyline>`, `<text>`, or `<image>`. Nested local `<use>` is recursively expanded. | |
| 223 | | Instance position | Generated `<use x>` / `<use y>` use finite unitless values; an explicit `px` suffix is read-compatible. Omitted values default to `0`. | |
| 224 | | Symbol viewport | A referenced `<symbol>` MUST have a finite four-number `viewBox` with positive width/height. Its `<use>` MUST have positive finite unitless `width` and `height`; an explicit `px` suffix is read-compatible. | |
| 225 | | Aspect ratio | Default/aligned `meet` values and plain `preserveAspectRatio="none"` are supported. `slice`, `refX`, and `refY` are forbidden. | |
| 226 | | Viewport boundary | Symbol artwork MUST stay inside its `viewBox`; expansion does not reproduce symbol overflow clipping. | |
| 227 | | Internal references | Author exact `href="#id"` and `url(#id)` fragments. The expander also reads legacy `xlink:href="#id"` and rewrites all instance-local cloned IDs. | |
| 228 | | Structural metadata | Neither the `<use>` instance nor its referenced subtree may carry `data-pptx-layer*`, chart/table replacement metadata (`data-pptx-replace-with`, `data-pptx-replacement-*`, `data-pptx-import-source`, or `data-pptx-fallback-*`), or `data-pptx-placeholder*`. Author those objects directly instead of reusing them. | |
| 229 | | Safety limits | A reachable reference chain may contain at most 64 instances, and one SVG may expand at most 10,000 local `<use>` instances. | |
| 230 | |
| 231 | **Forbidden — unsafe local references**: |
| 232 | |
| 233 | - External/file/data URLs, missing targets, conflicting `href` / `xlink:href`, |
| 234 | unsupported target elements, and circular reference chains |
| 235 | - Duplicate IDs on the referenced target, the `<use>` instance, or anywhere in |
| 236 | the reused subtree |
| 237 | - Quoted/whitespace CSS fragment variants such as `url('#id')`; use exact |
| 238 | `url(#id)` when an internal paint/filter/clip reference must be rewritten |
| 239 | |
| 240 | **Contract example**: |
| 241 | |
| 242 | ```xml |
| 243 | <svg xmlns="http://www.w3.org/2000/svg"> |
| 244 | <defs> |
| 245 | <symbol id="statusDot" viewBox="0 0 20 20" preserveAspectRatio="xMidYMid meet"> |
| 246 | <circle cx="10" cy="10" r="8" fill="#16A34A"/> |
| 247 | </symbol> |
| 248 | <g id="legendRow"> |
| 249 | <rect width="120" height="32" rx="8" fill="#F1F5F9"/> |
| 250 | <text x="42" y="22" font-size="16" fill="#0F172A">Ready</text> |
| 251 | </g> |
| 252 | </defs> |
| 253 | <use href="#statusDot" x="80" y="120" width="32" height="32"/> |
| 254 | <use href="#legendRow" x="120" y="120"/> |
| 255 | </svg> |
| 256 | ``` |
| 257 | |
| 258 | --- |
| 259 | |
| 260 | ### 1.4 Imported Native PowerPoint Shapes (Conditional Contract) |
| 261 | |
| 262 | `pptx_to_svg.py` emits rendering-neutral metadata when a visible SVG object |
| 263 | originates from `p:sp`, `p:cxnSp`, or `p:grpSp`. This contract is for lossless |
| 264 | import SVGs and unchanged imported objects that remain Slide-local or inside a |
| 265 | slot during mirror materialization. Ordinary authored SVG does not need these |
| 266 | attributes, and no separate source-payload opt-in marker exists. |
| 267 | |
| 268 | | Metadata | Placement | Required behavior | |
| 269 | |---|---|---| |
| 270 | | `data-pptx-object` | Logical `<g>` and native carrier | `shape`, `connector`, `group`, or `picture`; never infer the object kind from path appearance. | |
| 271 | | `data-pptx-shape-id` + `data-pptx-shape-scope` | Logical `<g>` and carrier | Preserve the source part-scoped identity. Export remaps duplicate Master/Layout/Slide ids into page-unique ids before rebinding connector references. | |
| 272 | | `data-pptx-frame="x y width height"` | Logical `<g>` and carrier | Own native `a:xfrm` position and size. Lossless import SVGs and tool-side native records use sufficient precision for exact EMU recovery; the model-facing authoring IR may use the compact page-coordinate spelling defined below. Path bounds, stroke, markers, shadows, and text glyph bounds never replace this frame. | |
| 273 | | `data-pptx-prst` | Preset carrier and logical `<g>` | One of the locked 187 DrawingML `ST_ShapeType` values. | |
| 274 | | `data-pptx-av-*` | Preset carrier and logical `<g>` | Preserve the complete validated DrawingML adjustment formula, including non-`val` formulas. | |
| 275 | | `data-pptx-part="geometry"` | One hidden carrier path | The single native export authority for frame, base fill/line/effect, preset/custom geometry, and object identity. | |
| 276 | | `data-pptx-part="geometry-preview"` / `geometry-detail` | Visible preview group/paths | Render the preset's independent path fill/stroke layers. A hash-locked preview group may mirror the carrier's one filter so a multi-path preset renders one aggregate imported effect; these elements are never emitted as duplicate PowerPoint shapes. | |
| 277 | | `data-pptx-preview-sha256` | Logical preset `<g>` and carrier | Detect edits to visible preset paths or paint. A stale preview fails quality check/export instead of silently reusing old native metadata. | |
| 278 | | `data-pptx-geometry-kind="custom"` + `data-pptx-custgeom` or `data-pptx-custgeom-ref` | Custom-geometry carrier | Preserve the validated original `a:custGeom` subtree. If the visible path hash is unchanged, export writes formulas, handles, connection sites, text rectangle, and path list exactly; edited paths compile from current SVG geometry. | |
| 279 | | `data-pptx-start/end-shape-id/site` | Connector logical `<g>` and carrier | Restore `a:stCxn` / `a:endCxn` after scoped shape-id allocation. A connector may retain one zero frame axis; it must not be expanded from visible stroke or marker bounds. | |
| 280 | | `data-pptx-shape-style` or `data-pptx-shape-style-ref` | Native carrier | Preserve a relationship-free `p:style` independently of text, including shapes with no visible text. | |
| 281 | | `data-pptx-effect-status="unsupported"` + `data-pptx-effect-reason` | Imported `p:sp` / `p:cxnSp` logical object and native carrier; imported `p:pic` carrier and logical object; imported `p:grpSp` logical group; imported table `p:graphicFrame` logical group | Record why an encountered source object or text-run `effectLst` / `effectDag` cannot enter the registered target-specific effect mapping without changing semantics. Checker and export stop with the recorded reason; these attributes are diagnostics, not a preserved effect payload or authoring syntax. | |
| 282 | | `metadata[data-pptx-part="txbody"]` with inline Base64 or `data-pptx-ref` | Logical shape `<g>` | Preserve unchanged `p:txBody`, including an empty text body. Content, whitespace, positioning, visible typography, or incompatible child-topology edits invalidate the payload. A source payload with run-level effects then blocks checker/export instead of losing those effects; an effect-free payload uses the normal SVG text fallback. | |
| 283 | |
| 284 | **Hard rule — compact native metadata transport**: Type A mirror |
| 285 | materialization moves `p:txBody`, relationship-free `p:style`, and |
| 286 | `a:custGeom` payloads into the content-addressed |
| 287 | `templates/native_payloads.json.gz` store. It also deduplicates repeated native |
| 288 | restoration fields—object identity, frame, preset/custom-geometry guards, |
| 289 | preview/text hashes, connector endpoints, payload references, and adjustment |
| 290 | formulas—into short `data-pptx-native-ref` records in the same store. Checker, |
| 291 | template-structure validation, and export validate and hydrate both layers in |
| 292 | memory. Keep Master/Layout, placeholder, layer, editable-object, diagnostic, |
| 293 | and editable chart/table metadata inline. Legacy inline Base64 and v1 |
| 294 | payload-only stores remain readable. |
| 295 | |
| 296 | One effect reason remains its existing plain token. If one imported object has |
| 297 | multiple independent unsupported reasons, both marker copies store the same |
| 298 | deduplicated, lexicographically sorted compact JSON string array in |
| 299 | `data-pptx-effect-reason`; adding a later reason must not overwrite an earlier |
| 300 | one. This array is still diagnostic metadata, not an authoring surface. |
| 301 | |
| 302 | **Import/authoring representation split**: |
| 303 | |
| 304 | | Representation | Contract | |
| 305 | |---|---| |
| 306 | | Lossless import SVG | Keep complete native payload, hidden carriers, and preview evidence in the temporary analysis workspace. It is immutable native-payload backing, not the editable template source. | |
| 307 | | Authoring IR bundle | Keep editable SVGs plus model-readable `authoring_summary.json` and tool-only `authoring_manifest.json`. Exclude opaque payload and duplicate hidden carriers from model context while retaining visible shape intent and a stable document-local `data-pptx-source-ref` on each imported logical object. Compact model-facing imported frames and safe transform page coordinates to at most two decimals before hashing the IR. The summary owns the compact current-file index; the manifest owns source paths and initial hashes and never enters model context. | |
| 308 | | `standard` / `fidelity` output | Use the compact authored-preset contract (§1.5) for newly authored stock shapes; do not transplant opaque import payload or source topology. | |
| 309 | | `mirror` output | Materialize from the edited authoring IR. Rehydrate supported imported metadata only when a Slide-local/slot object's source ref and initial authoring hash still match; otherwise keep the current SVG fallback. Expand fixed Master/Layout group wrappers into direct semantic atoms while preserving source ownership, paint order, and visible appearance. | |
| 310 | |
| 311 | **Hard rule — model-facing page-coordinate precision**: |
| 312 | |
| 313 | | Surface | Precision contract | |
| 314 | |---|---| |
| 315 | | Imported `data-pptx-frame` in authoring IR | At most two decimals. An unchanged mirror source ref recovers the exact lossless frame before tool-side native-record externalization. | |
| 316 | | `data-pptx-bounds` in generated and final template SVG | At most two decimals. | |
| 317 | | `translate(...)`, `rotate(... cx cy)`, and `matrix(... e f)` | Translation values and rotation centers use at most two decimals. Keep the rotation angle and matrix `a b c d` coefficients unchanged. | |
| 318 | | Protected values | Do not apply this compaction to path/points geometry, normalized crop or nested `viewBox` ratios, gradient offsets, opacity, scale arguments, canonical authored-preset frames, or lossless/tool-side native frames. | |
| 319 | |
| 320 | **Hard rule — authoring source refs**: `data-pptx-source-ref` is reserved for |
| 321 | the create-template authoring IR. Its value is unique within one authoring SVG, |
| 322 | not across the workspace, and must be resolved through that document's |
| 323 | `authoring_manifest.json` record by the owning tool. Models MUST NOT read that |
| 324 | machine manifest. Moving a referenced subtree into |
| 325 | `icons/imported/` for readability must preserve the attribute and record it in |
| 326 | the vector inventory; re-inlining re-establishes the same mapping. Final materialized |
| 327 | template SVGs and normal project `svg_output/` must not contain this attribute. |
| 328 | |
| 329 | **Hard rule — structural-layer boundary**: An unchanged imported logical object |
| 330 | may keep currently supported metadata while it remains Slide-local or inside a |
| 331 | slot. An imported logical `<g>` cannot be assigned to Master/Layout because |
| 332 | those layers require direct semantic atoms. Mechanically expand a fixed-layer |
| 333 | source group into direct atoms, rebuilding a preset when supported and |
| 334 | otherwise retaining the visible SVG fallback. A newly authored compact preset |
| 335 | `<g>` from §1.5 is the sole group exception: validation proves that it compiles |
| 336 | to exactly one native shape/connector. Do not use this normalization to change |
| 337 | ownership or appearance. |
| 338 | |
| 339 | **Hard rule — selective payload**: Do not copy every imported metadata block into |
| 340 | an authored template. Keep the full lossless import SVG separately as immutable |
| 341 | audit/fallback backing. Mirror may reuse only metadata already supported by the |
| 342 | converter on source-ref/hash-matching Slide-local/slot objects; unsupported or |
| 343 | edited objects use the current SVG fallback. `data-pptx-replace-with` remains reserved for the |
| 344 | optional PowerPoint-native Chart/Table replacement contract. |
| 345 | |
| 346 | **Registry and rendering rules**: |
| 347 | |
| 348 | - The hash-locked shared registry must equal the independent 187-value shape |
| 349 | catalog. Missing, duplicate, unknown, or corrupt definitions fail closed. |
| 350 | - Preset preview paths come from the shared DrawingML formula evaluator; do not |
| 351 | add per-shape Python geometry handlers. |
| 352 | - Preset size is controlled only by `data-pptx-frame` / `a:xfrm`. Adjustment |
| 353 | formulas control the contour inside that frame and are not rescaled when the |
| 354 | frame changes. |
| 355 | - A group transform may move, scale, rotate, or flip the complete logical |
| 356 | shape without invalidating its preview fingerprint. Editing a generated |
| 357 | `geometry-detail` path directly is unsupported unless the carrier metadata |
| 358 | and preview fingerprint are regenerated together. |
| 359 | - Unknown or malformed SVG transform operations fail closed. DrawingML cannot |
| 360 | represent arbitrary shear, so a non-orthogonal transform must stop native |
| 361 | export instead of being silently approximated as rotation and scale. |
| 362 | - Opaque XML payloads containing any `r:*` relationship attribute are never |
| 363 | copied into a new slide part. Relationship-bearing text content and |
| 364 | shape-level `a:blipFill` use the existing rebuilt visual fallback and are |
| 365 | not covered by atomic `p:sp + p:txBody` rehydration. |
| 366 | - Unknown future presets and explicit `unsupported` geometry status never |
| 367 | downgrade silently to `rect`; native export stops with the recorded reason. |
| 368 | |
| 369 | **Fidelity boundary**: native preset/custom geometry, logical frame, scoped |
| 370 | identity, connector topology, and relationship-free unchanged horizontal |
| 371 | text-body semantics on ordinary shape fills are `Native-stable`. The SVG |
| 372 | preview paint for gradient/pattern |
| 373 | `darken`/`lighten` layers is `Native-normalized`; original group child |
| 374 | coordinates, shape-level image-fill reconstruction, and vertical-text |
| 375 | reconstruction are also normalized rather than byte-identical OOXML. |
| 376 | |
| 377 | --- |
| 378 | |
| 379 | ### 1.5 Authored Native PowerPoint Presets (Conditional Contract) |
| 380 | |
| 381 | New SVG pages and project-owned canonical reusable templates may opt one |
| 382 | complete geometric object into a native DrawingML preset through the |
| 383 | deterministic fragment helper. Selection behavior lives in |
| 384 | [`native-shape-authoring.md`](./native-shape-authoring.md); this section owns |
| 385 | the machine contract. This compact canonical form describes the intended |
| 386 | preset, frame, adjustments, and paint once, keeps only registry-generated |
| 387 | visible SVG paths, and embeds no source OOXML or serialized preview fingerprint. |
| 388 | |
| 389 | | Metadata / structure | Required behavior | |
| 390 | |---|---| |
| 391 | | `data-pptx-authoring="preset"` | Appears once on the logical `<g>`; distinguishes strict project authoring from legacy/imported metadata. | |
| 392 | | `data-pptx-object` | `shape` or `connector`; connector-family presets must use `connector`, and `connector` must use a connector-family preset. Authored connectors require `fill="none"` plus a visible stroke and export as unconnected `p:cxnSp`. | |
| 393 | | `data-pptx-prst`, `data-pptx-frame`, `data-pptx-av-*` | Generated together from the locked registry and written once on the logical group. The frame is the helper's exact four-part, space-separated ordinary-decimal spelling and remains authoritative even when visible path bounds differ; commas, scientific notation, leading `+`, and redundant decimal spellings are rejected. | |
| 394 | | Local `fill` / `stroke` plus supported paint attributes | Base paint is written once on the group; a visible stroke also carries an explicit width. Canonical page/template authoring keeps channel paint local. Compatible ancestor paint/opacity may compose under the general SVG rules and receives a recommendation warning. | |
| 395 | | Ordered direct `<path>` children | Browser-visible registry layers only. Each child writes just its required path-level fill/stroke override; labels and decorations stay outside the atomic group. | |
| 396 | | No carrier / wrapper / fingerprint | `data-pptx-part`, hidden geometry carriers, preview wrappers, and `data-pptx-preview-sha256` belong to expanded import/compatibility transport, not canonical project authoring. | |
| 397 | |
| 398 | Generate one fragment at a time: |
| 399 | |
| 400 | ```bash |
| 401 | python3 ${SKILL_DIR}/scripts/preset_shape_svg.py render rightArrow \ |
| 402 | --id p03-growth-arrow \ |
| 403 | --frame 160 210 320 112 \ |
| 404 | --fill "#2563EB" \ |
| 405 | --stroke none \ |
| 406 | --adjust "adj1=val 50000" |
| 407 | ``` |
| 408 | |
| 409 | **Hard rule — helper-only metadata**: never add or edit authored preset |
| 410 | metadata or registry paths by hand. The compact helper output is atomic. |
| 411 | Regenerate it when preset, frame, adjustment, fill, stroke, or stroke width |
| 412 | changes. Replace the whole fragment with ordinary SVG when free contour editing |
| 413 | is required. |
| 414 | |
| 415 | Template ownership metadata is orthogonal to preset geometry. After inserting |
| 416 | the complete helper output, `create-template` may add only the registered |
| 417 | `data-pptx-layer`, `data-pptx-editable`, `data-pptx-carrier`, or |
| 418 | `data-pptx-role` attribute needed by the surrounding structured contract. It |
| 419 | must not change preset/frame/adjustment/paint metadata or any direct path. |
| 420 | |
| 421 | **Reusable-template boundary**: a project-owned canonical template may retain |
| 422 | one complete helper-generated atomic fragment when the stock preset is an exact |
| 423 | semantic match and its paint stays inside the authoring boundary below. The |
| 424 | fragment is an executable exemplar and one semantic atom, not a freely editable |
| 425 | template primitive. It may be Slide-local, the one carrier of an `object` slot, |
| 426 | or a direct Master/Layout fixed atom. An adaptation may reuse it unchanged only |
| 427 | when preset, frame, adjustments, and paint are unchanged; otherwise regenerate |
| 428 | the whole fragment with the helper. |
| 429 | Imported, mirror, and third-party templates are never upgraded by contour |
| 430 | inference. |
| 431 | |
| 432 | **Hard rule — visible page closure**: the helper prints a complete visible |
| 433 | fragment to stdout; export never invents its preview. The main Agent inserts |
| 434 | that output into the hand-authored page or canonical reusable template. The |
| 435 | helper cannot write a project, select layout, or generate a page. |
| 436 | |
| 437 | **Authoring paint boundary**: v1 accepts `none` or six-digit solid HEX fill and |
| 438 | stroke, optional fill/stroke opacity, stroke width, line cap, and line join. |
| 439 | Normal generated pages use `spec_lock.md` for stable semantic color anchors and |
| 440 | choose page-local paint from the retained Design Spec, style, and composition context. |
| 441 | The lockless [`quick-generate`](../workflows/profiles/quick-generate.md) profile |
| 442 | keeps every chosen paint value explicit in the SVG. |
| 443 | `create-template` authored templates take their values from the confirmed brief |
| 444 | and template `design_spec.md`. |
| 445 | Use ordinary SVG for gradients, patterns, filters, or other treatments outside |
| 446 | this narrow contract. Registry-derived multi-path darken/lighten colors and |
| 447 | other contextual derivatives need no separate lock row unless they become a |
| 448 | recurring named role. Mirror preserves source paint under §1.4 instead. |
| 449 | |
| 450 | **Validation**: quality check and export both rerender authored fragments from |
| 451 | `preset + frame + adjustments + group paint` and compare every visible path and |
| 452 | path-level paint override directly. Registry-path edits, geometry metadata that |
| 453 | leaves those paths stale, unknown adjustments, out-of-range frames/transforms, |
| 454 | zero-scale transforms, and shear/skew fail closed. Export expands the validated |
| 455 | compact group only in memory and reuses the lossless native-shape conversion |
| 456 | path. Older authored carrier/preview fragments remain compatible as ordinary |
| 457 | Slide-local input and |
| 458 | receive a non-blocking migration warning; they do not gain the new compact |
| 459 | group's structured-atom exception. `pptx_to_svg` expanded output remains the |
| 460 | lossless round-trip form and is not warned as authored input. |
| 461 | |
| 462 | **Fidelity boundary**: an unchanged authored fragment is `Native-stable` as |
| 463 | one `p:sp` or `p:cxnSp`. Text remains outside the atomic fragment and may export |
| 464 | as a grouped editable text box. Authoring v1 creates only unconnected |
| 465 | `p:cxnSp`; it does not accept hand-written endpoint/site metadata. An |
| 466 | `actionButton*` preset maps visual geometry only. Preset appearance never |
| 467 | invents connector attachment, action behavior, navigation targets, or |
| 468 | hyperlinks. |
| 469 | |
| 470 | --- |
| 471 | |
| 472 | ## 2. Conditional Compatibility Mappings |
| 473 | |
| 474 | ### 2.1 Literal Geometry Lengths and Inline Geometry |
| 475 | |
| 476 | **Hard rule — direct geometry length grammar**: New generated SVG writes the |
| 477 | following XML geometry values and `stroke-width` as finite unitless ordinary |
| 478 | decimals in the page `viewBox` coordinate space, for example `x="120"` and |
| 479 | `stroke-width="2"`. The explicit `px` suffix is read-compatible and receives a |
| 480 | recommendation warning. No other unit is registered for this surface. |
| 481 | |
| 482 | | Element / surface | Direct length attributes | |
| 483 | |---|---| |
| 484 | | `<svg>`, `<rect>`, `<image>`, `<use>` | `x`, `y`, `width`, `height`; `<rect>` also `rx`, `ry` | |
| 485 | | `<circle>` | `cx`, `cy`, `r` | |
| 486 | | `<ellipse>` | `cx`, `cy`, `rx`, `ry` | |
| 487 | | `<line>` | `x1`, `y1`, `x2`, `y2` | |
| 488 | | `<text>` / positional `<tspan>` | `x`, `y`; `<tspan>` also `dx`, `dy` | |
| 489 | | Any supported painted element | `stroke-width` | |
| 490 | |
| 491 | `width`, `height`, `r`, `rx`, `ry`, and `stroke-width` must be non-negative; |
| 492 | the stricter positive `<use>` symbol-viewport rule remains in §1.3. `pt`, |
| 493 | `pc` / `pica`, `in`, `cm`, `mm`, `q`, `em`, `rem`, percentages, unknown units, |
| 494 | non-finite values, expressions, scientific notation, leading plus signs, and |
| 495 | trailing decimal points are invalid here even when generic SVG/CSS defines |
| 496 | them. A missing attribute may use its documented SVG/project default; an |
| 497 | explicitly supplied invalid value never falls back to that default. |
| 498 | |
| 499 | The following geometry properties may appear in the same element's |
| 500 | `style="..."`. The pipeline materializes them as |
| 501 | XML geometry attributes before SVG post-processing and native PPTX conversion. |
| 502 | An inline geometry declaration overrides an existing same-name XML attribute. |
| 503 | |
| 504 | | Element | Recognized properties | |
| 505 | |---|---| |
| 506 | | `<rect>` | `x`, `y`, `width`, `height`, `rx`, `ry` | |
| 507 | | `<circle>` | `cx`, `cy`, `r` | |
| 508 | | `<ellipse>` | `cx`, `cy`, `rx`, `ry` | |
| 509 | | `<image>` | `x`, `y`, `width`, `height` | |
| 510 | | `<svg>` | `x`, `y`, `width`, `height` | |
| 511 | | `<use>` | `x`, `y`, `width`, `height` | |
| 512 | |
| 513 | **Hard rule — inline geometry grammar**: every non-zero value is one finite |
| 514 | `px` literal, such as `120px` or `-8.5px`; exact zero may be unitless. `width`, |
| 515 | `height`, `rx`, `ry`, and `r` must be non-negative. Percentages, `auto`, |
| 516 | `calc()`, `var()`, `!important`, `inherit`, and every other unit are forbidden. |
| 517 | Do not put geometry on an unsupported element: line endpoints, text positions, |
| 518 | path data, and polygon/polyline points remain XML attributes. |
| 519 | |
| 520 | **Forbidden — CSS geometry cascade**: `<style>`, `class`, selector rules, |
| 521 | external stylesheets, and imported styles remain forbidden. This contract is |
| 522 | only for literal declarations in an element's own `style` attribute; PPT Master |
| 523 | does not compute CSS cascade or custom properties. Root canvas authority remains |
| 524 | the `viewBox`, regardless of root `<svg>` compatibility width/height values. |
| 525 | |
| 526 | ### 2.2 Group Opacity Compatibility |
| 527 | |
| 528 | **Default — descendant alpha (may preserve compatible group opacity)**: New |
| 529 | `svg_output/` and reusable templates put alpha on the affected descendant |
| 530 | paint, text run, picture, or supported effect. DrawingML has no isolated |
| 531 | group-alpha model, so overlapping descendants can look different when one |
| 532 | group value is distributed across them. |
| 533 | |
| 534 | The converter nevertheless accepts `<g opacity="...">` and inline group |
| 535 | `opacity` by multiplying group alpha into descendants. That path is |
| 536 | `Approximate`; nested group/child alpha multiplies, and `--native-charts-and-tables` |
| 537 | rejects transparent native table/chart markers. The quality checker reports a |
| 538 | non-blocking fidelity warning so existing or intentionally authored input can |
| 539 | continue without modification. |
| 540 | |
| 541 | --- |
| 542 | |
| 543 | ## 3. Canvas Format Quick Reference |
| 544 | |
| 545 | Use the already locked canvas id and exact viewBox. [`canvas-formats.md`](canvas-formats.md) owns format selection; this core owns only SVG conformance on that canvas. The lockless [`quick-generate`](../workflows/profiles/quick-generate.md) profile uses its first SVG to establish the canvas; every remaining page must use the identical viewBox. |
| 546 | |
| 547 | --- |
| 548 | |
| 549 | ## 4. Required Page Contract and Conditional Packaging |
| 550 | |
| 551 | ### 4.0 Complete Page-Design Contract |
| 552 | |
| 553 | | Concern | Requirement | |
| 554 | |---|---| |
| 555 | | Visible slide result | The completed `svg_output/<slide>.svg` MUST contain every visible text, image, shape, diagram, chart/table fallback, background, and template-derived layout element intended for that slide. External visual assets are valid when the SVG references them explicitly. | |
| 556 | | Template/control inputs | Templates, `design_spec.md`, and `spec_lock.md` guide authoring. Do not depend on them to add visible elements after the page SVG is complete. | |
| 557 | | PPTX translation | The exporter may map represented SVG content to DrawingML/native objects and deduplicate represented elements into Master/Layout/Slide parts. It MUST NOT invent visible slide content absent from the SVG. | |
| 558 | | Excluded package behavior | Speaker notes, animations, transitions, narration audio, PPTX relationships, and direct native-PPTX workflows remain separately owned. They are not part of the SVG page-design contract. | |
| 559 | |
| 560 | **Hard rule — page-design closure**: A final page SVG is the sole visual/design authority for that page on every SVG-authoring route. SVG is not the authority for the entire PPTX package. |
| 561 | |
| 562 | ### 4.1 Semantic SVG Marker Contract |
| 563 | |
| 564 | Semantic markers are minimal compiler hints. Flat pages declare one root `data-pptx-page-role` and omit Master/Layout/layer/placeholder markers. Structured pages carry their final root identity, layer atoms, slots, and native-object metadata from authoring start and omit `data-pptx-page-role`. Use `data-pptx-role` with a stable `id` only when no specialized marker expresses page-frame behavior. Keep ordinary visible content in SVG attributes/text; [`semantic-svg.md`](semantic-svg.md) owns the vocabulary. |
| 565 | |
| 566 | - **Canvas authority**: New authoring writes `viewBox="0 0 W H"` with positive |
| 567 | integer pixels from the lock, or from the first SVG when the explicit |
| 568 | `quick-generate` profile is active. Numerically equivalent spellings and positive |
| 569 | fractional imported dimensions remain compatible; export quantizes once at |
| 570 | `1 SVG px = 9,525 EMU`. Invalid/non-finite values, non-zero origin, |
| 571 | non-positive size, or unsupported PowerPoint dimensions are errors. All pages |
| 572 | and Layout prototypes in one normal build share the numeric canvas and match |
| 573 | `spec_lock.md canvas.viewBox`; quick-generate pages match the first SVG; |
| 574 | standalone templates match `design_spec.md canvas_viewbox`. Optional root |
| 575 | `width`/`height` do not override `viewBox`. |
| 576 | Root `<svg>` transform is forbidden; nested crop and `<symbol viewBox>` keep |
| 577 | their own contracts. |
| 578 | - **Font portability**: font families used by the deck must resolve to installed |
| 579 | export faces. `@font-face` remains forbidden; the typography contract lives in |
| 580 | [`strategist.md §g`](strategist.md). |
| 581 | - **Icon placeholders**: `<use data-icon="library/name">` is a pipeline-specific |
| 582 | form, distinct from local SVG reuse. Follow the contract in |
| 583 | [`../templates/icons/README.md`](../templates/icons/README.md). |
| 584 | - **Local reuse**: ordinary same-document `<use>` follows §1.3. |
| 585 | |
| 586 | ### 4.2 Editability, Package Promotion, and Text Leading |
| 587 | |
| 588 | These forms are needed only when the stated PPT behavior matters: |
| 589 | |
| 590 | | Desired behavior | Required form | |
| 591 | |---|---| |
| 592 | | One editable PPT text frame with mixed formatting or multiline prose | Use one `<text>` per logical paragraph and non-positional `<tspan>` children for inline runs. Keep the first authored line as direct text; later lines use direct positioned `<tspan>` children that repeat parent `x` with positive relative `dy`; an all-`<tspan>` form may start at `dy="0"`. Default retains these breaks without PowerPoint wrapping; `--reflow-text` may join eligible lines. A font-size change, list marker, or larger accepted gap starts another paragraph. Sibling `<text>` elements are forbidden as one paragraph's line breaks; they remain valid for independent frames. | |
| 593 | | Stable object grouping or object-level animation anchor | Wrap the intended object in `<g id="...">`. Content grouping is **mandatory** per §4.3 — a top-level `<g id>` is also the animation anchor; it is not an optional convenience. | |
| 594 | | Native PowerPoint background promotion | Outside structured mode, the first eligible visual layer may be a direct full-canvas `<rect>` or one inside a simple single-child group. Its fill must have a registered native mapping (solid, linear/radial gradient, or preset pattern), and it must have no transform, filter, clip, rounding, or visible stroke. Export writes the fill as Slide `p:bg`; image elements remain pictures. Structured routes use the narrower explicit solid-background ownership contract in [`pptx-structure-interface.md`](./pptx-structure-interface.md). | |
| 595 | | Free-design / brand-only PowerPoint structure | Use `pptx_structure.mode: flat`. Keep every represented object Slide-local; export materializes one clean project-owned Master plus one Blank Layout from the current lock, removes stock content placeholders/Layout inventory, and retains only the standard date/footer/slide-number capability hooks. Do not author Master/Layout identities, layers, or placeholder slots. Quick-generate uses the same flat object ownership but converter-default theme scaffolding because no lock exists. | |
| 596 | | Reusable template-based PowerPoint Layout | Select one complete authoring SVG per page in `page_layouts`, declare each unique Master/Layout definition once, and assign pages through `page_pptx_layouts`. Strict preserves the prototype contract; adaptive retains its Master and uses a current or new Layout key already declared and assigned by Strategist. Construction cannot extend or mutate that mapping downstream. Non-mirror skin follows `spec_lock`. | |
| 597 | |
| 598 | **Default — leading by role and density (may be overridden for user, template, typeface, legibility, or locked visual-style fit)**: For direct positioned `<tspan>` rows, start multiline titles around `1.2–1.3 × font-size`, dense / small body around `1.4–1.5 ×`, ordinary body around `1.5–1.6 ×`, and large / sparse / breathing body around `1.6–2.0 ×`. These are starting ranges, not checker quotas; display headlines may be tighter when the selected style calls for it. Author the spacing as positive relative `dy`, not CSS/SVG `line-height`, which has no registered DrawingML mapping. |
| 599 | |
| 600 | **Hard rule — supported shape conversion**: Every PPT editability claim in this specification refers to the project converter reading `svg_output/` and emitting native DrawingML. `svg_final/` is a self-contained visual preview that may be inserted into PowerPoint as an SVG picture. PowerPoint's manual Convert-to-Shape operation is unsupported; do not narrow the authoring contract to its undocumented SVG subset. |
| 601 | |
| 602 | ### 4.3 Element Grouping (Mandatory) |
| 603 | |
| 604 | **Hard rule — root groups protect body-text layout**: Every visible direct root `<g>` declares positive root-coordinate `data-pptx-bounds="x y width height"`. Keep it when frame/native coordinates size one PowerPoint object; placeholder bounds also supply the slot frame. On flat pages, make each module zone as generous as the canvas and sibling layout allow without overlapping another module zone. Checker validates this subcanvas against the root `viewBox`, then recursively validates only estimable `<text>` descendants against it using the shared SVG-to-PPTX per-run width estimate and DrawingML wrapping headroom. It validates every estimable visible text carrier directly against the root `viewBox` with the same per-run estimate before that headroom. Nested groups and all shapes, images, paths, `<use>` instances, effects, and object frames are not module-boundary inputs. Per side, Checker ignores overflow through `1px`; module-boundary overflow warns through `5%` and fails above `5%`, while any larger root-`viewBox` text overflow fails. Bounds do not clip or reflow; unestimable visible text receives an advisory warning. The only page-boundary exception is a wholly off-canvas direct-root Morph endpoint marked `data-pptx-morph-staging="true"`; its own module bounds still apply, retained Morph uses an explicit pair, and the marker never excuses partial page overflow. |
| 605 | |
| 606 | Wrap each logical Slide-local body unit in one descriptive top-level `<g id>`; group count follows the page's semantic units, and each group becomes one stable animation target when animation is enabled. Generic deck-wide animation gives that target one step; an explicit animation sidecar may assign it several ordered effects. Nested implementation groups may remain anonymous and need no bounds; any nested bounds are ignored. Flat pages use ordinary groups; structured slots already qualify, while titles, direct atomic Master/Layout elements, and canvas-level static framing—including background images and full-canvas scrim/decoration rectangles—may remain root primitives. On flat pages, give such static framing a stable `id` plus `data-pptx-role="background"` / `"decoration"`; never add a `<g>` solely to silence an ungrouped-element advisory. |
| 607 | |
| 608 | **Reference — not a constraint**: A top-level semantic group may contain |
| 609 | descriptive nested `<g>` edit groups when its internal elements form useful |
| 610 | subunits, such as icon + title, value + label, or repeated information rows. |
| 611 | Nested groups carry no `data-pptx-bounds` and create no automatic animation |
| 612 | step; an unnecessary one-child wrapper may flatten. Choose whether and how |
| 613 | deeply to nest from the page's actual editing semantics—there is no default |
| 614 | nesting pattern, level, or quota. |
| 615 | |
| 616 | **Structural atoms and slots are excluded automatically.** `data-pptx-layer` and `data-pptx-placeholder` semantics are read first; otherwise explicit `data-pptx-role` values (`background`, `decoration`, `header`, `footer`, `chrome`, `watermark`, `page-number`, `logo`) mark Slide-local static framing (§4.1, [`semantic-svg.md`](semantic-svg.md)). A normal slot group has exactly one direct compatible carrier; several drawing atoms require the explicit composite `object` proxy fallback. Native chart/table carrier groups retain their specialized [`native-data-interface.md`](./native-data-interface.md) contract. |
| 617 | |
| 618 | **What to group** (one `<g id>` per unit): |
| 619 | |
| 620 | | Grouping unit | Contains | |
| 621 | |---|---| |
| 622 | | Card / panel | Background rect + optional shadow (only if it floats over a photo/colored panel, [`svg-effects.md`](./svg-effects.md) §6.4) + icon + title + body text | |
| 623 | | Process step | Number/marker + icon + label + description | |
| 624 | | List item | Bullet / number + icon + title + description | |
| 625 | | Icon-text combo | Icon element + adjacent label | |
| 626 | | Page header | Title + subtitle + accent decoration | |
| 627 | | Page footer | Page number + branding | |
| 628 | | Decorative cluster | Related decorative shapes (rings, dots, orbs) | |
| 629 | |
| 630 | An authored native preset fragment (§1.5) is already an atomic `<g id>` and |
| 631 | counts as one content group. Keep it top-level when it stands alone. When it |
| 632 | needs a label or decoration, place the preset and those siblings inside a |
| 633 | separate parent content group; never put them inside the preset group itself. |
| 634 | |
| 635 | **Forbidden**: |
| 636 | |
| 637 | - One giant `<g>` around the whole slide (collapses to a single animation step). |
| 638 | - Many ungrouped Slide-local `<rect>` / `<text>` / `<path>` atoms — they have no stable sidecar target and selection/editing degrades. Primitive fallback applies only when the root contains no top-level `<g>` at all; it is capped at 8 visible primitives. |
| 639 | - One top-level group per icon / text line / mark (too many animation steps). |
| 640 | - Anonymous top-level groups — every top-level semantic group needs a descriptive `id`. |
| 641 | |
| 642 | **Naming — required.** A descriptive, page-unique `id` on every top-level content `<g>` (`card-1`, `step-discover`, `header`, `footer`) is mandatory; it is the stable SVG-side animation and trace anchor. An anonymous top-level group still converts, but `animations.json` cannot reference it; an anonymous one-child implementation wrapper may also flatten. Primitive fallback is unrelated and applies only to roots with no top-level groups. |
| 643 | |
| 644 | ```xml |
| 645 | <g id="card-benefits-1" data-pptx-bounds="60 115 565 260"> |
| 646 | <!-- Shadow only if the card floats over a colored panel; on flat white, omit it. --> |
| 647 | <rect x="60" y="115" width="565" height="260" rx="20" fill="#FFFFFF" filter="url(#shadow)"/> |
| 648 | <use data-icon="chunk-filled/bolt" x="108" y="163" width="44" height="44" fill="#0071E3"/> |
| 649 | <g id="card-benefits-metric"> |
| 650 | <text x="105" y="270" font-size="56" font-weight="bold" fill="#0071E3">10×</text> |
| 651 | <text x="250" y="270" font-size="30" font-weight="bold" fill="#1D1D1F">Faster</text> |
| 652 | </g> |
| 653 | <text x="105" y="310" font-size="18" fill="#6E6E73">Reduce production time from days to hours.</text> |
| 654 | </g> |
| 655 | ``` |
| 656 | |
| 657 | --- |
| 658 | |
| 659 | ## 5. Workflow Authority |
| 660 | |
| 661 | The normal serial post-processing and export workflow belongs to |
| 662 | [`generate-pptx.md`](../workflows/generate-pptx.md) Step 7. The explicit |
| 663 | direct-generation exception belongs to |
| 664 | [`quick-generate.md`](../workflows/profiles/quick-generate.md). This file defines SVG |
| 665 | authoring boundaries and intentionally does not mirror commands, flags, or |
| 666 | output behavior. |
| 667 | |
| 668 | --- |
| 669 | |
| 670 | |
| 671 | ## 8. Scope Boundary |
| 672 | |
| 673 | Generate project structure, commands, quality-gate order, and export products |
| 674 | are owned by [`generate-pptx.md`](../workflows/generate-pptx.md) and its |
| 675 | selected profile. They are intentionally outside this SVG authoring policy. |
| 676 |