Honor miter percentage schema bounds

This commit is contained in:
Harrison Booth
2026-10-30 23:11:12 -05:00
parent 479c3cc5ea
commit 1de0101ced
3 changed files with 30 additions and 3 deletions
+22 -1
View File
@@ -234,7 +234,7 @@ pub fn line(imp: &mut Imp, part: &Part, ln: &El) -> Line {
"bevel" => l.join = Some(LineJoin::Bevel),
"miter" => {
l.join = Some(LineJoin::Miter);
l.miter_limit = e.attr("lim").and_then(|v| v.parse::<u32>().ok()).filter(|v| *v <= 1_000_000);
l.miter_limit = e.attr("lim").and_then(parse_miter_limit);
}
"headEnd" => l.head = line_end(e),
"tailEnd" => l.tail = line_end(e),
@@ -244,6 +244,17 @@ pub fn line(imp: &mut Imp, part: &Part, ln: &El) -> Line {
l
}
fn parse_miter_limit(value: &str) -> Option<u32> {
const MAX: u32 = i32::MAX as u32;
let value = value.trim();
if let Some(percent) = value.strip_suffix('%') {
let units = percent.trim().parse::<f64>().ok()? * 1000.0;
(units.is_finite() && (0.0..=MAX as f64).contains(&units)).then(|| units.round() as u32)
} else {
value.parse::<u32>().ok().filter(|limit| *limit <= MAX)
}
}
fn line_end(e: &El) -> Option<LineEnd> {
let kind = e.attr("type").unwrap_or("none");
Some(LineEnd { kind: kind.to_string(), w: e.attr("w").unwrap_or("med").to_string(), len: e.attr("len").unwrap_or("med").to_string() })
@@ -671,6 +682,16 @@ mod tests {
assert_eq!(color(&d.root).unwrap().base, ColorBase::Rgb { rgb: Rgba::rgb(255, 0, 0) });
}
#[test]
fn miter_limit_accepts_both_percentage_forms_with_schema_bounds() {
assert_eq!(parse_miter_limit("200000"), Some(200_000));
assert_eq!(parse_miter_limit("200%"), Some(200_000));
assert_eq!(parse_miter_limit("1000001"), Some(1_000_001));
assert_eq!(parse_miter_limit("malformed"), None);
assert_eq!(parse_miter_limit("2147483648"), None);
assert_eq!(parse_miter_limit("2147483.648%"), None);
}
#[test]
fn body_props() {
let d = parse(br#"<a:bodyPr lIns="91440" anchor="ctr" wrap="none" vert="vert270" numCol="2"><a:normAutofit fontScale="62500" lnSpcReduction="20000"/></a:bodyPr>"#).unwrap();
+4 -1
View File
@@ -233,7 +233,10 @@ pub fn line(w: &mut W, x: &mut Exp, o: &mut Out, tag: &str, l: &Line) {
match l.join {
Some(LineJoin::Round) => w.empty0("a:round"),
Some(LineJoin::Bevel) => w.empty0("a:bevel"),
Some(LineJoin::Miter) => w.empty("a:miter", A::new().o("lim", l.miter_limit)),
Some(LineJoin::Miter) => {
let limit = l.miter_limit.filter(|limit| *limit <= i32::MAX as u32);
w.empty("a:miter", A::new().o("lim", limit));
}
None => {}
}
for (t, e) in [("a:headEnd", &l.head), ("a:tailEnd", &l.tail)] {
+4 -1
View File
@@ -495,11 +495,14 @@ fn line_miter_limit_survives_two_pptx_round_trips() {
limited.line = Some(Line { join: Some(deckcraft_model::style::LineJoin::Miter), miter_limit: Some(200_000), ..Default::default() });
let mut defaulted = shape(&mut p, "Default miter", 140.0, 20.0, 100.0, 100.0);
defaulted.line = Some(Line { join: Some(deckcraft_model::style::LineJoin::Miter), ..Default::default() });
Arc::make_mut(&mut p.slides[0]).shapes.extend([limited, defaulted]);
let mut large = shape(&mut p, "Large miter", 260.0, 20.0, 100.0, 100.0);
large.line = Some(Line { join: Some(deckcraft_model::style::LineJoin::Miter), miter_limit: Some(1_000_001), ..Default::default() });
Arc::make_mut(&mut p.slides[0]).shapes.extend([limited, defaulted, large]);
let q = round(&round(&p));
assert_eq!(by_name(&q.slides[0], "Limited miter").line.as_ref().and_then(|line| line.miter_limit), Some(200_000));
assert_eq!(by_name(&q.slides[0], "Default miter").line.as_ref().and_then(|line| line.miter_limit), None);
assert_eq!(by_name(&q.slides[0], "Large miter").line.as_ref().and_then(|line| line.miter_limit), Some(1_000_001));
}
#[test]