pptx: infer paragraph builds when bldP does not disable them

This commit is contained in:
Neil Cawse committed 2026-11-08 13:36:49 +00:00
1 parent 48e2203123
commit 9d9610a893
1 file changed
+10 -7
+10 -7
View File
@@ -239,13 +239,12 @@ pub fn animations(imp: &mut Imp, part: &Part, ctx: &IdCtx, timing: &El, shapes:
let Some(tn) = timing.child("tnLst") else { return vec![] };
let mut found = vec![];
collect(tn, At { trigger: None, group: 0, offset: 0 }, &mut 0, &mut found, 0);
// Paragraph builds (`bldP build="p"`) by (spid, grpId).
let by_para: Vec<(u32, String)> = timing
// Paragraph builds (`bldP`) by (spid, grpId) -> whether `build="p"`.
let bld_p: std::collections::HashMap<(u32, String), bool> = timing
.child("bldLst")
.map(|b| {
b.children_named("bldP")
.filter(|p| p.attr("build") == Some("p"))
.filter_map(|p| Some((p.u32("spid")?, p.attr("grpId").unwrap_or("0").to_string())))
.filter_map(|p| Some(((p.u32("spid")?, p.attr("grpId").unwrap_or("0").to_string()), p.attr("build") == Some("p"))))
.collect()
})
.unwrap_or_default();
@@ -314,7 +313,7 @@ pub fn animations(imp: &mut Imp, part: &Part, ctx: &IdCtx, timing: &El, shapes:
a.color = dml::color(clr);
}
let grp = c.attr("grpId").unwrap_or("0").to_string();
let build = paragraph.is_some() && by_para.iter().any(|(s, g)| *s == file_id && *g == grp);
let build = paragraph.is_some() && bld_p.get(&(file_id, grp.clone())) != Some(&false);
out.push((a, file_id, grp, build));
at.push((f.trigger, f.group, f.offset));
}
@@ -328,9 +327,10 @@ pub fn animations(imp: &mut Imp, part: &Part, ctx: &IdCtx, timing: &El, shapes:
for run in out.chunk_by(same_build) {
let effects: Vec<&Animation> = run.iter().map(|(a, ..)| a).collect();
let build = run.first().is_some_and(|r| r.3);
let explicit_bld = run.first().is_some_and(|r| bld_p.get(&(r.1, r.2.clone())) == Some(&true));
let text = effects.first().and_then(|a| find_shape(shapes, a.shape)).and_then(|s| s.text.as_ref());
match (effects.first(), text) {
(Some(first), Some(text)) if build && plays_as_build(&effects, text) => {
(Some(first), Some(text)) if build && plays_as_build(&effects, text, explicit_bld) => {
anims.push(Animation { text_build: TextBuild::ByParagraph, paragraph: None, ..(*first).clone() });
}
_ => anims.extend(effects.into_iter().cloned()),
@@ -343,7 +343,7 @@ pub fn animations(imp: &mut Imp, part: &Part, ctx: &IdCtx, timing: &El, shapes:
/// first one plays: one effect per paragraph the build animates, in paragraph order, later top
/// paragraphs on click (after previous when the build doesn't start on click), deeper ones with
/// their parent, all with the same settings?
fn plays_as_build(effects: &[&Animation], text: &TextBody) -> bool {
fn plays_as_build(effects: &[&Animation], text: &TextBody, explicit_bld: bool) -> bool {
let roles = paragraph_build_roles(text);
let expected = roles.iter().enumerate().filter(|(i, r)| match r {
BuildRole::Top => true,
@@ -351,6 +351,9 @@ fn plays_as_build(effects: &[&Animation], text: &TextBody) -> bool {
BuildRole::Unowned => false,
});
let Some(first) = effects.first() else { return false };
if !explicit_bld && first.start != AnimStart::OnClick && (span_ms(first) != 500 || first.delay_ms != 0) {
return false;
}
let settings = |a: &Animation| Animation { start: AnimStart::OnClick, paragraph: None, ..a.clone() };
let base = settings(first);
let mut matched = 0;