diff --git a/crates/doc/src/text.rs b/crates/doc/src/text.rs index 1dc202553..62bca0ba1 100644 --- a/crates/doc/src/text.rs +++ b/crates/doc/src/text.rs @@ -1713,8 +1713,18 @@ impl TextObject { /// How far the characters' strokes paint past their glyphs, in document space: they scale /// with the type. pub fn char_stroke_outset(&self) -> f64 { + self.char_strokes(CharStyle::stroke_outset) + } + /// How far the characters' strokes can paint past their glyphs, their miter spikes and square + /// caps included ([`StrokeLayer::reach`]), in document space: what a change of the type must + /// redraw. The visual bounds take [`Self::char_stroke_outset`]. + pub fn char_stroke_reach(&self) -> f64 { + self.char_strokes(|style| if style.has_stroke() { style.stroke_layer().reach() } else { 0.0 }) + } + /// The farthest `outset` of the characters' styles, scaled with the type. + fn char_strokes(&self, outset: impl Fn(&CharStyle) -> f64) -> f64 { let k = crate::node::Scaling::factor(self.xf).unwrap_or(1.0); - self.runs.iter().map(|r| r.style.stroke_outset()).fold(0.0, f64::max) * k + self.runs.iter().map(|r| outset(&r.style)).fold(0.0, f64::max) * k } pub fn transform(&mut self, a: Affine) { self.xf = a * self.xf; diff --git a/crates/render/src/fx.rs b/crates/render/src/fx.rs index 98a1d6baa..b93d4fe2c 100644 --- a/crates/render/src/fx.rs +++ b/crates/render/src/fx.rs @@ -489,11 +489,12 @@ fn cull_bounds_in(symbols: &SymbolBoxes, n: &Node, after_reshape: bool) -> Optio } /// The box of type `t`'s glyphs as laid out (its layout bounds, else laid out now), grown by its -/// characters' strokes and by `outset`: on a path they stand above it and run past its ends, in an -/// area they can hang past its frame, where its own box ([`Node::visual_bounds_with`]) doesn't. +/// characters' strokes, miter spikes included, and by `outset`: on a path they stand above it and +/// run past its ends, in an area they can hang past its frame, where its own box +/// ([`Node::visual_bounds_with`]) doesn't. fn glyphs_box(t: &vectorcraft_doc::TextObject, outset: f64) -> Option { let b = t.cached_bounds.unwrap_or_else(|| vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t).bounds); - let o = outset.max(t.char_stroke_outset()); + let o = outset.max(t.char_stroke_reach()); (b.width() > 0.0 || b.height() > 0.0).then(|| t.xf.transform_rect_bbox(b).inflate(o, o)) } diff --git a/crates/render/src/tests_changed.rs b/crates/render/src/tests_changed.rs index b5e78138f..21b75c158 100644 --- a/crates/render/src/tests_changed.rs +++ b/crates/render/src/tests_changed.rs @@ -192,6 +192,28 @@ fn reshaping_the_path_of_type_on_a_path_changes_where_its_letters_were_and_are() redraw(&old, &new); } +/// Stroked type with sharp corners: the miter spikes of its characters' strokes reach past half +/// the weight, and change where they were and where they are. +#[test] +fn moving_type_with_mitred_strokes_changes_where_its_spikes_reach() { + let mut old = Document::new(110.0, 80.0); + let l = old.layers[0].id; + let style = vectorcraft_doc::text::CharStyle { + size: 30.0, + stroke: Paint::solid(Color::BLACK), + stroke_width: 10.0, + stroke_join: vectorcraft_doc::LineJoin::Miter, + stroke_miter_limit: 10.0, + ..Default::default() + }; + let mut t = vectorcraft_doc::text::TextObject::point(Point::new(10.0, 50.0), "VAWV", style); + t.cached_bounds = Some(vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t).bounds); + let text = old.insert(Some(l), 0, Node::new(NodeId(0), NodeKind::Text(Box::new(t)))).unwrap(); + let mut new = old.clone(); + new.node_mut(text).unwrap().transform(Affine::translate((9.0, 7.0)), false); + redraw(&old, &new); +} + #[test] fn an_unchanged_document_changes_nothing_and_a_new_symbol_everything() { let (old, _) = document();