Working with animation
PresentationML stores animation data in slide timing markup. The core element is <p:timing/>, which contains timing nodes and behavior elements such as <p:anim/>.
The animation model is loosely based on SMIL. Slide animations are time-based effects applied to slide objects or text. Slide transitions are related, but they are stored in <p:transition/> and occur before the slide's own animation timeline.
Animation structure
An animation behavior usually points at a target element on the slide and defines how a value changes over time.
<p:timing>
<p:tnLst>
<p:par>
<p:cTn id="1" dur="indefinite" restart="never"/>
</p:par>
</p:tnLst>
</p:timing>
Important animation-related elements include:
| PresentationML element | Purpose |
|---|---|
<p:timing/> | Container for slide timing and animation data |
<p:tnLst/> | Time node list |
<p:anim/> | Value animation behavior |
<p:cBhvr/> | Common behavior settings |
<p:tgtEl/> | Target element for the behavior |
<p:tavLst/> | Time-animated value list |
Important <p:anim/> attributes include:
| Attribute | Meaning |
|---|---|
by | Relative offset from the starting value |
from | Starting value |
to | Ending value |
calcmode | Interpolation mode |
valueType | Type of the animated property value |
In ooxmlsdk, the corresponding generated types live under ooxmlsdk::schemas::p, including Animate, CommonBehavior, TimeAnimateValueList, Timing, and TargetElement.
Rust workflow
Open the target slide part and add or replace a minimal timing tree:
#![allow(unused)] fn main() { pub fn add_basic_animation_timing( path: &Path, slide_index: usize, ) -> Result<Vec<u8>, Box<dyn std::error::Error>> { let mut document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?; let presentation_part = document.presentation_part()?; let Some(slide_part) = presentation_part.slide_parts(&document).nth(slide_index) else { return Err("slide index out of range".into()); }; let slide_xml = slide_part.data_as_str(&document)?.unwrap_or_default(); let updated_xml = replace_or_insert_timing(slide_xml, basic_timing_xml())?; slide_part.set_data(&mut document, updated_xml.into_bytes())?; let mut buffer = Cursor::new(Vec::new()); document.save(&mut buffer)?; Ok(buffer.into_inner()) } }
Animation markup is sensitive to ids and target references. The example targets shape id 2 from the simple fixture; in a general writer, discover the target shape id from the slide XML and keep timing node ids unique.