| 1 | > See [`shared-standards-core.md`](./shared-standards-core.md) for common technical constraints. |
| 2 | |
| 3 | # SVG Image Embedding Guide |
| 4 | |
| 5 | Technical spec and workflow for adding images to SVG files. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Image Resource List Format |
| 10 | |
| 11 | Each image carries an `Acquire Via` field plus a status annotation. This file |
| 12 | owns status names, resource lifecycle, and embedding workflow; |
| 13 | [`svg-effects.md`](./svg-effects.md) §6.5 owns native carrier, crop transport, |
| 14 | and filter/clip contracts. |
| 15 | |
| 16 | | Mode | Resource authority and preparation timing | |
| 17 | |---|---| |
| 18 | | Default Generate | `design_spec.md §VIII` plus its lock projection; when user-provided images are selected, run `analyze_images.py` after Strategist confirmation and complete the list before Executor | |
| 19 | | Quick Generate | Current main agent's active-context resource decisions; materialize explicit user paths first, resolve unspecified acquisition decisions automatically, and finish user/ai/web/slice/formula preparation before SVG authoring without confirmation or a persisted roster | |
| 20 | |
| 21 | ```markdown |
| 22 | | Filename | Dimensions | Purpose | Type | Layout pattern | Crop Policy | Acquire Via | Status | Reference | |
| 23 | |----------|------------|---------|------|----------------|-------------|-------------|--------|-----------| |
| 24 | | team.jpg | 800x600 | Team photo | Photography | `#P1-02 image left, copy right` | adaptive | web | Pending | Diverse engineering team in modern office | |
| 25 | | formula_001.png | 736x168 | Page 3 block equation | Latex Formula | formula | no-crop | formula | Rendered | `E = mc^2` | |
| 26 | ``` |
| 27 | |
| 28 | ### Image Status Enum |
| 29 | |
| 30 | | Status | Meaning | Executor Handling | |
| 31 | |--------|---------|-------------------| |
| 32 | | **Pending** | Acquisition needed (`Acquire Via: ai` / `web`) or derivation needed (`Acquire Via: slice`); not yet attempted | Image Acquisition Phase (Step 5) consumes this; must not remain after Step 5 | |
| 33 | | **Failed** | The latest automatic acquisition attempt failed; this is retryable and non-terminal | Step 5 reruns the owning manifest or explicitly resolves the row to `Needs-Manual`; Executor must never treat `Failed` as usable content | |
| 34 | | **Generated** | AI-generated file exists at expected path, or sliced element file exists at expected path | Reference from `../images/`; no on-slide credit needed. **Exception**: an `Illustration Sheet` row is only a slice source — it lives in §VIII but never in `spec_lock.md images`, so the Executor never places it | |
| 35 | | **Sourced** | Web-sourced file exists at expected path | Reference from `../images/`; check `image_sources.json` for `license_tier` — if `attribution-required`, render an inline credit element on the slide (see [`executor-web-image.md`](./executor-web-image.md) §1 and [`image-searcher.md`](./image-searcher.md) §7 for the attribution contract) | |
| 36 | | **Rendered** | Deterministic formula PNG exists at expected path (`Acquire Via: formula`) | Reference from `../images/`; use a legal anchor with `meet` for the complete placement (centered default: `xMidYMid meet`) and do not crop | |
| 37 | | **Needs-Manual** | Automatic acquisition is unavailable/exhausted or the selected path requires manual fulfillment; for `slice`, the parent sheet is unavailable | Default Generate may use a dashed placeholder until its readiness gate. Quick Generate blocks every required row still in this status, even if an unverified candidate file exists; validate a supplied replacement and reconcile it to `Generated`, `Sourced`, or `Rendered` first. For `slice`, supply the parent sheet and rerun `slice_images.py`; do not hand-place individual element files. | |
| 38 | | **Existing** | User already has image (`Acquire Via: user`) | Place in `images/`, reference with `<image>` | |
| 39 | | **Placeholder** | Intentionally not prepared yet (`Acquire Via: placeholder`) | Dashed border placeholder; replace later | |
| 40 | |
| 41 | --- |
| 42 | |
| 43 | ## Workflow |
| 44 | |
| 45 | ``` |
| 46 | 1. Resolve image needs: |
| 47 | - Default Generate → Strategist-owned resource list + lock projection |
| 48 | - Quick Generate → current main agent resolves the required resource in active context; explicit user paths/URLs/choices win, unspecified choices use automatic resolution, no interaction or persisted roster |
| 49 | 2. Prepare project-local resources before SVG authoring: |
| 50 | - user → materialize the explicit source under project/images/ → Existing |
| 51 | - formula → write formula_manifest.json and run latex_render.py → Rendered |
| 52 | - Pending / Failed + ai → Image_Generator runs image_gen.py → Generated |
| 53 | - Pending / Failed + web → Image_Searcher runs image_search.py → Sourced |
| 54 | - Pending + slice → after parent AI sheet is Generated, slice_images.py cuts element files → Generated |
| 55 | 3. SVG authoring consumes only prepared resources (Executor in Default Generate; current main agent in Quick Generate) |
| 56 | ├── Existing / Generated → <image href="../images/xxx.png" .../> |
| 57 | ├── Sourced + license_tier=no-attribution → <image href=...> only |
| 58 | ├── Sourced + license_tier=attribution-required → <image href=...> + small <text> credit element on the slide |
| 59 | ├── Sourced + license_tier=manual → <image href=...> only (user-supplied --from-url; rights/credit are user responsibility) |
| 60 | ├── Rendered formula → <image href="../images/formula_001.png" preserveAspectRatio="xMidYMid meet" .../> |
| 61 | └── Placeholder / Needs-Manual → Dashed border + description text until a supplied file is validated and status is reconciled |
| 62 | 4. Preview: python3 -m http.server -d <project_path> 8000 → /svg_output/<filename>.svg |
| 63 | 5. Export: |
| 64 | - Default Generate → follow [`generate-pptx.md`](../workflows/generate-pptx.md) Step 7 |
| 65 | - Quick Generate → after every required resource has a validated expected file/provenance and usable status, run the profile's final checker, then its `--quick-generate` export |
| 66 | ``` |
| 67 | |
| 68 | > Keep external references in `svg_output/` during generation. Default Generate uses `finalize_svg.py` to embed images into the mandatory `svg_final/` visual preview. Quick Generate omits that preview artifact. Both native PPTX exports independently read image references from `svg_output/`. |
| 69 | |
| 70 | **Hard rule — export boundary**: `svg_final/` is a self-contained SVG preview for embeddable raster/SVG assets and may be manually inserted into PowerPoint as an SVG picture. EMF/WMF assets retain the documented external-reference exception for lossless native passthrough. The only supported generated-PPTX route is `svg_output/` through the project SVG-to-DrawingML converter. PowerPoint's manual Convert-to-Shape operation is unsupported. |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## External Reference vs Base64 Embedding |
| 75 | |
| 76 | | Method | Pros | Cons | Suitable For | |
| 77 | |--------|------|------|-------------| |
| 78 | | **External reference** | Small file size, fast iteration, easy to replace | Preview requires HTTP server from project root | `svg_output/` development phase | |
| 79 | | **Base64 embedding** | Self-contained file, stable direct preview / SVG-picture insertion | Large file size | `svg_final/` preview phase | |
| 80 | |
| 81 | --- |
| 82 | |
| 83 | ## Method 1: External Reference (Recommended for Generation Phase) |
| 84 | |
| 85 | ### Syntax |
| 86 | |
| 87 | ```xml |
| 88 | <image href="../images/image.png" x="0" y="0" width="1280" height="720" |
| 89 | preserveAspectRatio="xMidYMid slice"/> |
| 90 | ``` |
| 91 | |
| 92 | ### Key Attributes |
| 93 | |
| 94 | | Attribute | Description | Example | |
| 95 | |-----------|-------------|---------| |
| 96 | | `href` | Image path (relative or absolute) | `"../images/cover.png"` | |
| 97 | | `x`, `y` | Image top-left corner position | `x="0" y="0"` | |
| 98 | | `width`, `height` | Image display dimensions | `width="1280" height="720"` | |
| 99 | | `preserveAspectRatio` | Scaling mode | `"xMidYMid slice"` | |
| 100 | |
| 101 | ### preserveAspectRatio Common Values |
| 102 | |
| 103 | | Value | Effect | |
| 104 | |-------|--------| |
| 105 | | `xMidYMid slice` | Center crop (similar to CSS `cover`) | |
| 106 | | `xMidYMid meet` | Complete display (similar to CSS `contain`) | |
| 107 | | `none` | Stretch to fill, no aspect ratio preservation | |
| 108 | |
| 109 | ### Preview Method |
| 110 | |
| 111 | Browser security blocks external images on directly opened SVGs. Serve via HTTP from the project root: |
| 112 | |
| 113 | ```bash |
| 114 | python3 -m http.server -d <project_path> 8000 |
| 115 | # Visit http://localhost:8000/svg_output/your_file.svg |
| 116 | ``` |
| 117 | |
| 118 | --- |
| 119 | |
| 120 | ## Method 2: Base64 Embedding (Recommended for Preview Phase) |
| 121 | |
| 122 | ### Syntax |
| 123 | |
| 124 | ```xml |
| 125 | <image href="data:image/png;base64,iVBORw0KGgo..." x="0" y="0" width="1280" height="720"/> |
| 126 | ``` |
| 127 | |
| 128 | ### MIME Types |
| 129 | |
| 130 | | MIME Type | File Format | |
| 131 | |-----------|-------------| |
| 132 | | `image/png` | PNG | |
| 133 | | `image/jpeg` | JPG/JPEG | |
| 134 | | `image/gif` | GIF | |
| 135 | | `image/webp` | WebP | |
| 136 | | `image/svg+xml` | SVG | |
| 137 | |
| 138 | --- |
| 139 | |
| 140 | ## Conversion Process |
| 141 | |
| 142 | Default Generate follows [`generate-pptx.md`](../workflows/generate-pptx.md) |
| 143 | Step 7; it owns the serial post-processing and export commands. Quick Generate |
| 144 | follows [`quick-generate.md`](../workflows/profiles/quick-generate.md) after its |
| 145 | required-resource gate. The native PPTX converter reads `svg_output/` and maps |
| 146 | its project-local image references directly to DrawingML in both modes. |
| 147 | |
| 148 | ### Standalone: align_embed_images.py (advanced) |
| 149 | |
| 150 | For processing specific SVGs without the full pipeline: |
| 151 | |
| 152 | ```bash |
| 153 | python3 scripts/svg_finalize/align_embed_images.py <svg_file> |
| 154 | python3 scripts/svg_finalize/align_embed_images.py --dry-run <svg_file> |
| 155 | ``` |
| 156 | |
| 157 | Use `finalize_svg.py --only align-images` for project-level batches. The old |
| 158 | `crop-images`, `fix-aspect`, and `embed-images` step names are compatibility |
| 159 | aliases only when invoked through `finalize_svg.py --only`. |
| 160 | |
| 161 | --- |
| 162 | |
| 163 | ## Best Practices |
| 164 | |
| 165 | ### Native PPTX Image Export |
| 166 | |
| 167 | **Default — preserve unmodified image bytes**: `svg_to_pptx.py` uses `--image-sizing cap`. It keeps original bytes when an image needs neither resizing nor EXIF geometry normalization, and re-encodes only images that require one of those transformations. Use the explicit compact command only when a compact export is requested. |
| 168 | |
| 169 | | Need | Command | |
| 170 | |---|---| |
| 171 | | Normal native export | `python3 scripts/svg_to_pptx.py <project_path>` | |
| 172 | | Explicit compact export | `python3 scripts/svg_to_pptx.py <project_path> --image-sizing display --image-scale 2 --image-quality 85` | |
| 173 | | Force original bytes | `python3 scripts/svg_to_pptx.py <project_path> --no-image-optimize` | |
| 174 | |
| 175 | ### File Organization |
| 176 | |
| 177 | ``` |
| 178 | project/ |
| 179 | ├── images/ # Image assets |
| 180 | ├── sources/ # Source files and their accompanying images |
| 181 | │ └── article_files/ |
| 182 | ├── svg_output/ # Raw version (external references) |
| 183 | └── svg_final/ # Derived self-contained visual preview (images embedded) |
| 184 | ``` |
| 185 | |
| 186 | ### Rounded Corner / Non-rectangular Image Cropping |
| 187 | |
| 188 | `clipPath` **on `<image>` elements** is conditionally allowed — authoritative constraints in [`shared-standards-core.md`](./shared-standards-core.md) §1.2; do not restate or relax here. |
| 189 | |
| 190 | Fallback when `clipPath` doesn't fit: bake rounded corners into the source image (PNG with alpha) before embedding. |
| 191 | |
| 192 | --- |
| 193 | |
| 194 | ## FAQ |
| 195 | |
| 196 | **Q: Can't see images when opening SVG directly?** |
| 197 | Browser security blocks cross-directory requests. Serve via HTTP from project root, or run `finalize_svg.py` first and view from `svg_final/`. |
| 198 | |
| 199 | **Q: Base64 file too large?** |
| 200 | Compress the source, use JPEG, reduce resolution to match actual display dimensions. |
| 201 | |
| 202 | **Q: How to reverse-extract a Base64 image?** |
| 203 | ```bash |
| 204 | base64 -d image.b64 > image.png |
| 205 | ``` |
| 206 |