mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Native reopen: type in symbols gets its layout bounds back (#1738)
Type's layout bounds are a cache that isn't saved. On open they were computed again only for type in the layers, so type in a symbol definition (or a pattern tile, graph design or opacity mask) kept the rough estimate, and an envelope warping it was fitted to a different box: warped type in a symbol changed shape after a save and reopen. The refresh now goes through every art tree, masks included.
This commit is contained in:
1 parent
904414aa7d
commit
e54fe9c31e
2 files changed
+65
-13
No files matched your search
+33
-13
@@ -1229,22 +1229,17 @@ impl Session {
|
||||
format!("Untitled-{}", self.untitled_counter)
|
||||
}
|
||||
/// Text layout bounds are a cache (not saved): compute them for a document just read, or
|
||||
/// selection boxes and hit testing would use the rough estimate until each text is edited.
|
||||
/// selection boxes and hit testing would use the rough estimate until each text is edited, and
|
||||
/// what is fitted to type's bounds (an envelope warping it) would come out differently. That
|
||||
/// holds for the type in symbol definitions, pattern tiles, graph designs and opacity masks too,
|
||||
/// not only in the layers (#1738).
|
||||
pub(crate) fn refresh_text_bounds(doc: &mut Document) {
|
||||
// Inline graphics in text take their size from their symbols' art (not saved either).
|
||||
doc.resolve_inline_art();
|
||||
let mut texts = vec![];
|
||||
doc.walk(|n| {
|
||||
if matches!(n.kind, NodeKind::Text(_)) {
|
||||
texts.push(n.id);
|
||||
}
|
||||
});
|
||||
for id in texts {
|
||||
if let Some(NodeKind::Text(t)) = doc.node_mut(id).map(|n| &mut n.kind)
|
||||
&& t.cached_bounds.is_none()
|
||||
{
|
||||
cmd::typecmd::refresh_bounds(t);
|
||||
}
|
||||
let trees = doc.layers.iter_mut().chain(doc.symbols.iter_mut().map(|s| &mut s.art));
|
||||
let trees = trees.chain(doc.graph_designs.iter_mut().map(|g| &mut g.art));
|
||||
for tree in trees.chain(doc.patterns.iter_mut().flat_map(|p| p.art.iter_mut())) {
|
||||
refresh_tree_text_bounds(tree);
|
||||
}
|
||||
}
|
||||
/// Add a document and make it active.
|
||||
@@ -1704,6 +1699,31 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lay out the type in tree `n` (opacity mask art included) whose bounds aren't cached. Only the
|
||||
/// nodes on the way to such type are copied.
|
||||
fn refresh_tree_text_bounds(n: &mut Arc<vectorcraft_doc::Node>) {
|
||||
fn stale(n: &vectorcraft_doc::Node) -> bool {
|
||||
matches!(&n.kind, NodeKind::Text(t) if t.cached_bounds.is_none())
|
||||
|| n.extras().mask.as_deref().is_some_and(|m| stale(&m.art))
|
||||
|| n.children().is_some_and(|ch| ch.iter().any(|c| stale(c)))
|
||||
}
|
||||
if !stale(n) {
|
||||
return;
|
||||
}
|
||||
let n = Arc::make_mut(n);
|
||||
if let NodeKind::Text(t) = &mut n.kind
|
||||
&& t.cached_bounds.is_none()
|
||||
{
|
||||
cmd::typecmd::refresh_bounds(t);
|
||||
}
|
||||
if let Some(m) = n.extras_if_any().and_then(|e| e.mask.as_deref_mut()) {
|
||||
refresh_tree_text_bounds(&mut m.art);
|
||||
}
|
||||
for c in n.children_mut().into_iter().flatten() {
|
||||
refresh_tree_text_bounds(c);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -288,3 +288,35 @@ fn enveloped_type_keeps_each_runs_colour_on_the_canvas() {
|
||||
}
|
||||
assert!(red > 100 && black > 100, "{red} red, {black} black");
|
||||
}
|
||||
|
||||
/// #1738: type warped by an envelope inside a symbol looks the same after a native save and
|
||||
/// reopen: the type in symbol definitions gets its layout bounds back (they aren't saved), so the
|
||||
/// envelope is fitted to the same box as before.
|
||||
#[test]
|
||||
fn warped_type_in_a_symbol_reopens_unchanged() {
|
||||
let mut s = session();
|
||||
s.execute("text.create", &json!({"x": 30, "y": 160, "text": "STUDIO", "size": 72, "color": "#E3AB49"})).unwrap();
|
||||
s.execute("object.envelope.makeWithWarp", &json!({"style": "wave", "bend": 18})).unwrap();
|
||||
s.execute("symbol.new", &json!({"name": "Master"})).unwrap();
|
||||
let render = |s: &Session| vectorcraft_render::Renderer::new().render(&s.doc().unwrap().doc, 400, 300, Affine::IDENTITY, &Default::default());
|
||||
let before = render(&s);
|
||||
let dir = std::env::temp_dir().join(format!("vectorcraft-1738-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("warped.vectorcraft");
|
||||
s.execute("document.save", &json!({"path": path})).unwrap();
|
||||
let mut o = Session::new();
|
||||
o.execute("document.open", &json!({"path": path})).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let d = &o.doc().unwrap().doc;
|
||||
let mut texts = 0;
|
||||
d.symbols[0].art.walk(&mut |n| {
|
||||
if let NodeKind::Text(t) = &n.kind {
|
||||
texts += 1;
|
||||
assert!(t.cached_bounds.is_some(), "the symbol's type has no layout bounds after reopening");
|
||||
}
|
||||
});
|
||||
assert_eq!(texts, 1);
|
||||
let after = render(&o);
|
||||
let differ = (0..300).flat_map(|y| (0..400).map(move |x| (x, y))).filter(|&(x, y)| before.pixel(x, y) != after.pixel(x, y)).count();
|
||||
assert_eq!(differ, 0, "{differ} pixels changed on reopening");
|
||||
}
|
||||
Reference in new issue
Block a user