返回 ppt-master
advanced-image-motion-smoke.md
根目录 / skills / ppt-master / scripts / docs / advanced-image-motion-smoke.md
1 # Advanced Image and Motion Maintenance Smoke
2
3 Run this manual smoke from the repository root after changing nested image
4 crop finalization, native picture export, deterministic Morph pairing, authored
5 PowerPoint presets, or their planning contracts. It builds one temporary
6 two-slide project under the gitignored `projects/_smoke_*` namespace and follows
7 the inline-smoke convention from
8 [`code-style.md`](../../../../docs/rules/code-style.md) §11; do not turn it into
9 a test file or public example deck.
10
11 The fixture deliberately closes the full planning and execution chain:
12
13 - `design_spec.md` carries `Motion suggestion`, `Native shape suggestion`, one
14 current §VIII image row, and `Crop Policy`;
15 - `spec_lock.md` projects that row with optional layout pattern `#M1-11`;
16 - both pages reuse one raster through ordinary, ellipse-preset, and custom-path
17 independent nested crops;
18 - `animations.json` pairs the main crop across adjacent Morph pages;
19 - one helper-authored `rightArrow` verifies native preset discovery and export.
20
21 ```bash
22 python3 - <<'PY'
23 import base64
24 import io
25 import json
26 import re
27 import subprocess
28 import sys
29 import tempfile
30 import zipfile
31 from pathlib import Path
32 from xml.etree import ElementTree as ET
33
34 from PIL import Image, ImageDraw
35
36 project = Path(
37 tempfile.mkdtemp(
38 prefix="_smoke_advanced_image_motion_",
39 dir="projects",
40 )
41 )
42 scripts = Path("skills/ppt-master/scripts")
43 images = project / "images"
44 svg_output = project / "svg_output"
45 images.mkdir()
46 svg_output.mkdir()
47 (project / "README.md").write_text(
48 "# Advanced image and motion maintenance smoke\n",
49 encoding="utf-8",
50 )
51
52
53 def run_tool(script, *args):
54 result = subprocess.run(
55 [sys.executable, str(scripts / script), *map(str, args)],
56 capture_output=True,
57 text=True,
58 )
59 assert result.returncode == 0, result.stderr or result.stdout
60 return result.stdout.strip()
61
62
63 scene = Image.new("RGB", (1280, 720), "#E2E8F0")
64 draw = ImageDraw.Draw(scene)
65 draw.rectangle((0, 0, 420, 720), fill="#2563EB")
66 draw.rectangle((420, 0, 860, 720), fill="#F97316")
67 draw.rectangle((860, 0, 1280, 720), fill="#0F172A")
68 draw.ellipse((460, 150, 820, 510), fill="#F8FAFC")
69 scene.save(images / "scene.png")
70
71 preset = run_tool(
72 "preset_shape_svg.py",
73 "render",
74 "rightArrow",
75 "--id",
76 "native-arrow",
77 "--frame",
78 "500",
79 "570",
80 "280",
81 "80",
82 "--fill",
83 "#2563EB",
84 "--stroke",
85 "none",
86 )
87 preset = (
88 '<g id="native-arrow-module" data-pptx-bounds="500 570 280 80">'
89 + preset
90 + "</g>"
91 )
92
93 (project / "design_spec.md").write_text(
94 """<!-- ppt-master-schema: design-spec/v1 -->
95 # Advanced Image and Motion Smoke - Design Spec
96
97 ## I. Project Information
98
99 | Item | Value |
100 | --- | --- |
101 | Project Name | advanced-image-motion-smoke |
102 | Canvas Format | PPT 16:9 |
103 | Page Count | 2 |
104
105 ## II. Canvas Specification
106
107 - ViewBox: `0 0 1280 720`
108 - Format: `ppt169`
109
110 ## III. Visual Theme
111
112 - Direction: restrained technical maintenance fixture
113 - Colors: blue, orange, slate, and white
114
115 ## IV. Typography System
116
117 - Title: Arial, sans-serif at 36px
118 - Body: Arial, sans-serif at 20px
119
120 ## V. Layout Principles
121
122 - Reuse one source image through independently editable crop objects.
123 - Keep every visible crop inside the slide canvas.
124
125 ## VI. Icon Usage Specification
126
127 - No icons.
128
129 ## VIII. Image Resource List
130
131 | Filename | Dimensions | Ratio | Purpose | Type | Layout pattern | Crop Policy | Acquire Via | Status | Reference | text_policy | page_role |
132 | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
133 | scene.png | 1280x720 | 16:9 | Morph crop continuity | Photo | #M1-11 same-source independent crops with a shaped detail | adaptive | user | Existing | Synthetic three-band scene for crop and Morph verification | none | local |
134
135 ## IX. Content Outline
136
137 ### Part 1: Crop continuity
138
139 #### Slide 01 - Overview
140
141 - **Audience move**: See separate source views as one editable image system.
142 - **Layout**: Ordinary crop, shaped crop, and one native directional preset.
143 - **Title**: Overview crop state
144 - **Core message**: One source can support independent native picture objects.
145 - **Content**: Show the wide crop and shaped detail together.
146 - **Images**: Use `scene.png` for both visible crop objects.
147 - **Native shape suggestion**: Use the PowerPoint `rightArrow` preset to indicate continuation.
148 - **Motion suggestion**: Continue the primary crop into Slide 02 as one deterministic Morph object.
149
150 #### Slide 02 - Detail
151
152 - **Audience move**: Recognize the same image after a controlled crop and position change.
153 - **Layout**: Enlarged primary crop plus a second shaped detail.
154 - **Title**: Detail crop state
155 - **Core message**: Morph identity is independent from the picture crop geometry.
156 - **Content**: Move and zoom the primary crop while preserving the shared source.
157 - **Images**: Reuse `scene.png`; do not generate or replace the source.
158 - **Motion suggestion**: Pair the primary crop with Slide 01 and use Morph by object.
159
160 ## X. Speaker Notes Requirements
161
162 - Notes are disabled for this maintenance smoke.
163 """,
164 encoding="utf-8",
165 )
166
167 (project / "spec_lock.md").write_text(
168 """<!-- ppt-master-schema: spec-lock/v1 -->
169 # Execution Lock
170
171 ## canvas
172 - viewBox: 0 0 1280 720
173 - format: ppt169
174 ## communication
175 - audience: PPT Master maintainers
176 - objective: Verify advanced image and motion contracts end to end.
177 - core_message: One raster remains editable across native crops and Morph.
178 ## mode
179 - mode: showcase
180 ## visual_style
181 - visual_style: swiss-minimal
182 ## colors
183 - bg: #FFFFFF
184 - primary: #2563EB
185 - accent: #F97316
186 - text: #0F172A
187 ## typography
188 - font_family: Arial, sans-serif
189 - title_family: Arial, sans-serif
190 - body_family: Arial, sans-serif
191 - title: 36
192 - body: 20
193 ## icons
194 - library: none
195 - inventory: none
196 ## images
197 - scene: images/scene.png | source=user | pattern=#M1-11 same-source independent crops with a shaped detail | crop=adaptive
198 ## page_rhythm
199 - P01: dense
200 - P02: dense
201 ## pptx_structure
202 - mode: flat
203 ## forbidden
204 - Unsupported SVG constructs
205 """,
206 encoding="utf-8",
207 )
208
209 slide_1 = f"""<svg xmlns="http://www.w3.org/2000/svg"
210 viewBox="0 0 1280 720" data-pptx-page-role="content">
211 <defs>
212 <clipPath id="overview-shape" clipPathUnits="userSpaceOnUse">
213 <ellipse cx="0.725" cy="0.5" rx="0.275" ry="0.5"/>
214 </clipPath>
215 </defs>
216 <rect id="background" data-pptx-role="background"
217 x="0" y="0" width="1280" height="720" fill="#FFFFFF"/>
218 <g id="hero-overview" data-pptx-bounds="80 140 448 360">
219 <svg id="overview-crop" x="80" y="140" width="448" height="360"
220 viewBox="0 0 0.7 1" preserveAspectRatio="none" overflow="hidden">
221 <image href="../images/scene.png" x="0" y="0" width="1" height="1"
222 preserveAspectRatio="none"/>
223 </svg>
224 </g>
225 <g id="shaped-overview" data-pptx-bounds="760 170 330 340">
226 <svg id="overview-shaped-crop" data-pptx-crop="1"
227 x="760" y="170" width="330" height="340"
228 viewBox="0.45 0 0.55 1" preserveAspectRatio="none" overflow="hidden">
229 <image href="../images/scene.png" x="0" y="0" width="1" height="1"
230 preserveAspectRatio="none" clip-path="url(#overview-shape)"/>
231 </svg>
232 </g>
233 {preset}
234 </svg>
235 """
236
237 slide_2 = """<svg xmlns="http://www.w3.org/2000/svg"
238 viewBox="0 0 1280 720" data-pptx-page-role="content">
239 <defs>
240 <clipPath id="detail-shape" clipPathUnits="userSpaceOnUse">
241 <path d="M 0.5 1 L 0.75 0 L 1 1 Z"/>
242 </clipPath>
243 </defs>
244 <rect id="background" data-pptx-role="background"
245 x="0" y="0" width="1280" height="720" fill="#FFFFFF"/>
246 <g id="hero-detail" data-pptx-bounds="0 0 1280 720">
247 <svg id="detail-crop" x="0" y="0" width="1280" height="720"
248 viewBox="0.15 0.1 0.36 0.36"
249 preserveAspectRatio="none" overflow="hidden">
250 <image href="../images/scene.png" x="0" y="0" width="1" height="1"
251 preserveAspectRatio="none"/>
252 </svg>
253 </g>
254 <g id="shaped-detail" data-pptx-bounds="820 160 320 360">
255 <svg id="detail-shaped-crop" data-pptx-crop="1"
256 x="820" y="160" width="320" height="360"
257 viewBox="0.5 0 0.5 1" preserveAspectRatio="none" overflow="hidden">
258 <image href="../images/scene.png" x="0" y="0" width="1" height="1"
259 preserveAspectRatio="none" clip-path="url(#detail-shape)"/>
260 </svg>
261 </g>
262 </svg>
263 """
264
265 (svg_output / "01_overview.svg").write_text(slide_1, encoding="utf-8")
266 (svg_output / "02_detail.svg").write_text(slide_2, encoding="utf-8")
267 (project / "animations.json").write_text(
268 json.dumps(
269 {
270 "version": 1,
271 "slides": {
272 "02_detail": {
273 "transition": {
274 "effect": "morph",
275 "effect_options": {"morph_by": "object"},
276 "duration": 0.8,
277 },
278 "morph": {
279 "from": "01_overview",
280 "pairs": {
281 "hero-image": {
282 "from": "hero-overview",
283 "to": "hero-detail",
284 }
285 },
286 },
287 }
288 },
289 },
290 ensure_ascii=False,
291 indent=2,
292 )
293 + "\n",
294 encoding="utf-8",
295 )
296
297 run_tool("project_manager.py", "validate", project)
298 run_tool("svg_quality_checker.py", project, "--format", "ppt169")
299 run_tool("animation_config.py", "validate", project)
300
301
302 def finalized_image_sizes():
303 sizes = []
304 for svg_path in sorted((project / "svg_final").glob("*.svg")):
305 root = ET.parse(svg_path).getroot()
306 for image in root.iter("{http://www.w3.org/2000/svg}image"):
307 href = image.get("href") or image.get(
308 "{http://www.w3.org/1999/xlink}href"
309 )
310 assert href and href.startswith("data:image/"), href
311 payload = base64.b64decode(href.split(",", 1)[1], validate=True)
312 with Image.open(io.BytesIO(payload)) as embedded:
313 sizes.append(embedded.size)
314 return sizes
315
316
317 finalized_sizes = []
318 for finalize_args in ((), ("--no-compress",)):
319 run_tool("finalize_svg.py", project, *finalize_args)
320 embedded_sizes = finalized_image_sizes()
321 assert len(embedded_sizes) == 4, embedded_sizes
322 assert all(width > 100 and height > 100 for width, height in embedded_sizes)
323 finalized_sizes.append(embedded_sizes)
324
325 pptx = project / "advanced-image-motion.pptx"
326 run_tool("svg_to_pptx.py", project, "--no-notes", "-o", pptx)
327 with zipfile.ZipFile(pptx) as archive:
328 slide_names = sorted(
329 name
330 for name in archive.namelist()
331 if re.fullmatch(r"ppt/slides/slide[12]\.xml", name)
332 )
333 slide_xml = [archive.read(name) for name in slide_names]
334 assert len(slide_xml) == 2, slide_names
335 assert all(b'!!hero-image' in xml for xml in slide_xml)
336 assert b'morph' in slide_xml[1]
337 assert sum(xml.count(b"<a:srcRect") for xml in slide_xml) >= 4
338 assert b'<a:prstGeom prst="ellipse">' in slide_xml[0]
339 assert b'<a:prstGeom prst="rightArrow">' in slide_xml[0]
340 assert b"<a:custGeom>" in slide_xml[1]
341
342 readback = project / "readback"
343 run_tool(
344 "pptx_to_svg.py",
345 pptx,
346 "-o",
347 readback,
348 "--inheritance-mode",
349 "flat",
350 "--strict",
351 )
352 readback_svg = "\n".join(
353 path.read_text(encoding="utf-8")
354 for path in sorted((readback / "svg").glob("slide_*.svg"))
355 )
356 assert readback_svg.count('data-pptx-crop="1"') >= 2
357 assert readback_svg.count('data-pptx-shape-name="!!hero-image"') == 2
358
359 print(
360 "Advanced image and motion smoke: passed "
361 f"(embedded sizes={finalized_sizes}; project={project})"
362 )
363 PY
364 ```
365
366 Every command must exit successfully. The finalized data URIs must retain
367 useful source pixels rather than collapsing to the nested child’s unit
368 coordinates; the package must contain four native picture crops, the authored
369 `rightArrow`, one Morph transition, and exactly one `!!hero-image` target on
370 each adjacent slide. The strict PPTX-to-SVG readback must restore both shaped
371 crop markers and both forced-Morph object names.
372
373 The printed project is intentionally retained for manual Microsoft PowerPoint
374 inspection. Morph must continue the primary picture without a visible
375 cross-fade, sharpness jump, or media re-decoding flash. That Office-only visual
376 check is evidence gathering; to stress differing media payloads, replace
377 `scene.png` with a `7680×4320` source and re-export. Do not trigger media
378 deduplication changes from package structure alone.
379
379 lines MARKDOWN