280 lines
14 KiB
Markdown
280 lines
14 KiB
Markdown
# PPTX Animation Core
|
|
|
|
The shared animation core owns the object-effect vocabulary, trigger
|
|
semantics, OOXML timing writer, semantic read-back, and package validation for
|
|
PowerPoint OOXML. Per-element animation remains opt-in: generated PPTX export
|
|
defaults to `none`, exactly as before this validation upgrade.
|
|
|
|
## 1. Ownership
|
|
|
|
| Concern | Owner |
|
|
|---|---|
|
|
| Effect registry, timing writer, and read-back | `scripts/pptx_animations.py` |
|
|
| Sidecar parsing and SVG target discovery | `svg_to_pptx/animation_config.py` |
|
|
| SVG group-to-shape mapping | `svg_to_pptx/drawingml/converter.py` |
|
|
| Generated PPTX resolution and validation | `svg_to_pptx/pptx_package/builder.py` |
|
|
| Narration timing merge | `svg_to_pptx/pptx_package/narration.py` |
|
|
| Public authoring contract | `references/animations.md` |
|
|
| Customization stage | `workflows/stages/customize-animations.md` |
|
|
|
|
**Hard rule**: only the generated SVG-to-PPTX route writes object
|
|
animations. Direct-PPTX routes preserve source animations and run structural
|
|
package validation; they do not resolve or author animation effects.
|
|
|
|
---
|
|
|
|
## 2. Domain Model
|
|
|
|
`groups.<id>` accepts either one backward-compatible effect object or one
|
|
non-empty `effects[]` array; the forms are exclusive and every array row names
|
|
`effect`. Both expand into the same row model, so repeated shape targets are
|
|
valid. Legacy rows also accept `trigger`; omitted row settings inherit the
|
|
resolved slide animation.
|
|
|
|
One resolved row contains these fields:
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| Target | Positive PowerPoint shape id written to `p:spTgt@spid` |
|
|
| Effect | One canonical PowerPoint-authored preset class / id / subtype / behavior-tree signature |
|
|
| Trigger | Row-specific `on-click`, `with-previous`, or `after-previous`; omitted values inherit the resolved slide Start mode |
|
|
| Trigger shape | Optional different top-level group; maps to PowerPoint `On Click of` |
|
|
| Duration | Finite positive schedule duration; scalable native behavior trees preserve their internal timing ratios |
|
|
| Delay | Finite non-negative row offset; shape-trigger rows use it as `TriggerDelayTime` |
|
|
| Order | Positive integer sidecar order; ties retain stable SVG group order, then `effects[]` index |
|
|
| Effect options | Effect-specific `direction`, `amount`, `color`, `font_name` (one installed PowerPoint face, required for Change Font; not a CSS list), `relative`, or `size` values from PowerPoint `EffectParameters` |
|
|
| Timing options | Repeat count/span, auto-reverse, rewind, accelerate/decelerate, bounce-end ratio, and restart policy |
|
|
| Completion / cue | Optional dim/hide behavior and packaged `.m4a`/`.mp3`/`.wav` sound |
|
|
|
|
Modes resolve before XML writing:
|
|
|
|
| Mode | Resolution |
|
|
|---|---|
|
|
| `auto` | Generic entrance only: deterministic semantic mapping from the SVG group id |
|
|
| `mixed` | Generic entrance only: deterministic cycle over canonical PowerPoint entrance presets |
|
|
| `random` | Generic entrance only: stable seeded choice from the same canonical entrance pool |
|
|
| `none` | No object-animation sequence |
|
|
|
|
The same effective input produces the same `random` choices. When enabled,
|
|
`--conversion-trace` records each resolved row and effect, so a generated deck
|
|
can be audited without replaying the resolver.
|
|
|
|
`animation_config.py scaffold` is neutral: object defaults are `none`, and
|
|
empty `{}` group placeholders inherit no motion until populated.
|
|
|
|
Bundled sound discovery/materialization is a workflow concern, not part of the
|
|
animation core. After the SVG and object-motion solution are complete,
|
|
`sound_sync.py` may copy only selected namespaced ids into
|
|
`<project>/sounds/`; new sidecars then reference those project-relative WAV
|
|
paths. Existing low-level project-relative/absolute `.m4a`, `.mp3`, and `.wav`
|
|
inputs remain compatible. The core never resolves a library id or reads
|
|
`templates/sounds/` directly; see [`animations.md`](../../references/animations.md)
|
|
§2.2.
|
|
|
|
---
|
|
|
|
## 3. Canonical Registry and Compatibility Inputs
|
|
|
|
The canonical registry contains 203 PowerPoint-authored presets:
|
|
|
|
| Category | Key prefix | Count | Example |
|
|
|---|---|---:|---|
|
|
| Entrance | `entrance_*` | 53 | `entrance_bounce` |
|
|
| Emphasis | `emphasis_*` | 33 | `emphasis_spin` |
|
|
| Motion path | `path_*` | 64 | `path_circle` |
|
|
| Exit | `exit_*` | 53 | `exit_faded_zoom` |
|
|
|
|
The 29 established short names remain valid only as compatibility inputs.
|
|
Normalization resolves them to canonical PowerPoint-authored presets before
|
|
selection, XML writing, read-back, tracing, or validation.
|
|
|
|
| Compatibility input | Canonical preset |
|
|
|---|---|
|
|
| `appear`, `cut` | `entrance_appear` |
|
|
| `fade` | `entrance_fade` |
|
|
| `fly`, `fly_left`, `fly_right`, `fly_top` | `entrance_fly` |
|
|
| `zoom` | `entrance_zoom` |
|
|
| `wipe`, `wipe_left`, `wipe_right`, `wipe_up`, `wipe_down` | `entrance_wipe` |
|
|
| `split`, `blinds`, `checkerboard`, `dissolve`, `random_bars`, `peek` | matching `entrance_*` preset |
|
|
| `wheel`, `box`, `circle`, `diamond`, `plus`, `strips`, `wedge`, `stretch`, `expand`, `swivel` | matching `entrance_*` preset |
|
|
|
|
`cut` maps to `entrance_appear` because current PowerPoint exposes no separate
|
|
Cut object-animation preset. Old Fly/Wipe names desugar to the canonical effect
|
|
plus `effect_options.direction`; legacy `wheel` desugars to
|
|
`entrance_wheel` plus `amount: 4`. New output never writes those aliases.
|
|
|
|
Together with the 29 accepted compatibility names, the public input surface
|
|
contains 232 keys. New selections, generated sidecars, conversion traces,
|
|
writers, and documentation examples use canonical keys; short names exist only
|
|
at compatibility input boundaries.
|
|
|
|
The shipped `pptx_animation_presets.json` contains the PowerPoint-authored
|
|
`p:cTn` row for every native effect. Complex effects use combinations of
|
|
`p:set`, `p:anim`, `p:animClr`, `p:animEffect`, `p:animMotion`, `p:animRot`,
|
|
and `p:animScale`; reducing them to one filter would silently change the
|
|
effect. `pptx_animations.py --list` prints the full categorized public
|
|
registry; `pptx_animations.py --describe <effect>` prints that effect's exact
|
|
option values and shared timing/completion contract.
|
|
|
|
Native presets map to the object-capable `MsoAnimEffect` values. Media play,
|
|
pause, stop, and play-from-bookmark are excluded because they require a
|
|
media/bookmark target rather than an SVG-derived shape. Exit effects use the
|
|
same entrance-capable `MsoAnimEffect` identity with PowerPoint's exit flag and
|
|
serialize as `presetClass="exit"`.
|
|
|
|
Paragraph/text-range build controls are likewise outside this writer: generated
|
|
targets are top-level SVG groups, not paragraph ranges. For that target model,
|
|
the public contract covers all PowerPoint effect parameters, timing modifiers,
|
|
completion controls, sound, and object-trigger linkage; Speed and smooth
|
|
start/end remain derived rather than duplicated.
|
|
|
|
**Hard rule — no downgrade**:
|
|
|
|
- Keep all 29 established short names accepted as compatibility inputs.
|
|
- Reject an unknown effect, mode, or trigger; never substitute another value.
|
|
- Reject booleans and non-finite, out-of-range, or invalidly ordered values.
|
|
- Reject a missing slide, missing group, or structural-layer target.
|
|
- Keep the generated-route default at `none`; validation does not opt a deck in.
|
|
|
|
---
|
|
|
|
## 4. Target Resolution
|
|
|
|
Generated object animation targets top-level SVG content groups. Explicit SVG
|
|
semantics are authoritative; the group-id chrome heuristic is only a fallback
|
|
for marker-free legacy SVGs.
|
|
|
|
| Target state | Behavior |
|
|
|---|---|
|
|
| Ordinary content group | Animatable; a legacy block resolves one row and `effects[]` may resolve several rows against the same final shape |
|
|
| Legacy chrome-like id | Skipped unless explicitly named in `animations.json` |
|
|
| Explicit sidecar group override | May override only the legacy chrome-name heuristic |
|
|
| `data-pptx-layer` or explicit static role/placeholder | Structural and never animatable |
|
|
|
|
An explicit sidecar entry cannot turn a Master/Layout/Slide structural layer or
|
|
an explicitly marked static page-frame role/placeholder into an animation
|
|
target. This boundary preserves PPTX structure even when a legacy id resembles
|
|
content.
|
|
|
|
---
|
|
|
|
## 5. OOXML Rules
|
|
|
|
The writer emits animation timing after `p:transition` and before `p:extLst`.
|
|
Normally this is one root `p:timing`; nonzero `bounce_end` uses PowerPoint's
|
|
native `mc:AlternateContent` with a p14 Choice and non-bounce Fallback. Each
|
|
branch contains a `tmRoot`, a `mainSeq` when ordinary Start rows exist, one
|
|
`interactiveSeq` per trigger-shape row, unique branch-local `p:cTn@id` values,
|
|
and same-slide `p:spTgt` references.
|
|
|
|
Trigger mapping:
|
|
|
|
| Public trigger | Object row `p:cTn@nodeType` |
|
|
|---|---|
|
|
| `on-click` | `clickEffect` |
|
|
| `with-previous` | `withEffect` |
|
|
| `after-previous` | `afterEffect` |
|
|
|
|
A row-level `trigger_shape` resolves to a different shape id and writes
|
|
PowerPoint's native `interactiveSeq` with `onClick` shape conditions. Its row
|
|
remains `clickEffect`; row `delay` becomes `TriggerDelayTime`. Ordinary rows
|
|
remain in `mainSeq` and keep the slide Start mode.
|
|
|
|
Row `trigger` overrides slide Start in both forms. `trigger_shape` implies
|
|
`on-click` and conflicts with an explicit non-`on-click` Start. Repeated
|
|
`p:spTgt@spid` values are valid distinct Animation Pane rows. Ordinary rows
|
|
retain page-wide `order`; trigger-shape rows retain their relative order in
|
|
separate `interactiveSeq` branches and do not interleave with `mainSeq`.
|
|
|
|
The writer does not emit `p:bldP` for grouped content or pictures. Microsoft
|
|
defines `p:bldP@spid` for a text-bearing `p:sp`; using it for `p:grpSp` or
|
|
`p:pic` creates an invalid build reference. Package validation still accepts a
|
|
valid source `p:bldP` that targets a text-bearing shape.
|
|
|
|
Direct-PPTX preserve mode also tolerates an unchanged legacy `p:bldP` that
|
|
targets an existing group/picture. Earlier PPT Master exports wrote this form;
|
|
the direct routes fingerprint and preserve it instead of blocking those decks.
|
|
New generated output never writes it, and generated-package validation remains
|
|
strict.
|
|
|
|
`entrance_appear` is the visibility-flip exception: its `p:set` behavior is
|
|
always 1ms. The configured positive duration remains the row's scheduling span
|
|
used when computing the next `after-previous` offset; read-back verifies the
|
|
1ms behavior and the resulting timeline offset separately. The compatibility
|
|
inputs `appear` and `cut` normalize to this canonical preset.
|
|
|
|
Other native presets with a
|
|
finite duration scale every finite behavior duration and start delay
|
|
proportionally, preserving multi-step timing such as bounce and teeter.
|
|
PowerPoint-authored instantaneous emphasis presets keep their `indefinite`
|
|
behavior duration; their configured duration remains the scheduling span for
|
|
the next `after-previous` row.
|
|
|
|
---
|
|
|
|
## 6. Validation and Read-Back
|
|
|
|
Before export, `animation_config.py validate` uses the writer's effect-behavior
|
|
test for `bounce_end` and resolves declared sound paths against the project
|
|
root. Missing paths, non-files, and unsupported audio extensions fail this
|
|
project-level preflight; field-only validation remains filesystem-independent.
|
|
|
|
Generated export reads every slide back before packaging and compares each
|
|
requested row with the serialized result:
|
|
|
|
- row count and row order, including stable repeated-target rows;
|
|
- trigger, optional trigger shape, and shape target;
|
|
- resolved effect key, preset class, filter, `presetID`, and `presetSubtype`;
|
|
- exact effect options, repeat/reverse/rewind/acceleration/bounce/restart
|
|
semantics, completion behavior, sound relationship, and playback span;
|
|
- native behavior-tree signature, serialized behavior duration, and computed
|
|
timeline offset (`entrance_appear` and instantaneous native presets use the
|
|
exceptions above).
|
|
|
|
After packaging, validation scans every slide part for root timing placement,
|
|
duplicate or malformed `p:cTn` ids, missing `p:spTgt` shapes, invalid build
|
|
targets, and unsupported generated effect tuples. A mismatch fails export
|
|
before the requested output file replaces an existing deck.
|
|
|
|
Narration injection parses and merges the slide DOM. It adds audio timing under
|
|
the existing `tmRoot`, allocates fresh ids, and preserves object animation.
|
|
For bounce timing it updates both p14 Choice and Fallback; unsupported nested
|
|
timing containers still fail safely instead of being duplicated.
|
|
|
|
Direct-PPTX routes run the structural package validator with generated-effect
|
|
enforcement disabled. This permits preservation of source/extension effects and
|
|
legacy group build rows while still rejecting corrupt timing IDs or missing
|
|
targets. Template fill and native enhancement fingerprint the source
|
|
object-animation tree before and after their allowed edits; any semantic change
|
|
fails. These routes have no object-animation write ownership.
|
|
|
|
The conversion trace is also the authoritative input for downstream video
|
|
motion. `video_motion_plan.py` preserves the resolved effect/options, direction,
|
|
row order, base and repeat-aware playback duration, absolute offset, object
|
|
bounds, and narration-derived slide advance while adding only renderer-specific enhancement parameters. Video
|
|
renderers must not bypass this read-back result and infer motion from sidecar
|
|
delay values alone.
|
|
|
|
---
|
|
|
|
## 7. Compatibility Scope
|
|
|
|
The compatibility contract covers PowerPoint OOXML and PowerPoint read-back.
|
|
Other presentation applications may interpret timing trees or filter values
|
|
differently; the exporter does not make an unconditional Keynote guarantee.
|
|
|
|
Official references:
|
|
|
|
- [Microsoft `MsoAnimEffect` enumeration](https://learn.microsoft.com/en-us/office/vba/api/powerpoint.msoanimeffect)
|
|
- [Microsoft `Sequence.AddEffect`](https://learn.microsoft.com/en-us/office/vba/api/powerpoint.sequence.addeffect)
|
|
- [Microsoft `Effect.Exit`](https://learn.microsoft.com/en-us/office/vba/api/powerpoint.effect.exit)
|
|
- [Microsoft animation-filter implementation notes](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/a96dab70-2e72-4319-928d-0eb4b275ce58)
|
|
- [Microsoft `p:bldP` implementation restrictions](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/40d17b6d-30c0-4c10-b042-b2597824a820)
|
|
- [Open XML SDK time-node values](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.presentation.timenodevalues?view=openxml-3.0.1)
|
|
- [Open XML SDK shape target](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.presentation.shapetarget?view=openxml-3.0.1)
|
|
|
|
See [`pptx-transitions.md`](./pptx-transitions.md) for the symmetric page-motion
|
|
core, MCE handling, and slide-advance contract.
|
|
See [`video-motion-plan.md`](./video-motion-plan.md) for the downstream
|
|
animation-to-video contract.
|