mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 07:46:07 +00:00
Type: a document replaced in the back measures its type when it is replaced
Session::replace_document on a tab other than the current one cleared the bounds of type set in a font named like a composite font and left that type with estimated bounds until the document came to the front. - The replacement's type without bounds is measured at once, in the document's own composite fonts, lent for the measuring. The front document stays the one whose composite fonts are registered. - When the document then comes to the front unchanged, its type in its own composite fonts is not measured again. Type set in a composite font only another document defines is still measured again, as missing, and so is type set in a composite font name that another thread's loan held with a different definition.
This commit is contained in:
@@ -536,6 +536,80 @@ fn a_document_replaced_in_the_back_is_measured_when_the_front_one_closes() {
|
||||
assert_eq!(actual.cached_bounds, Some(expected));
|
||||
}
|
||||
|
||||
/// The type of document `index` with id `id`, wherever its tab is.
|
||||
fn text_in(s: &Session, index: usize, id: u64) -> vectorcraft_doc::TextObject {
|
||||
match &s.documents()[index].doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => (**t).clone(),
|
||||
_ => panic!("not text"),
|
||||
}
|
||||
}
|
||||
|
||||
/// A document replaced while in the back has its type measured in its own composite fonts at once,
|
||||
/// while the front document stays in front with its own definition registered; coming to the front
|
||||
/// does not measure that type again.
|
||||
#[test]
|
||||
fn a_document_replaced_in_the_back_is_measured_before_it_comes_to_the_front() {
|
||||
let (bytes, id, width) = full_roman_file("CF Pane");
|
||||
let mut s = session();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
s.execute("type.compositeFonts.set", &json!({"fonts": [font("CF Pane")]})).unwrap();
|
||||
assert!(s.replace_document(0, vectorcraft_format::load(&bytes).unwrap()));
|
||||
assert_eq!(s.active_index(), Some(1));
|
||||
let front = vectorcraft_text::FontDb::global().composite_font("CF Pane").unwrap();
|
||||
assert_eq!(front.parts.get(vectorcraft_doc::CharClass::Roman).size, 50.0);
|
||||
let measured = text_in(&s, 0, id);
|
||||
assert!((measured.cached_bounds.unwrap().width() - width).abs() < 1e-6);
|
||||
let node = s.documents()[0].doc.node(NodeId(id)).unwrap().geometric_bounds().unwrap();
|
||||
assert!((node.width() - width).abs() < 1e-6);
|
||||
let doc = std::sync::Arc::as_ptr(&s.documents()[0].doc);
|
||||
assert!(s.set_active(0));
|
||||
assert!(std::ptr::eq(doc, std::sync::Arc::as_ptr(&s.documents()[0].doc)));
|
||||
assert_eq!(text(&s, id).cached_bounds, measured.cached_bounds);
|
||||
}
|
||||
|
||||
/// Type a document replaced in the back sets in a composite font that only the front document
|
||||
/// defines is measured in that font while the front one is registered, and again, as missing, when
|
||||
/// the replaced document comes to the front.
|
||||
#[test]
|
||||
fn a_document_replaced_in_the_back_measures_a_font_it_lacks_again_in_front() {
|
||||
let mut s = session();
|
||||
let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Ab12", "size": 20, "font": "CF Lacked"})).unwrap()["id"].as_u64().unwrap();
|
||||
let bytes = vectorcraft_format::save_file(&s.doc().unwrap().doc);
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
s.execute("type.compositeFonts.set", &json!({"fonts": [font("CF Lacked")]})).unwrap();
|
||||
assert!(s.replace_document(0, vectorcraft_format::load(&bytes).unwrap()));
|
||||
let back = text_in(&s, 0, id);
|
||||
let expected = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &back).bounds;
|
||||
assert_eq!(back.cached_bounds, Some(expected));
|
||||
assert!(s.set_active(0));
|
||||
assert!(vectorcraft_text::FontDb::global().composite_font("CF Lacked").is_none());
|
||||
let actual = text(&s, id);
|
||||
let expected = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &actual).bounds;
|
||||
assert_eq!(actual.cached_bounds, Some(expected));
|
||||
}
|
||||
|
||||
/// Text holding an inline graphic whose symbol's art is type in a composite font, in a document
|
||||
/// replaced in the back, is measured with the graphic at the size the document's own definition
|
||||
/// gives it before it comes to the front.
|
||||
#[test]
|
||||
fn a_document_replaced_in_the_back_measures_inline_graphics_in_its_own_definition() {
|
||||
let mut s = session();
|
||||
let mut full = font("CF Back Inline");
|
||||
full["parts"]["roman"]["size"] = json!(100);
|
||||
s.execute("type.compositeFonts.set", &json!({"fonts": [full]})).unwrap();
|
||||
let art =
|
||||
s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Ab12", "size": 20, "font": "CF Back Inline"})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("symbol.new", &json!({"name": "CF Back Inline Mark", "ids": [art]})).unwrap();
|
||||
let id = s.execute("text.create", &json!({"x": 10, "y": 150, "text": "x", "size": 20})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("text.insertInline", &json!({"id": id, "at": 0, "symbol": "CF Back Inline Mark"})).unwrap();
|
||||
let width = text(&s, id).cached_bounds.unwrap().width();
|
||||
let bytes = vectorcraft_format::save_file(&s.doc().unwrap().doc);
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
s.execute("type.compositeFonts.set", &json!({"fonts": [font("CF Back Inline")]})).unwrap();
|
||||
assert!(s.replace_document(0, vectorcraft_format::load(&bytes).unwrap()));
|
||||
assert!((text_in(&s, 0, id).cached_bounds.unwrap().width() - width).abs() < 1e-6);
|
||||
}
|
||||
|
||||
/// A failed batch takes back the composite fonts it set, in the font database too.
|
||||
#[test]
|
||||
fn a_failed_batch_takes_back_its_composite_fonts() {
|
||||
|
||||
@@ -125,6 +125,11 @@ pub struct DocState {
|
||||
empty_artboards: std::cell::RefCell<EmptyArtboards>,
|
||||
/// What [`DocState::any_art_to_fit`] last measured. Not saved.
|
||||
art_to_fit: std::cell::RefCell<Option<ArtToFit>>,
|
||||
/// The document as [`crate::Session::replace_document`] left it in a tab in the back, with its
|
||||
/// type measured in its own composite fonts, and the lowercased names of those fonts. When the
|
||||
/// same document comes to the front, its type set in them is not measured again. While this
|
||||
/// reference lasts, its allocation holds no other document. Not saved.
|
||||
pub(crate) measured_in_own: Option<(std::sync::Weak<Document>, std::collections::HashSet<String>)>,
|
||||
}
|
||||
|
||||
/// Whether a document has an artboard for Delete Empty Artboards to delete, with the document that
|
||||
@@ -341,6 +346,7 @@ impl DocState {
|
||||
empty_artboards: Default::default(),
|
||||
plugin_effects: Default::default(),
|
||||
art_to_fit: Default::default(),
|
||||
measured_in_own: None,
|
||||
}
|
||||
}
|
||||
/// `measure`'s answer for the document now. `measure` runs once for each version of the document,
|
||||
|
||||
@@ -241,10 +241,18 @@ impl Session {
|
||||
/// Register the composite fonts of the document now in front ([`cmd::compositefonts::sync`]),
|
||||
/// after a switch or a close brought it there. Its type set in a composite font that differs
|
||||
/// from the one of that name before (added, changed or removed) is measured again, and type not
|
||||
/// measured yet (in a document replaced while in the back) is measured now.
|
||||
/// measured yet is measured now. Type that [`Session::replace_document`] measured in the
|
||||
/// document's own composite fonts while it was in the back is not measured again when the
|
||||
/// document is unchanged since.
|
||||
fn sync_front_composite_fonts(&mut self) {
|
||||
let changed: HashSet<String> = cmd::compositefonts::sync(self).into_iter().collect();
|
||||
let mut changed: HashSet<String> = cmd::compositefonts::sync(self).into_iter().collect();
|
||||
let Some(index) = self.active else { return };
|
||||
if let Some(st) = self.docs.get_mut(index)
|
||||
&& let Some((doc, own)) = st.measured_in_own.take()
|
||||
&& std::ptr::eq(doc.as_ptr(), Arc::as_ptr(&st.doc))
|
||||
{
|
||||
changed.retain(|n| !own.contains(n));
|
||||
}
|
||||
self.refresh_document_text_bounds(index, |t| t.cached_bounds.is_none() || sets_in_any(t, &changed));
|
||||
}
|
||||
|
||||
@@ -347,14 +355,10 @@ impl Session {
|
||||
doc.resolve_inline_art();
|
||||
doc.guides_into_nodes();
|
||||
cmd::dimension::conform(&mut doc);
|
||||
// In the back, type set in a font named like a composite font is measured when the document
|
||||
// comes to the front and its own composite fonts are registered, not in the front
|
||||
// document's.
|
||||
let mut back_names = None;
|
||||
if self.active == Some(index) {
|
||||
// Pending tool work (typing, a drag) belongs to the content being thrown away.
|
||||
self.reset_tool_for_doc_switch();
|
||||
} else {
|
||||
// In the back, type set in a font named like a composite font is measured again below, in
|
||||
// the document's own composite fonts, not in the front document's.
|
||||
let back = self.active != Some(index);
|
||||
if back {
|
||||
let mut names = cmd::compositefonts::names_in_use(self);
|
||||
names.extend(doc.composite_fonts.iter().map(|f| f.name.to_lowercase()));
|
||||
for tree in text_trees_mut(&mut doc) {
|
||||
@@ -363,7 +367,9 @@ impl Session {
|
||||
t.cached_baselines.clear();
|
||||
});
|
||||
}
|
||||
back_names = Some(names);
|
||||
} else {
|
||||
// Pending tool work (typing, a drag) belongs to the content being thrown away.
|
||||
self.reset_tool_for_doc_switch();
|
||||
}
|
||||
let old = &self.docs[index];
|
||||
let mut st = DocState::new(doc, old.path.clone());
|
||||
@@ -396,16 +402,36 @@ impl Session {
|
||||
if let Some(stash) = &mut self.batch_stash {
|
||||
stash.push(old);
|
||||
}
|
||||
match back_names {
|
||||
Some(names) => self.refresh_document_text_bounds(index, |t| t.cached_bounds.is_none() && !sets_in_any(t, &names)),
|
||||
None => {
|
||||
self.sync_opened_composite_fonts(index);
|
||||
self.sync_front_profiles();
|
||||
}
|
||||
if back {
|
||||
self.measure_back_document(index);
|
||||
} else {
|
||||
self.sync_opened_composite_fonts(index);
|
||||
self.sync_front_profiles();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Measure the type not measured yet in open document `index`, which is in the back, in its own
|
||||
/// composite fonts, lent for the measuring, so its type has the bounds it will have when the
|
||||
/// document comes to the front. The front document stays the one whose composite fonts are
|
||||
/// registered. The names of the document's composite fonts its type was measured in are kept,
|
||||
/// so its coming to the front does not measure that type again.
|
||||
fn measure_back_document(&mut self, index: usize) {
|
||||
let Some(st) = self.docs.get(index) else { return };
|
||||
let fonts = st.doc.composite_fonts.clone();
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
let lent = db.lend_composite_fonts(&fonts);
|
||||
// A name another loan holds with a different definition is not lent: type set in it is
|
||||
// measured again when the document comes to the front.
|
||||
let own: HashSet<String> =
|
||||
fonts.iter().filter(|f| db.composite_font(&f.name).is_some_and(|have| *have == **f)).map(|f| f.name.to_lowercase()).collect();
|
||||
self.refresh_document_text_bounds(index, |t| t.cached_bounds.is_none());
|
||||
drop(lent);
|
||||
if let Some(st) = self.docs.get_mut(index) {
|
||||
st.measured_in_own = Some((Arc::downgrade(&st.doc), own));
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the document in tab `index` (`file.close`). Closing a tab other than the current one
|
||||
/// keeps the current document current, and its tool keeps its state: no document is switched
|
||||
/// to. Closing the current document makes the tab now at `index` current, or the last tab when
|
||||
|
||||
Reference in New Issue
Block a user