| 1 | > See [`shared-standards-core.md`](./shared-standards-core.md) for the mandatory SVG foundation. |
| 2 | |
| 3 | # SVG Effects and Geometry Specification |
| 4 | |
| 5 | Authority for advanced paint, effects, transforms, freeform/radial geometry, and constructed visual styles. Default and Quick Generate load it before SVG authoring; other SVG-authoring routes follow their workflow trigger. |
| 6 | |
| 7 | **Cross-reference map**: unqualified §1, §2, and §4 references point to [`shared-standards-core.md`](./shared-standards-core.md); §6 references are local to this file. |
| 8 | |
| 9 | ## 6. Advanced SVG Effects and Authoring Techniques |
| 10 | |
| 11 | **Mandatory**: Default and Quick Generate read this file completely before SVG |
| 12 | authoring and keep its compatible techniques in active construction vocabulary. |
| 13 | Before finalizing each page, run the §6.1 selection procedure and Visual Job |
| 14 | Router. Use §6.13 when diagnosed jobs benefit from one coordinated page recipe. |
| 15 | |
| 16 | **Default — situational use (may override when plain construction is stronger)**: |
| 17 | “Advanced” means capability depth, not an effect quota. During page authoring, |
| 18 | recall relevant techniques from content, hierarchy, legibility, semantics, |
| 19 | rhythm, and style; apply those that materially help. |
| 20 | |
| 21 | ### 6.1 Availability, Precedence, and Fidelity |
| 22 | |
| 23 | | Decision layer | Authority | |
| 24 | |---|---| |
| 25 | | Technical validity | Required / Forbidden / Conditional contracts in this file | |
| 26 | | Project values | Default: `<project_path>/spec_lock.md` anchors plus retained Design Spec/page context; Quick: anchors resolved in the current context | |
| 27 | | Aesthetic fit | Locked or Quick-resolved `visual_style` / `visual_style_behavior` | |
| 28 | | Per-page choice | Content purpose, hierarchy, legibility, semantics, and rhythm | |
| 29 | |
| 30 | **Mandatory — job-first effect selection**: establish the editable semantic |
| 31 | skeleton first, then diagnose effect jobs before treating the page as complete. |
| 32 | Plain construction remains valid only when that diagnostic finds no unresolved |
| 33 | visual job. |
| 34 | |
| 35 | | Pass | Decision | |
| 36 | |---|---| |
| 37 | | Skeleton / diagnose | Establish native information, relationships, and hierarchy. Before completion, check image/text integration, plane separation, focus, state/direction, material/style, and the recurring motif; keep plain construction when none needs treatment. | |
| 38 | | Surface / select | Name the target, confirm its owning subsection and fidelity, then use the Router. Choose a compatible technique that fully performs the job; prefer simpler/native-stable alternatives only when communication is equal. `Approximate` requires review, not automatic rejection. | |
| 39 | | Integrate / stop | Align paint, contour, light, hierarchy, and z-order; combine only techniques with different jobs. Check legibility, editability, density, fidelity, and style; simplify failures, use legal alternatives, and bake only the smallest pixel-dependent layer. Keep authoritative text/data native. | |
| 40 | |
| 41 | #### Visual Job Router |
| 42 | |
| 43 | **Reference — not a quota**: route diagnosed problems through this table. A |
| 44 | page may use no listed technique, one technique, or several techniques with |
| 45 | different jobs. |
| 46 | |
| 47 | | Diagnosed visual problem | Candidate technique | Authority / stop | |
| 48 | |---|---|---| |
| 49 | | Meaningful direction, continuous value, or center focus is missing | Linear/radial gradient or channel alpha | §6.2 / §6.3; otherwise keep solid paint | |
| 50 | | Picture/card/overlay elevation or boundary is unclear | Object or picture/carrier shadow, restrained glow, or hairline | §6.4; equal peers stay flat; one light direction | |
| 51 | | Native copy and image do not integrate | Scrim, fade, wash, vignette, off-center spotlight, or faux glass | §6.5 and the Image-Treatment Implementation Map; verify contrast; no backdrop blur | |
| 52 | | Relationship state, direction, continuity, or boundary is unclear | Draft/optional/future → dash; direction → marker; undirected → solid; continuous flow → gradient stroke; repeated boundary → frame/contour/crop edge; exact grid → multi-subpath | §6.6 / §6.3; every line needs a job | |
| 53 | | Short display text needs notation or silhouette | Removed/former → strike; eyebrow distinction → tracking; display silhouette → outline/gradient; luminous metric → glow; semantic list → native bullet | §6.7 / §6.4; no decorative body-copy treatment | |
| 54 | | Tilt, repetition, or reversible asset direction helps composition | Rotate, translate/mirror, or local `<use>` | §6.8; never mirror text, logos, or directional evidence | |
| 55 | | Resolved style needs hand, print, pixel, facets, layers, ribbon, or line-plus-area | Matching constructed recipe | §6.11; no generic decorative freeform | |
| 56 | | Meaning needs an unmatched silhouette, radial hierarchy, gauge, or custom route | Freeform, explicit arc/sector, or calculated arrowhead | §6.9 / §6.10; prefer an equal stock shape/marker | |
| 57 | | Look depends on dense texture, source blur, per-pixel composite, reflection, or skew | Native-safe alternative or prepared/baked asset | §6.12; text/data stay editable | |
| 58 | |
| 59 | #### Image-Treatment Implementation Map |
| 60 | |
| 61 | **Reference — not a constraint**: when image composition names one of these |
| 62 | modifier or prepared-asset treatments, resolve its implementation here. |
| 63 | `Effect-only` keeps a visible capability here without restoring a layout ID. |
| 64 | |
| 65 | | Image handles / treatment | Construction / boundary | |
| 66 | |---|---| |
| 67 | | `M2 · 01/03/04/08/09` · scrim, wash, fade, grid | Explicit solid/linear/radial layers over one picture; §6.2 / §6.3 / §6.5 | |
| 68 | | `M2 · 06/07` · atmospheric wash, watermark/receded field | Reduced picture alpha + optional wash; subordinate to native content; §6.2 / §6.5 | |
| 69 | | `M2 · 02/05` · vignette or spotlight | Radial layer with movable `fx/fy` or `cx/cy`; outer geometry `Approximate`; §6.3 / §6.5 | |
| 70 | | `M3 · 04` · lifted picture panel / visible overlay edge | Picture/carrier shadow, glow, or hairline; shadow one support shape for a framed/captioned panel; §6.4 | |
| 71 | | `M3 · 01/02/05` · frame, print frame, contour/cut edge | Registered native stroke/path; §6.6 | |
| 72 | | `M3 · 03; M1 · 09` · rotation, misregistration, Riso offset | Transform + explicit duplicate layers; §6.8 / §6.11 | |
| 73 | | `M1 · 03` + effect-only forms · paper cut, facets/folds, ribbon, staging | Ordered paths/facets + consistent paint/light; §6.11 / [`native-shape-authoring.md`](./native-shape-authoring.md) §7 | |
| 74 | | `M1 · 01/02/04–08` · crop, opening, subtraction, reveal | Direct clip or materialized Boolean; no `<mask>`; [`shared-standards-core.md`](./shared-standards-core.md) §1.2 / [`native-shape-authoring.md`](./native-shape-authoring.md) §6 | |
| 75 | | Effect-only · faux glass | Visible field + translucent panel + highlight; no blur or frosted-crop substitution; §6.5 | |
| 76 | | `A1 · 02–04; A3 · 02/03` · blur, duotone, blend, frost, desaturation | Prepared local bitmap/composite/derivative; registered frost is a blurred derivative; §6.12 | |
| 77 | |
| 78 | **Reference — illustrative colors**: colors below demonstrate syntax only; |
| 79 | generated pages choose paint from the Default locked or Quick-resolved identity |
| 80 | anchors, visual style, content semantics, and current composition. A contextual |
| 81 | tint, gradient stop, shadow/glow paint, or one-off display color need not |
| 82 | already be a persistent identity role; |
| 83 | promote it only when it becomes a recurring named role. Fidelity labels are defined |
| 84 | in [`shared-standards-core.md`](./shared-standards-core.md). Review an `Approximate` result in native PPTX |
| 85 | when the effect carries material meaning. |
| 86 | |
| 87 | --- |
| 88 | |
| 89 | ### 6.2 Color, Alpha, and Opacity |
| 90 | |
| 91 | Compatible paint grammar includes recognized named colors, `rgb()` / `rgba()`, |
| 92 | `hsl()` / `hsla()`, and `#RGB` / `#RGBA` / `#RRGGBB` / `#RRGGBBAA`. The |
| 93 | converter also tolerates legacy bare 3/4/6/8-digit hexadecimal tokens. |
| 94 | |
| 95 | **Default — canonical generated paint tokens (may preserve compatible |
| 96 | alternatives)**: New `svg_output/` and reusable template SVGs write solid paint |
| 97 | as uppercase six-digit `#RRGGBB`. `fill` / `stroke` may instead use lowercase |
| 98 | `none` or the exact local reference form `url(#id)`. Named colors, lowercase or |
| 99 | short/alpha HEX, functional colors, and bare legacy HEX remain supported input. |
| 100 | The quality checker prints an optional canonical rewrite as a recommendation |
| 101 | warning; it does not require modification or block export. |
| 102 | Explicit empty, malformed, or unrecognized paint values are errors in both |
| 103 | Checker and exporter preflight; neither converts unknown intent into |
| 104 | `noFill` or default black. Omitted properties still follow their own element |
| 105 | contract, such as SVG's default fill or §6.3's required gradient-stop color. |
| 106 | |
| 107 | | Intent | Canonical authoring | Native result / fidelity | |
| 108 | |---|---|---| |
| 109 | | Solid fill or text paint | `fill="#RRGGBB"` | Solid DrawingML paint; `Native-stable` | |
| 110 | | Fill/text alpha | Opaque `fill` + `fill-opacity="0..1"` | Fill/run alpha; `Native-stable` | |
| 111 | | Stroke alpha | Opaque `stroke` + `stroke-opacity="0..1"` | Line/outline alpha; `Native-stable` | |
| 112 | | Gradient-stop alpha | Opaque `stop-color` + `stop-opacity="0..1"` | Per-stop alpha; `Native-stable` | |
| 113 | | Shadow/glow alpha | Opaque `flood-color` + `flood-opacity="0..1"` | Glow is `Native-stable`; outer shadow is visually calibrated `Approximate` within §6.4 | |
| 114 | | Picture fade | `<image opacity="0..1">` | Picture `<a:alphaModFix>`; `Native-stable` | |
| 115 | | One atomic whole-object fade | Non-group element `opacity="0..1"` | Alpha compiled into its supported paint/effect channels; `Native-normalized` | |
| 116 | | Pattern alpha | Opaque pattern child paint + child fill/stroke opacity | Conditional; [`native-data-interface.md`](./native-data-interface.md) | |
| 117 | | CSS color alpha | Alpha-bearing named/functional/HEX paint | `Native-normalized`; recommendation warning only | |
| 118 | | Group fade | `<g opacity>` compatibility | `Approximate`; fidelity warning; §2.2 | |
| 119 | |
| 120 | ```text |
| 121 | effective fill alpha |
| 122 | = color alpha × ancestor group opacity × element opacity × fill-opacity |
| 123 | ``` |
| 124 | |
| 125 | **Default — opaque color authority (may preserve compatible alpha colors)**: |
| 126 | New generated SVG puts alpha on the semantic channel that owns it. Existing or |
| 127 | intentional alpha-bearing color tokens remain convertible; they normalize into |
| 128 | the matching DrawingML color/alpha channels. |
| 129 | |
| 130 | **Default — channel-specific alpha (may override for one atomic whole-object |
| 131 | fade)**: use `fill-opacity`, `stroke-opacity`, `stop-opacity`, or |
| 132 | `flood-opacity` when only that channel fades. Use element `opacity` only when |
| 133 | an image or one non-group atomic object intentionally fades all of its |
| 134 | supported paint/effect channels together. Do not use element `opacity` as an |
| 135 | alias for `rgba()` on a fill-only object. |
| 136 | |
| 137 | **Default — alpha grammar (may preserve compatible alternatives)**: write |
| 138 | `opacity`, `fill-opacity`, `stroke-opacity`, `stop-opacity`, and |
| 139 | `flood-opacity` as finite unitless numbers from `0` to `1`. The converter also |
| 140 | accepts finite numeric values that SVG/CSS clamps into that interval; |
| 141 | `stop-opacity` and `flood-opacity` additionally accept finite percentages. The |
| 142 | checker reports those supported non-default spellings as recommendation warnings. |
| 143 | Malformed or non-finite values are errors in both Checker and exporter |
| 144 | preflight; neither substitutes an opaque default for unknown intent. |
| 145 | `fill="transparent"` / `stroke="transparent"` become no fill/line; use a color |
| 146 | plus alpha when a painted transparent layer must remain represented. Prefer |
| 147 | descendant alpha over group opacity when isolated compositing matters (§2.2). |
| 148 | |
| 149 | PPTX import is a user-input boundary, not generated authoring. Tolerant mode |
| 150 | retains recognized color semantics, omits only unsupported paint properties, |
| 151 | and records the decision in `conversion-report.json`; `--strict` keeps the |
| 152 | closed parser checks. See |
| 153 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary). |
| 154 | --- |
| 155 | |
| 156 | ### 6.3 Gradients and Paint Effects |
| 157 | |
| 158 | | Concern | Contract | |
| 159 | |---|---| |
| 160 | | Definition | Direct `<linearGradient>` / `<radialGradient>` child of `<defs>` with unique `id` | |
| 161 | | Reference | Exact local `url(#id)` | |
| 162 | | Stops | ≥2 direct `<stop>` children; explicit color; finite non-decreasing offset in `0..1` or `0%..100%` (ties form hard edges); optional alpha | |
| 163 | | Coordinates | `objectBoundingBox` only. Generated values: `0..1`; omitted linear axis = `(0,0) → (1,0)`. Only import-normalized linear projections may reach `-0.105..1.105`; radial values stay in `0..1`, and their effective focus must lie inside the circle centered at `(0.5,0.5)` with radius `0.5` | |
| 164 | | Forbidden | External/quoted refs, `href` inheritance, `gradientTransform`, `spreadMethod`, CSS gradients | |
| 165 | |
| 166 | | Target | Contract and fidelity | |
| 167 | |---|---| |
| 168 | | `<rect>`, `<circle>`, `<ellipse>`, `<path>`, `<polygon>` fill/stroke | Linear `Native-normalized`; radial `Approximate` | |
| 169 | | `<line>` / `<polyline>` | Gradient stroke only; linear `Native-normalized`, radial `Approximate` | |
| 170 | | `<text>` / non-positional `<tspan>` | Gradient fill only; no gradient text outline | |
| 171 | | `<image>` | No gradient paint; use §6.5 overlays | |
| 172 | |
| 173 | Linear export preserves stops/alpha and reduces direction to an angle; |
| 174 | coincident endpoints are invalid. Radial export preserves the effective focus |
| 175 | (`fx/fy`, otherwise `cx/cy`) as a point-focused circle; its outer center and |
| 176 | radius normalize to `0.5`, so distinct outer `cx/cy` and `r` are dropped. A |
| 177 | focus outside that canonical circle is invalid because SVG renderers clamp it |
| 178 | to the circumference while DrawingML retains the rectangle coordinates; |
| 179 | reverse import centers such a source focus and records a diagnostic. |
| 180 | Gradient strokes stay editable; |
| 181 | reverse import may keep the first stop only. Stop alpha multiplies element opacity. |
| 182 | PPTX import normalizes gradients and reports degradation; |
| 183 | `--strict` keeps the closed parser contract. See |
| 184 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary). |
| 185 | Checker/exporter preflight share this validation. |
| 186 | Gradient-stop colors are contextual paint values. Keep them coherent with the |
| 187 | deck anchors and page intent; they are not required to duplicate existing |
| 188 | Default `spec_lock.colors` literals or Quick-resolved anchors. |
| 189 | |
| 190 | **Hard rule — non-degenerate gradient geometry**: an `objectBoundingBox` |
| 191 | gradient stroke requires non-zero intrinsic width and height. SVG stroke width |
| 192 | does not expand that object bounding box, so a perfectly horizontal or vertical |
| 193 | gradient ribbon disappears even when its stroke is thick. Author such a ribbon |
| 194 | as a closed shape with gradient `fill`, or use a path whose intrinsic geometry |
| 195 | has both dimensions. Checker and exporter reject the degenerate stroke form. |
| 196 | |
| 197 | ```xml |
| 198 | <defs> |
| 199 | <linearGradient id="flow" x1="0" y1="0" x2="1" y2="0"> |
| 200 | <stop offset="0%" stop-color="#2563EB"/> |
| 201 | <stop offset="100%" stop-color="#10B981" stop-opacity="0.7"/> |
| 202 | </linearGradient> |
| 203 | </defs> |
| 204 | <path d="M100 200 C260 80 420 320 620 180" fill="none" |
| 205 | stroke="url(#flow)" stroke-width="12"/> |
| 206 | ``` |
| 207 | |
| 208 | Preset patterns are a separate PPT interface in [`native-data-interface.md`](./native-data-interface.md). |
| 209 | |
| 210 | --- |
| 211 | |
| 212 | ### 6.4 Shadows, Glow, and Elevation |
| 213 | |
| 214 | Filters are native-effect metadata, not a general pixel-filter surface. |
| 215 | |
| 216 | | Concern | Contract | |
| 217 | |---|---| |
| 218 | | Definition/reference | Direct `<defs><filter id="...">` child with unique id; direct `filter="url(#id)"` attribute, never inline style | |
| 219 | | Public targets | `<rect>`, `<circle>`, `<image>`, `<path>`, `<text>`; an exact outer `<g filter>` is also registered when its sole visual child is one clipped `<image>` | |
| 220 | | Required primitive | `feDropShadow` or `feGaussianBlur` | |
| 221 | | Generated glow form | Zero-offset `feDropShadow` with flood paint, or the complete blur + flood + composite + merge graph below; never bare blur | |
| 222 | | Required parameters | Explicit `stdDeviation` on either effect primitive; explicit `dx`, `dy`, and `flood-opacity` on `feDropShadow`; explicit `flood-opacity` on `feFlood`; explicit `slope` on linear `feFuncA` | |
| 223 | | Accepted helpers | `feOffset`, `feFlood`, `feComposite`, `feMerge`, `feMergeNode`, `feComponentTransfer`, linear `feFuncA` | |
| 224 | | Alpha transfer | Linear `feFuncA` maps multiplicative `slope` only; `intercept` is unsupported | |
| 225 | | Blur sampling | `feGaussianBlur edgeMode` is unsupported; native effects do not expose the SVG edge-sampling modes | |
| 226 | | Primitive coordinates | Omit `primitiveUnits` or use `userSpaceOnUse`; `objectBoundingBox` coordinates are unsupported | |
| 227 | | Numeric values | Finite unitless values; non-negative `stdDeviation`; finite `dx` / `dy`; `feFuncA slope` within `0..1`; mapped glow `rad = stdDeviation × 9525`, shadow `blurRad = stdDeviation × 2 × 9525`, and shadow `dist = hypot(dx,dy) × 9525` must round into DrawingML `0..27273042316900` | |
| 228 | | Classification | Meaningful non-zero offset → one outer shadow; zero/no offset → one glow | |
| 229 | | Fidelity | `Approximate`; one filter becomes one DrawingML effect | |
| 230 | |
| 231 | Flood opacity, linear `feFuncA slope`, and element opacity multiply. The |
| 232 | converter-only historical path may also multiply flood-color alpha and |
| 233 | ancestor group opacity. |
| 234 | Native export does not preserve filter-region, `in/in2/result`, merge order, or |
| 235 | composite topology. Other primitives, multiple independent effects, filters on |
| 236 | `<tspan>` / ordinary `<g>` / unsupported targets are forbidden; apply the |
| 237 | effect to supported objects or use explicit layers. |
| 238 | Special `<g filter>` carriers are limited to the exact single clipped-image |
| 239 | form in §6.5, the hash-locked |
| 240 | `data-pptx-part="geometry-preview"` transport in §1.4—a direct child of an |
| 241 | imported preset object referencing the hidden geometry carrier's filter—and the |
| 242 | exact imported picture-crop carrier in §6.5, which keeps the effect outside its |
| 243 | viewport. Neither authorizes ordinary group filters or creates a second |
| 244 | PowerPoint object. |
| 245 | PPTX import maps one classifiable shape/connector/picture outer shadow or glow |
| 246 | to this contract. Unsupported effects and outer-shadow variants whose scale, |
| 247 | skew, alignment, or rotation semantics cannot be retained become import |
| 248 | diagnostics instead of a silently simplified authoring surface. See |
| 249 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary) |
| 250 | for tolerant, strict, and release-handling behavior. |
| 251 | The quality checker and exporter preflight enforce the same definition, |
| 252 | reference, primitive, target, and numeric-value contract. Missing required |
| 253 | geometry and malformed values are never replaced by effect defaults during |
| 254 | native export. |
| 255 | |
| 256 | ```xml |
| 257 | <defs> |
| 258 | <filter id="softShadow" x="-15%" y="-20%" width="130%" height="150%"> |
| 259 | <feDropShadow dx="0" dy="6" stdDeviation="8" |
| 260 | flood-color="#000000" flood-opacity="0.10"/> |
| 261 | </filter> |
| 262 | <filter id="expandedShadow" x="-15%" y="-20%" width="130%" height="150%"> |
| 263 | <feGaussianBlur in="SourceAlpha" stdDeviation="8" result="b"/> |
| 264 | <feOffset in="b" dx="0" dy="6" result="o"/> |
| 265 | <feFlood flood-color="#000000" flood-opacity="0.10" result="c"/> |
| 266 | <feComposite in="c" in2="o" operator="in" result="s"/> |
| 267 | <feMerge><feMergeNode in="s"/><feMergeNode in="SourceGraphic"/></feMerge> |
| 268 | </filter> |
| 269 | <filter id="titleGlow" x="-30%" y="-30%" width="160%" height="160%"> |
| 270 | <feGaussianBlur in="SourceAlpha" stdDeviation="6" result="b"/> |
| 271 | <feFlood flood-color="#38BDF8" flood-opacity="0.45" result="c"/> |
| 272 | <feComposite in="c" in2="b" operator="in" result="g"/> |
| 273 | <feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge> |
| 274 | </filter> |
| 275 | </defs> |
| 276 | ``` |
| 277 | |
| 278 | Even `feDropShadow` with `dx="0" dy="0"` becomes glow. Use an existing accent |
| 279 | color; black reads as diffuse shadow. Bare `feGaussianBlur` remains compatible |
| 280 | input but is never generated: preview blurs the object while export emits glow. |
| 281 | |
| 282 | | Elevation | Use | `dy` | `stdDeviation` | Alpha | |
| 283 | |---|---|---:|---:|---:| |
| 284 | | Floor | Backgrounds, dividers, equal peers, body containers, decorative lines/icons, single-layer pages | — | — | — | |
| 285 | | Resting | Card over photo/panel, secondary callout | 2–4 | 4–8 | 0.06–0.10 | |
| 286 | | Raised | Primary CTA, focused card, overlay | 6–10 | 10–16 | 0.12–0.20 | |
| 287 | | Glow | Short display text, metric, focus accent | 0 offset | 4–8 | 0.35–0.55 | |
| 288 | |
| 289 | **Default — one light source per page (may override when every affected layer |
| 290 | uses one deliberate alternative direction)**: every `feOffset` shadow on one |
| 291 | slide shares the same `dx`/`dy` direction (default `dx="0"`, |
| 292 | `dy="4"`–`dy="8"`, light from upper front). Contradictory shadow directions |
| 293 | make one plane read as several incompatible surfaces. A deliberate upward |
| 294 | paper-layer treatment flips every affected layer together; never mix |
| 295 | directions on the same plane. |
| 296 | |
| 297 | **Reference — not a constraint**: use no more elevation categories than the |
| 298 | hierarchy needs; a page may reuse one category across several related objects. |
| 299 | Do not lift every peer card or stack strong shadow, border, gradient, and tint |
| 300 | on one container. Same-family colored shadow is reserved for a focal accent. |
| 301 | On dark backgrounds, prefer a light hairline or restrained glow; never glow body copy. |
| 302 | For older/strict renderers, replace a filter with two or three offset |
| 303 | translucent shapes behind the object: |
| 304 | alpha `0.03–0.05`, increasing offset/radius, and optional same-family tint near |
| 305 | `0.04` (`Native-stable`). |
| 306 | |
| 307 | --- |
| 308 | |
| 309 | ### 6.5 Image Treatments, Overlays, and Glass-like Surfaces |
| 310 | |
| 311 | #### Image Carrier and Crop Contracts |
| 312 | |
| 313 | | Need | Authoring contract | Fidelity | |
| 314 | |---|---|---| |
| 315 | | Cover/crop | Readable raster dimensions + aligned `slice` | Native `srcRect`; `Native-stable`; otherwise native crop cannot be guaranteed | |
| 316 | | Contain/fit | Aligned `meet` | Fitted picture frame; `Native-normalized` | |
| 317 | | Stretch | `preserveAspectRatio="none"` | Native stretched frame | |
| 318 | | Uniform fade | `<image opacity="...">` | Native picture alpha | |
| 319 | | Shaped picture | §1.2 image-only `clip-path` | Preset/custom picture geometry | |
| 320 | |
| 321 | **Hard rule — closed image aspect-ratio grammar**: on `<image>`, omit |
| 322 | `preserveAspectRatio` for the default `xMidYMid meet`, use `none` alone for |
| 323 | stretch, or use one of the nine case-sensitive alignments (`xMinYMin`, |
| 324 | `xMidYMin`, `xMaxYMin`, `xMinYMid`, `xMidYMid`, `xMaxYMid`, `xMinYMax`, |
| 325 | `xMidYMax`, `xMaxYMax`) followed by explicit `meet` or `slice`. Generated SVG |
| 326 | always includes the mode on an aligned value. An alignment without a mode and |
| 327 | values needing whitespace normalization are compatible input and receive a |
| 328 | Checker recommendation. Empty values, `defer`, unknown/wrong-case alignments or |
| 329 | modes, `none` with a mode, and extra tokens are errors; the converter never |
| 330 | guesses a fallback. |
| 331 | |
| 332 | **Hard rule — fit/clip interaction**: a non-trivial clip disables `meet` |
| 333 | frame-fit. Match the image box to the source ratio or use `slice`. Put one §6.4 |
| 334 | filter directly on an unclipped `<image>`. For a clipped picture, keep |
| 335 | `clip-path` on the `<image>` and put the filter on an exact outer `<g>` whose |
| 336 | sole visual child is that image. Never combine `filter` and `clip-path` on the |
| 337 | same `<image>`: SVG would clip the preview effect while PowerPoint would not. |
| 338 | The carrier may keep object-local id, role, transform, and |
| 339 | `data-pptx-carrier`. It may own `data-pptx-layer="master|layout"` only when |
| 340 | the carrier itself is the direct fixed atom. It must not own |
| 341 | `data-pptx-placeholder`, `data-pptx-binding`, or chart/table replacement |
| 342 | metadata; keep slot ownership on the outer placeholder boundary. |
| 343 | |
| 344 | **Hard rule — picture frames and sources are explicit and decodable**: every |
| 345 | SVG `<image>` has explicit positive `width`/`height` and exactly one non-empty |
| 346 | `href` or compatible `xlink:href`. A data URI must use a supported `image/*` |
| 347 | MIME type, valid strict base64 when marked |
| 348 | `base64`, a non-empty payload, and bytes that decode as the declared format. |
| 349 | An external asset must resolve, use a supported extension, be non-empty, and |
| 350 | decode as that extension. The registered formats are PNG, JPEG, GIF, WebP, |
| 351 | BMP, TIFF, SVG, EMF, and WMF. Explicit template substitution tokens may remain |
| 352 | unresolved only during template checking; export requires the resolved image. |
| 353 | Missing, ambiguous, corrupt, mislabeled, or unsupported sources are errors and |
| 354 | must never be dropped or packaged as invalid zero-byte media. |
| 355 | |
| 356 | **Hard rule — nested SVG is picture-crop transport, not a general viewport**: |
| 357 | every non-root `<svg>` is the exact wrapper accepted by the shared crop parser: |
| 358 | |
| 359 | | Part | Required form | |
| 360 | |---|---| |
| 361 | | Outer | Registered `x`, `y`, positive `width`/`height`; four ordinary-decimal unit coordinates in `viewBox`; `preserveAspectRatio="none"`; `overflow="hidden"` | |
| 362 | | Child | Exactly one direct empty `<image>` with one non-empty `href`/`xlink:href`, `x="0" y="0" width="1" height="1" preserveAspectRatio="none"` | |
| 363 | | Context | Only root SVG / ordinary visual `<g>` ancestors; outer may add `id`, supported `transform`, registered layer/carrier metadata, and `data-pptx-frame`, `data-pptx-object`, `data-pptx-shape-id`, `data-pptx-shape-name`, `data-pptx-shape-scope`; an exact imported picture carrier may hold its one §6.4 filter outside this viewport | |
| 364 | | Shape crop | Exact outer `data-pptx-crop="1"`; authored wrappers put the registered, locally resolving image-only clip on the inner image, using `userSpaceOnUse` geometry matching the visible `viewBox`; legacy imported outer clips remain compatible | |
| 365 | |
| 366 | The inner image may add only registered `opacity` and that clip. Quantize the |
| 367 | `viewBox` without clamping: every signed crop fits |
| 368 | `-2147483648..2147483647`, with `l + r < 100000` and `t + b < 100000`. |
| 369 | Retain negative/outside-source crops exactly; write redundant `0 0 1 1` as a |
| 370 | plain `<image>`. Extra, indirect, or character content; unknown attributes; |
| 371 | malformed or unrepresentable crops; and general nested viewports fail. Checker |
| 372 | and converter share this parser. |
| 373 | |
| 374 | #### Image Overlay and Material Techniques |
| 375 | |
| 376 | | Overlay | Construction | Typical stops / alpha | |
| 377 | |---|---|---| |
| 378 | | Directional scrim | Linear rect, darkest beside text | `0%: 0.88; 55%: 0.30; 100%: 0` | |
| 379 | | Bottom title fade | Vertical rect over lower image | black `0 → 0.72` | |
| 380 | | Vignette/spotlight | Radial rect; place the hotspot with `fx/fy` or `cx/cy` inside the canonical focus circle; outer center/radius remain approximate | black `0 → 0.58` | |
| 381 | | Brand wash | Directional existing brand-color gradient | `0.80 → 0.10` | |
| 382 | | Grid scrim | Seamless no-stroke rect cells over one image; vary neighboring alpha narrowly and irregularly | Keep the field subordinate; a regular alternation reads as a checkerboard | |
| 383 | | Faux glass | Visible fields + diagonal linear panel (`0,0 → 1,1`) + highlight stroke; optional §6.4 elevation | white `0.38 → 0.12`; stroke about `0.55` | |
| 384 | |
| 385 | Layer in document order: image → scrim/wash → text. True source/backdrop blur is |
| 386 | `Bake-required`; faux glass is explicit layering, not blur. Validate contrast |
| 387 | against the actual image. All overlay gradients follow §6.3 linear/radial |
| 388 | fidelity. |
| 389 | |
| 390 | --- |
| 391 | |
| 392 | ### 6.6 Lines, Connectors, Borders, and Markers |
| 393 | |
| 394 | | Surface | Contract / native result | |
| 395 | |---|---| |
| 396 | | Solid stroke/width/alpha | `Native-stable` editable line | |
| 397 | | `4,4`; `6,3`; `2,2`; `8,4`; `8,4,2,4` (comma or space separators) | `dash`; `dash`; `sysDot`; `lgDash`; `lgDashDot` (`Native-normalized`) | |
| 398 | | Canonical custom dash | Exactly two positive finite unitless ordinary decimals (`dash gap`); export scales/quantizes against stroke width; `Native-normalized` | |
| 399 | | Compatible custom dash | Three or more positive finite unitless values are accepted but reduce to the first pair with a Checker recommendation; compatible numeric spellings also warn | |
| 400 | | `stroke-linecap` | `butt`, `round`, `square`; `Native-stable` | |
| 401 | | `stroke-linejoin` | `miter`, `round`, `bevel`; `Native-stable` | |
| 402 | | `vector-effect` | Exactly `none` or `non-scaling-stroke`; export resolves the choice into native line width (`Native-normalized`) | |
| 403 | | `stroke-dashoffset` | No general line mapping; allowed only as a direct finite unitless ordinary-decimal attribute on a §6.10 thick-circle shorthand (`px` suffix is compatible input and warns) | |
| 404 | | Gradient stroke | §6.3; re-import may flatten to first stop | |
| 405 | | `marker-start` / `marker-end` | §1.1 native line end; type `Native-normalized`, size `Approximate` (`sm/med/lg`) | |
| 406 | |
| 407 | PPTX import treats unsupported line properties as source diagnostics: tolerant |
| 408 | mode retains the object and omits only the unsupported outline; `--strict` |
| 409 | retains the closed rejection behavior. See |
| 410 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary). |
| 411 | |
| 412 | The dash grammar is closed: exact lowercase `none`, or at least two finite |
| 413 | unitless numbers separated by whitespace or one comma. Generated SVG uses |
| 414 | ordinary decimal spellings. A leading plus sign, exponent, trailing decimal |
| 415 | point, surrounding whitespace, or longer custom list is compatible input and |
| 416 | produces a non-blocking normalization recommendation. Unknown units, one-value |
| 417 | arrays, empty or repeated comma fields, non-finite values, and negative or zero |
| 418 | entries are errors. The only zero exception is a gap declared directly on the |
| 419 | §6.10 thick-circle element. |
| 420 | |
| 421 | Generated cap, join, and `vector-effect` values use the exact lowercase tokens |
| 422 | in the table. Surrounding whitespace is compatible input and produces a |
| 423 | recommendation; every other token is an error. |
| 424 | |
| 425 | Match marker paint to the parent stroke using the shape-specific channel from |
| 426 | §1.1: fill for closed/oval line ends and stroke for the open arrow. Use markers |
| 427 | for connectors and §6.10 calculated geometry for a manual diagonal arrowhead. |
| 428 | When exact grid spacing matters, use one multi-subpath path rather than a |
| 429 | fixed-density preset pattern: |
| 430 | |
| 431 | ```xml |
| 432 | <path d="M40 0V120 M80 0V120 M0 40H120 M0 80H120" |
| 433 | fill="none" stroke="#2E6EA8" stroke-width="0.8"/> |
| 434 | ``` |
| 435 | |
| 436 | --- |
| 437 | |
| 438 | ### 6.7 Advanced Text Treatments |
| 439 | |
| 440 | **Hard rule — closed text property grammar**: generated text uses only the |
| 441 | values in the `Canonical authoring` column. Registered compatible input remains |
| 442 | convertible and receives a non-blocking normalization recommendation. Every |
| 443 | other value is invalid; the converter must not replace it with a default. |
| 444 | |
| 445 | | Property | Canonical authoring | Compatible input | DrawingML mapping / rejection boundary | |
| 446 | |---|---|---|---| |
| 447 | | `font-weight` | `normal`, `bold`, or an exact integer hundred from `100` through `900` | `medium` → `500`; `semibold` → `600` | `normal` and `100..500` map to regular; `bold` and `600..900` map to `b="1"`; therefore numeric weights are `Native-normalized` | |
| 448 | | `font-style` | `normal` or `italic` | None | `italic` maps to `i="1"`; oblique, angle, relative, and CSS-wide values are invalid | |
| 449 | | `text-anchor` | `start`, `middle`, or `end` on `<svg>`, `<g>`, or `<text>` | None | Maps to left/center/right paragraph alignment plus normalized frame position; it is invalid on `<tspan>` because run-level anchoring has no mapping | |
| 450 | | `text-decoration` | `none`, `underline`, `line-through`, or `underline line-through` | `line-through underline` → canonical order | Maps to the single underline and strike run properties; unknown, repeated, or substring-like tokens are invalid | |
| 451 | | `letter-spacing` | Finite unitless ordinary decimal SVG px | The same ordinary decimal with `px`, `pt`, or `em`; normalize to unitless px | Maps to `a:rPr@spc`; the final value must fit DrawingML `-400000..400000`, and negative tracking must leave every generated DrawingML run with a positive estimated advance and its text frame with a positive extent; keywords, percentages, exponents, leading plus signs, trailing decimal points, non-finite values, and other units are invalid | |
| 452 | |
| 453 | The registered text properties follow SVG inheritance, including declarations |
| 454 | on the root `<svg>`: inline `style` overrides the same element's direct |
| 455 | attribute, which overrides its ancestor. Relative font sizes and `em` tracking |
| 456 | resolve against the same effective inherited size in Checker and converter. |
| 457 | Every declaration is validated even when a later declaration overrides it, so |
| 458 | hidden garbage cannot bypass preflight. |
| 459 | |
| 460 | The DrawingML character-spacing range is necessary but not sufficient for |
| 461 | negative tracking. After run assembly, each output run must retain a positive |
| 462 | estimated advance using the quantized `sz` and `spc` values that will actually |
| 463 | be written; a wider sibling run or paragraph line cannot hide a run whose |
| 464 | aggregate advance would reverse or collapse, which can reorder or drop |
| 465 | characters across PowerPoint-compatible renderers. The generated text frame |
| 466 | must also retain a positive horizontal and vertical extent. Checker rejects |
| 467 | directly measurable single-line violations, and the converter revalidates |
| 468 | every generated run and text frame before writing OOXML. It must not clamp, |
| 469 | take the absolute value of, or otherwise hide a non-positive advance or extent. |
| 470 | Adjacent authored runs with identical final DrawingML run properties form one |
| 471 | output run before sizing and validation; splitting text across equivalent |
| 472 | `<tspan>` nodes is not a tracking escape hatch. Tracking and width estimates |
| 473 | count the registered project text clusters rather than raw Unicode code points: |
| 474 | combining marks, variation selectors, emoji modifiers and ZWJ sequences, |
| 475 | paired regional indicators, and same-script virama conjuncts do not receive |
| 476 | internal spacing. |
| 477 | An unchanged imported native text body reuses the geometry carrier's positive |
| 478 | shape frame and attaches the preserved `txBody` payload instead of regenerating |
| 479 | runs or a text frame from the SVG estimate. |
| 480 | |
| 481 | **Hard rule — element-specific text surface**: |
| 482 | |
| 483 | - Inheritable text declarations belong only on `<svg>`, `<g>`, `<text>`, or |
| 484 | `<tspan>`; placing them on geometry, image, definition, or reuse elements is |
| 485 | an error rather than ignored decoration. |
| 486 | - `<text>` accepts `x`, `y`, registered paint/alpha/run properties, the text |
| 487 | properties above, `font-family`, `font-size`, direct `filter`, direct |
| 488 | `transform`, `xml:space`, `id`, and project `data-*` metadata. |
| 489 | - `<tspan>` accepts `x`, `y`, `dx`, `dy`, registered paint/alpha/run |
| 490 | properties, `font-family`, `font-size`, `font-weight`, `font-style`, |
| 491 | `letter-spacing`, `text-decoration`, `xml:space`, `id`, and project `data-*` |
| 492 | metadata. It does not accept `text-anchor`, `filter`, or `transform`. |
| 493 | - `word-spacing`, `dominant-baseline`, `alignment-baseline`, `baseline-shift`, |
| 494 | font shorthand/variant/stretch/feature/variation/synthesis controls, |
| 495 | `font-kerning`/`kerning`, `font-size-adjust`, `line-height`, text alignment, |
| 496 | indent/shadow/rendering controls, white-space/word-break/hyphenation |
| 497 | controls, `writing-mode`, `vertical-align`, `direction`, `unicode-bidi`, and |
| 498 | `text-transform` have no registered native mapping and are errors as direct |
| 499 | attributes or inline style. |
| 500 | - Any other unregistered `font-*` or `text-*` property is also an error; the |
| 501 | closed grammar must not grow through an ignored CSS spelling. |
| 502 | |
| 503 | **Hard rule — project text whitespace**: |
| 504 | |
| 505 | - `xml:space` is the project's closed authoring control for significant text |
| 506 | whitespace. It is valid only as an exact direct attribute on `<text>` or |
| 507 | `<tspan>`, accepts only the case-sensitive values `default` and `preserve`, |
| 508 | inherits through the text tree, and may be reset on a child `<tspan>`. |
| 509 | - The project maps this control to the visible Chromium/SVG2 behavior used by |
| 510 | Live Preview; it does not claim the legacy SVG 1.1 newline-deletion model. |
| 511 | XML line endings and tabs become U+0020 SPACE. In `default` mode, contiguous |
| 512 | U+0020 characters collapse across inline run boundaries and leading or |
| 513 | trailing default-mode spaces in the resulting text chunk are removed. In |
| 514 | `preserve` mode, every resulting U+0020 character remains significant. |
| 515 | - Only XML whitespace is normalized. NBSP, ideographic space, and other |
| 516 | Unicode spacing characters remain literal text and must not be rewritten by |
| 517 | a generic Unicode-whitespace regular expression. |
| 518 | - Source line breaks do not create PowerPoint paragraphs. Use the registered |
| 519 | positioned-`tspan`/paragraph structure for visual lines, and preserve DOM |
| 520 | text/tail order plus original style inheritance when normalizing that |
| 521 | structure. |
| 522 | |
| 523 | These allowlists are additive to the global structural blacklist and the |
| 524 | paint, font-size, opacity, filter, and transform value contracts owned by their |
| 525 | respective sections; they do not weaken those contracts. |
| 526 | |
| 527 | | Treatment | SVG surface | Result / boundary | |
| 528 | |---|---|---| |
| 529 | | Underline / strike / both | `text-decoration="underline"`, `line-through`, or both | `Native-stable`; both emits both run properties | |
| 530 | | Mixed runs | Non-positional `<tspan>` | One `Native-normalized` editable frame; §4.2 | |
| 531 | | Font size | Generated default is a finite unitless SVG px value; compatible `px`, `pt`, `pc`/`pica`, `in`, `cm`, `mm`, `q`, `em`, and `rem` values receive a recommendation warning only | Converted to SVG px, then editable DrawingML point size; unsupported units/percentages error | |
| 532 | | Tracking | §6.7 closed `letter-spacing` grammar | `Native-normalized`; compatible units normalize to SVG px before DrawingML conversion | |
| 533 | | Transparency | `opacity` / `fill-opacity` on text/run | `Native-normalized` run alpha, not isolated compositing | |
| 534 | | Gradient fill | §6.3 gradient on text/run | Editable fill; geometry normalizes | |
| 535 | | Outline | Solid `stroke`, `stroke-width`, `stroke-opacity` | `Native-normalized` editable run outline; re-import does not reconstruct it | |
| 536 | | Shadow/glow | §6.4 filter on `<text>` only | Shape shadow / run glow; `Approximate` | |
| 537 | | Native bullet | Leading `· • ● ▪ ■ ◆ ◇ ◦ ‣` + non-empty content | `·`/`•` → `•`; others unchanged; color/alpha from marker run; font/size follow text | |
| 538 | |
| 539 | ```xml |
| 540 | <text x="100" y="200" font-size="20" xml:space="preserve">Current <tspan |
| 541 | fill="#999999" text-decoration="line-through">old</tspan> value</text> |
| 542 | ``` |
| 543 | |
| 544 | Use strikethrough for removed/former values; it is ordinary notation, not a |
| 545 | style-exclusive effect. Imported double underline/strike normalizes to single. |
| 546 | Bullet detection allows optional leading whitespace, requires non-empty content, |
| 547 | and leaves non-leading decorative glyphs as ordinary text. |
| 548 | Keep body tracking normal; CJK tracking defaults near/below 2% of font size and |
| 549 | above 5% triggers review. Text outline is solid only. `textPath`, masks, blend |
| 550 | modes, generated effects, and text-image knockouts are outside editable text. |
| 551 | |
| 552 | --- |
| 553 | |
| 554 | ### 6.8 Transforms, Layering, and Static Reuse |
| 555 | |
| 556 | | Surface | Contract / fidelity | |
| 557 | |---|---| |
| 558 | | `rotate(angle[, cx, cy])` | Geometry/image/text/ordinary group; `Native-normalized` | |
| 559 | | `translate(x y)` | Geometry/image/group; pure translation also safe on text; `Native-normalized` | |
| 560 | | Positive scale / negative mirror | Geometry/image or a group/use whose expanded visual subtree is geometry/image only; explicit pivot; `Native-normalized` | |
| 561 | | `matrix(a b c d e f)` | Geometry/image or the same geometry/image-only group/use; transformed axes finite, non-zero, orthogonal; excludes rounded rectangles and subtrees containing them; `Native-normalized` | |
| 562 | | Source order | Back-to-front PPT z-order; `Native-stable` | |
| 563 | | `<g opacity>` | Compatible approximate mapping; generated SVG prefers descendant alpha, §2.2 | |
| 564 | | Local `<use>` | §1.3 compile-time reuse; `Native-normalized` | |
| 565 | |
| 566 | **Hard rule — closed transform grammar**: Use only lowercase `translate`, |
| 567 | `scale`, `rotate`, and `matrix` with exact finite unitless argument counts: |
| 568 | `translate` 1/2, `scale` 1/2, `rotate` 1/3, and `matrix` 6. Separate arguments |
| 569 | and operations with whitespace or one comma. Leading/trailing/repeated commas, |
| 570 | adjacent operations without a separator, units, unknown functions, and |
| 571 | incomplete input fail quality check and export. Generated numeric tokens use |
| 572 | ordinary decimals; a supported leading `+`, exponent, or trailing decimal point |
| 573 | remains compatible input and receives a non-blocking normalization warning. |
| 574 | Model-facing translation values, rotation centers, and matrix `e/f` use at |
| 575 | most two decimals under §1.4; angles, scale arguments, and matrix `a/b/c/d` |
| 576 | retain the precision required by the transform. |
| 577 | |
| 578 | Set text size/position directly. A text transform is either a translate-only |
| 579 | list or one rotate operation; do not scale, matrix-transform, or mix operations |
| 580 | on text. A group containing text follows the same translate-only/single-rotate |
| 581 | limit. `skewX`, `skewY`, zero/non-orthogonal axes, and shear matrices are |
| 582 | forbidden. Native chart/table markers allow translate/scale only. The §6.10 |
| 583 | thick-circle shortcut does not inherit general transform support. Positive |
| 584 | rotation is clockwise and pivoted rotation normalizes the native frame. Every |
| 585 | cumulative matrix, including transforms split across ancestors, must remain |
| 586 | finite, non-zero, and orthogonal; importer/live-editor matrices do not expand |
| 587 | the hand-authored contract. |
| 588 | Mirror around vertical pivot `cx` with |
| 589 | `translate(cx 0) scale(-1 1) translate(-cx 0)`; use the analogous Y sequence |
| 590 | for a horizontal pivot. During mirror materialization, imported PowerPoint |
| 591 | groups with an axis flip keep their geometry reflection, while each descendant |
| 592 | SVG text node receives the matching counter-reflection so browser previews keep |
| 593 | glyphs upright. The tool-side native record retains the source group flip. |
| 594 | |
| 595 | Layer back-to-front: background/image → scrim/shadow → main geometry → labels / |
| 596 | icons → top annotation. Finalization and native export independently expand |
| 597 | `<use>` into cloned editable primitives; PowerPoint does not retain a symbol / |
| 598 | instance graph. |
| 599 | |
| 600 | --- |
| 601 | |
| 602 | ### 6.9 Freeform Shapes and Curves |
| 603 | |
| 604 | | Input | Native normalization | Fidelity | |
| 605 | |---|---|---| |
| 606 | | `M/L/H/V`, absolute or relative | Absolute `M/L` | `Native-normalized` | |
| 607 | | `C` | Cubic Bézier | `Native-normalized` | |
| 608 | | `S/Q/T` | Explicit cubic controls | `Native-normalized` | |
| 609 | | `A` | Cubic segments of at most 90° | `Approximate` | |
| 610 | | `Z`; polygon/polyline | Closed/open freeform | `Native-normalized` | |
| 611 | |
| 612 | **Hard rule — complete freeform grammar**: Generated `path@d` and |
| 613 | `polygon` / `polyline@points` use finite unitless ordinary decimals and only |
| 614 | the commands registered above. Native export consumes the complete attribute; |
| 615 | it never extracts recognizable fragments while ignoring other characters. |
| 616 | Finite scientific notation, a leading plus sign, and a trailing decimal point |
| 617 | remain read-compatible and receive recommendation warnings; generated SVG does |
| 618 | not write them. Unknown commands or characters, misplaced/repeated commas, |
| 619 | non-finite numbers, missing attributes, incomplete command groups, and odd |
| 620 | point counts are invalid. A path starts with `M` / `m`; `A` radii are |
| 621 | non-negative and both arc flags are exactly `0` or `1`. Each registered path |
| 622 | command accepts its uppercase absolute and lowercase relative form. Legal |
| 623 | separator-free arc flag sequences remain valid and are parsed as individual |
| 624 | flag tokens. A polygon has at least three coordinate pairs and a polyline at |
| 625 | least two. |
| 626 | |
| 627 | **Validation**: Checker and native export consume the same parser in |
| 628 | [`paths.py`](../scripts/svg_to_pptx/drawingml/paths.py); native-object fallback |
| 629 | bounds reuse its normalized commands rather than a second path grammar. |
| 630 | |
| 631 | Command identity, relative coordinates, shorthand, arc parameters, and original |
| 632 | handles are not retained. Geometry needs non-zero bounds. Before authoring a |
| 633 | freeform, apply [`native-shape-authoring.md`](./native-shape-authoring.md): |
| 634 | prefer an editable basic primitive, one exact Office preset, or a Boolean |
| 635 | materialization. Use a closed cubic path only for an organic silhouette those |
| 636 | cannot express, polygon/closed path for unmatched ribbons/facets, and an open |
| 637 | path only for a required data curve, custom route, or locked or Quick-resolved |
| 638 | hand-drawn / organic style. Straight relationships use `<line>`; exact stock bends/curves |
| 639 | use an authored native Connector preset. Multi-`M` paths remain available for |
| 640 | exact linework, and a [`shared-standards-core.md`](./shared-standards-core.md) |
| 641 | §1.2 path clip for unmatched organic pictures. Filled silhouettes end with |
| 642 | `Z`; open paths use `fill="none"`. Do not depend on |
| 643 | `fill-rule="evenodd"`; build explicit visible geometry or bake an essential |
| 644 | knockout. |
| 645 | For a fixed background, a background-colored overlay is also valid. |
| 646 | |
| 647 | | Rounded rect input | Result | |
| 648 | |---|---| |
| 649 | | One positive radius, or `0 < rx == ry <= min(width,height)/2` | `Native-stable` adjustable `roundRect` without distorting transforms; the same short-side limit applies to one-radius input | |
| 650 | | `0 < abs(rx-ry) < 0.5px` after scaling | One normalized native radius; `Approximate` | |
| 651 | | `abs(rx-ry) >= 0.5px`, either positive | Cubic custom geometry; no radius handle; `Approximate` | |
| 652 | | Equal radius above half the short side | Native short-side clamp may differ from SVG; `Approximate` | |
| 653 | |
| 654 | --- |
| 655 | |
| 656 | ### 6.10 Radial Geometry, Donuts, Gauges, Sunbursts, and Diagonal Arrowheads |
| 657 | |
| 658 | For center `(cx,cy)`, radius `r`, and degrees `θ`: |
| 659 | |
| 660 | ```text |
| 661 | x = cx + r × cos(θ × π / 180) |
| 662 | y = cy + r × sin(θ × π / 180) |
| 663 | ``` |
| 664 | |
| 665 | For clockwise pie/donut sectors, default to `-90°` only when the chart starts at |
| 666 | 12 o'clock. A full-circle percentage sector spans `percentage × 360°`; |
| 667 | large-arc is `1` above `180°`; outer sweep is `1`, inner return is `0`. Split |
| 668 | both outer and inner boundaries of a full ring into at least two arcs each. |
| 669 | Calculated endpoints survive subject to EMU rounding; `A` curves remain cubic |
| 670 | approximations. Verify all spans plus gaps against the planned sweep. |
| 671 | Explicit arc sectors are editable `Approximate` freeforms. Thin circles using a |
| 672 | §6.6 preset/two-number dash stay `Native-normalized` ellipse lines. |
| 673 | |
| 674 | ```xml |
| 675 | <!-- 75% donut: center 400,400; outer 180; inner 100; -90° → 180°. --> |
| 676 | <path d="M400 220 A180 180 0 1 1 220 400 |
| 677 | L300 400 A100 100 0 1 0 400 300 Z" fill="#2563EB"/> |
| 678 | ``` |
| 679 | |
| 680 | **Gauge**: require `max > min`, `p = clamp((value-min)/(max-min),0,1)`, and |
| 681 | `0 < planned clockwise sweep <= 360°`; value sweep is `p × planned sweep`. |
| 682 | `valueEndAngle = startAngle + valueSweep`; large-arc is `1` iff |
| 683 | `abs(valueSweep) > 180°`. |
| 684 | Omit the value sector at `p=0`. At `p=1` with `360°`, split both boundaries into |
| 685 | at least two arcs. Track/value share center, radii, start, and sweep flags. |
| 686 | |
| 687 | **Sunburst — `Approximate`**: one explicit annular sector per node; each depth owns one radius |
| 688 | band and child angular intervals partition the parent. Do not use one `evenodd` |
| 689 | compound ring. |
| 690 | |
| 691 | **Thick-circle shorthand — `Approximate`, non-position-sensitive only**: |
| 692 | |
| 693 | - One circle per segment; `fill="none"`; the circle may use one `rotate` for its |
| 694 | start angle, and ancestor transforms must be translate-only. |
| 695 | - Exactly two non-preset finite unitless ordinary-decimal values (`dash gap`); |
| 696 | `stroke-dashoffset` is a direct finite unitless ordinary-decimal attribute. |
| 697 | - `0 < stroke-width < 2r`, `stroke-width/r >= 0.15`, |
| 698 | `0 < dash < 2πr`, `gap >= 0`, and `dash + gap >= 2πr - 1` SVG unit. The |
| 699 | one-unit tolerance exists only for integer-rounded circumference values. |
| 700 | - Native construction uses only the first dash and re-imports as a freeform. |
| 701 | Its native start is 90° counterclockwise from the SVG preview; use explicit |
| 702 | arcs whenever start angle, cap, or radial precision matters. |
| 703 | |
| 704 | ```xml |
| 705 | <circle cx="400" cy="400" r="140" fill="none" stroke="#2563EB" |
| 706 | stroke-width="48" stroke-dasharray="615.75 263.90" stroke-dashoffset="0"/> |
| 707 | ``` |
| 708 | |
| 709 | **Diagonal polygon arrowhead**: for a non-zero line, calculate rather than use a |
| 710 | fixed triangle: |
| 711 | |
| 712 | ```text |
| 713 | dx=x2-x1; dy=y2-y1; len=√(dx²+dy²); ux=dx/len; uy=dy/len |
| 714 | px=-uy; py=ux |
| 715 | tip=(x2,y2) |
| 716 | back1=(x2-ux×12+px×5, y2-uy×12+py×5) |
| 717 | back2=(x2-ux×12-px×5, y2-uy×12-py×5) |
| 718 | ``` |
| 719 | |
| 720 | Use §1.1 markers for ordinary connectors; the polygon is for a manually drawn |
| 721 | filled `Native-normalized` arrowhead. Example: |
| 722 | `<polygon points="370,430 365.6,417.8 358.2,424.6"/>`. |
| 723 | |
| 724 | --- |
| 725 | |
| 726 | ### 6.11 Constructed Technique Recipes |
| 727 | |
| 728 | **Hard rule — explicit construction**: these are supported-layer recipes, not |
| 729 | browser-filter permissions. |
| 730 | |
| 731 | **Reference — not a constraint**: use them only when they match the locked or |
| 732 | Quick-resolved style. Their curve recipes are explicit exceptions to the |
| 733 | Shape-first default above; they do not authorize decorative freeforms in |
| 734 | another style. |
| 735 | |
| 736 | | Family | Technique | Use when | Construction / boundary | |
| 737 | |---|---|---|---| |
| 738 | | Material / depth | Faux glass | Visible field must remain present behind a panel | §6.5 translucent panel + highlight; no backdrop blur; `Native-normalized` | |
| 739 | | Material / depth | Paper cut | Ordered layers/openings carry the material language | Organic paths + one §6.4 shadow per layer, never the group; `Approximate` | |
| 740 | | Hand / print | Hand-drawn mark | Annotation, underline, or highlighter gesture | Rotated translucent bar + restrained `Q/C` paths + round caps; no roughness filter; `Native-normalized` | |
| 741 | | Hand / print | Ink wash | Brush mass or atmosphere | Same-family translucent curves/strokes; no feather/wet edge; `Native-normalized` | |
| 742 | | Hand / print | Riso offset | Deliberate print misregistration | Offset duplicate, second ink, lower alpha; no blend mode; `Native-normalized` | |
| 743 | | Hand / print | Pixel grid | Sparse hard-cell digital accent | Integer-aligned rect grid; `shape-rendering` preview-only; `Native-stable` | |
| 744 | | Hand / print | Halftone | Sparse screen modulation | Calculated circles; `Native-stable`; bake dense screens or use [`native-data-interface.md`](./native-data-interface.md) | |
| 745 | | Form / geometry | Faceted or folded form | Isometric object, folded ribbon, dimensional numeral/band | Shared vertices, one light direction, same-hue alternating paint per [`native-shape-authoring.md`](./native-shape-authoring.md) §7.1; no 3D; `Native-normalized` | |
| 746 | | Form / geometry | Gradient ribbon | Continuous directional energy, not faceted depth | Cubic gradient stroke or closed gradient-filled band; no mesh gradient; `Native-normalized`, re-import may flatten color | |
| 747 | | Data expression | Line plus area | Magnitude context beneath an exact reading edge | Subordinate low-alpha area first, crisp line above; `Native-normalized` | |
| 748 | |
| 749 | **Minimal construction anchors**: |
| 750 | |
| 751 | ```xml |
| 752 | <!-- Hand-drawn + ink. --> |
| 753 | <rect x="80" y="80" width="240" height="28" fill="#FDE68A" |
| 754 | opacity="0.72" transform="rotate(-1,200,94)"/> |
| 755 | <path d="M90 150 Q210 142 330 151" fill="none" stroke="#1F2937" |
| 756 | stroke-width="3" stroke-linecap="round"/> |
| 757 | <path d="M80 220 C160 160 250 180 330 230 Z" fill="#1F2937" opacity="0.16"/> |
| 758 | <path d="M90 240 C180 210 250 260 340 220" fill="none" stroke="#1F2937" |
| 759 | stroke-width="10" stroke-linecap="round" opacity="0.70"/> |
| 760 | |
| 761 | <!-- Riso, pixel cells, sparse dots. --> |
| 762 | <text x="86" y="320" font-family="Arial, sans-serif" font-size="64" |
| 763 | fill="#EC4899" opacity="0.85">PRINT</text> |
| 764 | <text x="92" y="326" font-family="Arial, sans-serif" font-size="64" |
| 765 | fill="#2563EB">PRINT</text> |
| 766 | <g id="pixel-cells" shape-rendering="crispEdges" fill="#2563EB"> |
| 767 | <rect x="400" y="80" width="16" height="16"/><rect x="416" y="80" width="16" height="16"/> |
| 768 | </g> |
| 769 | <g id="sparse-dots" fill="#EC4899"><circle cx="410" cy="140" r="3"/><circle cx="426" cy="140" r="6"/></g> |
| 770 | |
| 771 | <!-- Isometric facets + line-over-area. --> |
| 772 | <g id="isometric-facets" transform="translate(520 160)"> |
| 773 | <polygon points="0,0 80,-24 160,0 80,24" fill="#60A5FA"/> |
| 774 | <polygon points="0,0 0,48 80,72 80,24" fill="#3B82F6"/> |
| 775 | <polygon points="80,24 80,72 160,48 160,0" fill="#2563EB"/> |
| 776 | </g> |
| 777 | <path d="M760 260 L860 220 L960 250 L960 340 L760 340 Z" fill="#2563EB" opacity="0.10"/> |
| 778 | <path d="M760 260 L860 220 L960 250" fill="none" stroke="#2563EB" stroke-width="4"/> |
| 779 | ``` |
| 780 | |
| 781 | **Default — integer pixel grid (may override for deliberate irregular |
| 782 | treatment)**: avoid soft scaling; use explicit dots only for sparse editable |
| 783 | halftone and route dense full-slide texture to §6.12. |
| 784 | |
| 785 | --- |
| 786 | |
| 787 | ### 6.12 Unsupported Effects and Native-Safe Alternatives |
| 788 | |
| 789 | | Unsupported intent | Do not author | Fidelity | Alternative | |
| 790 | |---|---|---|---| |
| 791 | | Source/backdrop blur; procedural texture | Plain blur, `feTurbulence`, `feDisplacementMap`, `feColorMatrix`, arbitrary filter graph | `Bake-required` | §6.4 effect, explicit geometry, translucent layers, or baked texture | |
| 792 | | Inner shadow, soft edge, reflection | Non-outer-shadow/glow graph | `Bake-required` | Explicit inset/highlight/shadow layers or image | |
| 793 | | Per-pixel compositing | Mask, blend mode, knockout, arbitrary alpha composite | `Bake-required` | Direct geometry; §1.2 image clip; otherwise bake | |
| 794 | | Exact custom tile | Unannotated `<pattern>` / `patternTransform` | `Bake-required` | Multi-subpath geometry, suitable [`native-data-interface.md`](./native-data-interface.md) preset, or bake | |
| 795 | | Sheared object | Skew/shear matrix | `Bake-required` | Pre-transform geometry path; bake text/image | |
| 796 | |
| 797 | **Hard rule — blur semantics**: within §6.4, zero-offset `feGaussianBlur` means |
| 798 | glow; it does not blur the object or backdrop. Use a low-alpha raster for dense |
| 799 | grain and explicit circles/paths only for sparse editable marks. |
| 800 | |
| 801 | Unsupported source effects remain visible where possible and retain their |
| 802 | import diagnostics. Resolve those diagnostics before release export; see |
| 803 | [`conversion.md`](../scripts/docs/conversion.md#import-compatibility-and-recovery-boundary). |
| 804 | |
| 805 | --- |
| 806 | |
| 807 | ### 6.13 Page-Level Composition Recipes |
| 808 | |
| 809 | **Reference — not a quota**: use the planned page skeleton; when images are |
| 810 | active, select it through |
| 811 | [`image-layout-patterns.md`](./image-layout-patterns.md). Read each recipe |
| 812 | back-to-front and omit every layer without a distinct job. |
| 813 | |
| 814 | | Page / deck job | Back-to-front stack | Stop | |
| 815 | |---|---|---| |
| 816 | | Cover | Hero field → optional scrim/wash → purposeful opening/contour → native title | Stop when copy is safe and title/field read together | |
| 817 | | Divider | Image band or quiet field → restrained wash → recurring geometry → number/title | Reuse deck language; add no effect family | |
| 818 | | Text-led explanation | Quiet field → recurring material/contour → native hierarchy → optional local emphasis | Emphasis clarifies the argument, never decorates body copy | |
| 819 | | Process / system | Context field → native relation lines → nodes/labels → optional state/direction focus | Every connector stays semantic; atmosphere must not obscure flow | |
| 820 | | Evidence / metric | Context field → local contrast → native leaders/labels/metric → optional focus/elevation | Claims stay native; atmosphere must not weaken evidence | |
| 821 | | Comparison | Matched planes → optional shared wash/divider → matched labels → one difference marker | Keep crop, elevation, and paint symmetric unless asymmetry is the claim | |
| 822 | | Closing / CTA | Receded field → echoed contour/gradient → native action → optional raised accent | Add no effect family or competing image | |
| 823 | | Cross-page motif | Reuse contour, gradient direction, line language, texture, or light logic; vary scale, crop, or position by page job | Preserve recognition without copying the page or adding novelty effects | |
| 824 | |
| 825 | --- |
| 826 |