From 6b2e319f965636f2fb1d71c70b127dca63a7ef16 Mon Sep 17 00:00:00 2001 From: aiongg Date: Tue, 13 Oct 2026 01:02:40 +0000 Subject: [PATCH] Integration (#636): bracket clamps can't panic on a NaN path length --- crates/compose/src/lib.rs | 2 +- crates/compose/src/tests.rs | 13 +++++++++++++ crates/engine/src/cmd/path_type.rs | 2 +- crates/geom/src/warp.rs | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/compose/src/lib.rs b/crates/compose/src/lib.rs index bb6c59fdb..8e8d86d14 100644 --- a/crates/compose/src/lib.rs +++ b/crates/compose/src/lib.rs @@ -716,7 +716,7 @@ pub fn frame_specs(doc: &Document, sid: StoryId) -> Vec { // Keep the path identity: its synthetic area uses full line extents. ..tf.options.clone() }; - let end = pt.end.map_or(len, |e| e.clamp(0.0, len)); + let end = pt.end.map_or(len, |e| e.max(0.0).min(len)); (Rect::new(0.0, 0.0, (end - pt.start).max(1.0), size * 1.6), opts) } None => (item.text_area(), tf.options.clone()), diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index 2c07450e9..0e2b133ba 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -2533,6 +2533,19 @@ fn path_text_keeps_the_original_vertical_fit_limit() { } } +/// A path whose length is NaN (a NaN point from a damaged file) composes without panicking on the +/// end bracket's clamp. +#[test] +fn path_text_with_a_nan_length_composes() { + let (mut d, sid, fid) = doc_with("HELLO", Rect::new(0.0, 0.0, 200.0, 100.0), ParaAttrs::default()); + let it = d.item_mut(fid).unwrap(); + it.shape = designcraft_doc::Shape::Path; + it.path = designcraft_geom::shapes::line(Point::ZERO, Point::new(f64::NAN, 0.0)); + it.text_frame_mut().unwrap().options.path = + Some(designcraft_doc::PathType { start: 0.0, end: Some(50.0), flip: false, align: designcraft_doc::PathAlign::Baseline }); + let _ = compose_story(&d, sid, &ComposeOptions::default()); +} + fn two_keep_frames(text: &str) -> (Document, StoryId, ItemId, ItemId) { let (mut d, sid, first) = baseline_fit_doc(text, 48.0); d.item_mut(first).unwrap().path = designcraft_geom::shapes::rectangle(Rect::new(0.0, 0.0, 75.0, 48.0)); diff --git a/crates/engine/src/cmd/path_type.rs b/crates/engine/src/cmd/path_type.rs index 7c531d77e..ffd852f97 100644 --- a/crates/engine/src/cmd/path_type.rs +++ b/crates/engine/src/cmd/path_type.rs @@ -97,7 +97,7 @@ fn path_options(s: &mut Session, p: &Value) -> Result { return Ok(json!({"deleted": true})); } if let Some(v) = p.get("start").and_then(Value::as_f64).filter(|v| v.is_finite()) { - pt.start = v.clamp(0.0, len); + pt.start = v.max(0.0).min(len); } match p.get("end") { Some(Value::Null) => pt.end = None, diff --git a/crates/geom/src/warp.rs b/crates/geom/src/warp.rs index 34041d280..dacdedd06 100644 --- a/crates/geom/src/warp.rs +++ b/crates/geom/src/warp.rs @@ -25,7 +25,7 @@ impl PathWarp { /// Point and tangent angle (radians) at arc length `s` (clamped to the path). pub fn at(&self, s: f64) -> Option<(Point, f64)> { let s = if self.flip { self.total - s } else { s }; - let mut s = s.clamp(0.0, self.total); + let mut s = s.max(0.0).min(self.total); let n = self.segs.len(); for (i, (seg, len)) in self.segs.iter().enumerate() { if s <= *len || i + 1 == n {