Fix pasted type losing its layout bounds cache
Type pasted or imported from another app (Illustrator, PDF, SVG) arrives without cached_bounds, which is not serialized. TextObject::bounds() then falls back to estimate_bounds(), a rough 0.55 em per character, so the selection box came up short of the last character and alignment looked off until the file was saved and reopened (Session::add_document refreshes it). Refresh the bounds of pasted text in paste_clip, covering paste and paste in front/back/in place and every clipboard flavour, with a regression test.
This commit is contained in:
parent
e2a4173337
commit
1ee36c2c07
|
|
@ -237,6 +237,26 @@ mod tests {
|
|||
assert!((b.width() - 30.0).abs() < 1e-6 && (b.height() - 40.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pasted_type_gets_its_layout_bounds_cache() {
|
||||
// Reported from Illustrator: pasted type kept no layout bounds, so its box (and alignment)
|
||||
// fell back to the rough estimate until the file was saved and reopened.
|
||||
let mut s = session();
|
||||
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="500" height="120"><text x="0" y="80" font-family="Source Sans 3" font-size="40">WWWWWWWW</text></svg>"#;
|
||||
s.execute("clipboard.importSvg", &json!({"svg": svg})).unwrap();
|
||||
s.execute("edit.paste", &json!({})).unwrap();
|
||||
let mut checked = 0;
|
||||
s.doc().unwrap().doc.walk(|n| {
|
||||
let vectorcraft_doc::NodeKind::Text(t) = &n.kind else { return };
|
||||
let cached = t.cached_bounds.expect("pasted type computed its bounds");
|
||||
let real = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t).bounds;
|
||||
assert!((cached.width() - real.width()).abs() < 1e-6, "{cached:?} {real:?}");
|
||||
assert!(cached.width() > t.estimate_bounds().width() + 1.0, "{cached:?} is just the estimate");
|
||||
checked += 1;
|
||||
});
|
||||
assert_eq!(checked, 1, "one text object pasted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn paste_never_lands_in_an_object_that_reused_an_undone_layers_id() {
|
||||
// Found by the model-based test: undo restores the id counter, so a compound path made
|
||||
|
|
|
|||
|
|
@ -364,6 +364,9 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
|
|||
}
|
||||
}
|
||||
sel.set(new_ids.iter().copied());
|
||||
// Pasted type arrives without the layout bounds cache: fill it so boxes and alignment are
|
||||
// right away (opening the file later would, but paste must not depend on a reopen).
|
||||
super::typecmd::refresh_bounds_of(d, &new_ids);
|
||||
Ok((new_ids, imported))
|
||||
})?;
|
||||
Ok(json!({
|
||||
|
|
|
|||
|
|
@ -103,6 +103,20 @@ pub(crate) fn refresh_bounds(t: &mut TextObject) {
|
|||
t.cached_bounds = Some(lay.bounds);
|
||||
}
|
||||
|
||||
/// Recompute the layout bounds cache for the text among `ids` (and their descendants) that lacks
|
||||
/// it. Pasted or imported type arrives without the cache (not serialized), so selection boxes, the
|
||||
/// Transform panel and hit testing would fall back to [`TextObject::estimate_bounds`] (a rough
|
||||
/// 0.55 em per character) until the text is edited — the box comes up short and alignment looks off.
|
||||
pub(crate) fn refresh_bounds_of(d: &mut Document, ids: &[NodeId]) {
|
||||
for id in text_ids(d, ids) {
|
||||
if let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind)
|
||||
&& t.cached_bounds.is_none()
|
||||
{
|
||||
refresh_bounds(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text objects among `ids` and their descendants.
|
||||
fn text_ids(d: &Document, ids: &[NodeId]) -> Vec<NodeId> {
|
||||
let mut out = vec![];
|
||||
|
|
|
|||
Loading…
Reference in New Issue