mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 12:16:53 -06:00
fix(compose): text recomposes when the baseline grid, a wrap layer's visibility or section numbering changes (#718)
This commit is contained in:
@@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-13** · Text recomposes when the document baseline grid changes, when a layer holding a text-wrap object is shown or hidden, or when a section's start number, numbering style, prefix or marker changes; the composition cache keyed only on the section count and item identities and kept the old layout. No readiness percentage change.
|
||||
- **2026-10-12** · macOS Dock Quit uses the same unsaved-document prompts as window and menu Quit. Repeated requests keep the current answers; Cancel retains every tab and unsaved recovery checkpoint, and completed Save/Don't Save answers exit through the ordinary event loop. No readiness percentage change.
|
||||
- **2026-10-12** · Orderly native exit writes the latest recovery state after finalizing current quit approvals; incomplete or stale plans keep their documents, and Undo to the saved state clears stale recovery. Unavailable or failed recovery is reported. Cancelable macOS Dock Quit remains open (#544). No readiness percentage change.
|
||||
- **2026-10-12** · Recovered documents keep a checkpoint until saved, closed or reverted, including a second crash before autosave. Restarted document IDs and explicit recovery folders preserve other snapshots; failed republishing retains the original. No readiness percentage change.
|
||||
|
||||
@@ -47,7 +47,18 @@ fn doc_signature(doc: &Document) -> u64 {
|
||||
}
|
||||
(Arc::as_ptr(&doc.styles) as usize).hash(&mut h);
|
||||
format!("{:?}", doc.text_variables).hash(&mut h);
|
||||
sections_hash(doc).hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
/// Everything page names and section markers are made from.
|
||||
fn sections_hash(doc: &Document) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
doc.sections.len().hash(&mut h);
|
||||
for s in &doc.sections {
|
||||
(s.start, s.start_number, s.style as u8, &s.prefix, &s.marker, s.include_prefix).hash(&mut h);
|
||||
}
|
||||
h.finish()
|
||||
}
|
||||
|
||||
@@ -253,8 +264,17 @@ impl Cache {
|
||||
|
||||
fn signature(doc: &Document, story: &Arc<Story>) -> (Vec<usize>, Vec<Arc<Item>>) {
|
||||
// The document's font scope: another document's fonts of the same name set it differently.
|
||||
let mut sig =
|
||||
vec![Arc::as_ptr(story) as usize, story.rev as usize, Arc::as_ptr(&doc.styles) as usize, doc.sections.len(), doc.font_scope as usize];
|
||||
// Sections name the pages under the frames (page-number and section markers).
|
||||
let g = &doc.settings.baseline_grid;
|
||||
let mut sig = vec![
|
||||
Arc::as_ptr(story) as usize,
|
||||
story.rev as usize,
|
||||
Arc::as_ptr(&doc.styles) as usize,
|
||||
sections_hash(doc) as usize,
|
||||
doc.font_scope as usize,
|
||||
g.start.to_bits() as usize,
|
||||
g.increment.to_bits() as usize,
|
||||
];
|
||||
let mut keep = Vec::new();
|
||||
let mut spreads = Vec::new();
|
||||
for f in &story.frames {
|
||||
@@ -273,6 +293,8 @@ fn signature(doc: &Document, story: &Arc<Story>) -> (Vec<usize>, Vec<Arc<Item>>)
|
||||
for it in &sp.items {
|
||||
if it.wrap.mode != WrapMode::None {
|
||||
sig.push(Arc::as_ptr(it) as usize);
|
||||
// Wrap objects on hidden layers don't wrap text.
|
||||
sig.push(usize::from(doc.layer(it.layer).is_some_and(|l| l.visible)));
|
||||
keep.push(it.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2929,3 +2929,47 @@ fn empty_paragraphs_get_no_bullet_or_number() {
|
||||
assert_eq!(list_labels(&cs, LABEL_CHARS), ["1.", "", "2.", ""], "list `{name}`");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_recomposes_when_the_baseline_grid_changes() {
|
||||
let para = ParaAttrs { grid_align: Some(GridAlign::AllLines), ..Default::default() };
|
||||
let (mut d, sid, _) = doc_with(LOREM, Rect::new(0.0, 0.0, 200.0, 600.0), para);
|
||||
let cache = Cache::new();
|
||||
let a = cache.get(&d, sid, None);
|
||||
d.settings.baseline_grid.increment = 30.0;
|
||||
let b = cache.get(&d, sid, None);
|
||||
assert!(!Arc::ptr_eq(&a, &b));
|
||||
let (la, lb) = (&a.frames[0].lines[1], &b.frames[0].lines[1]);
|
||||
assert!((la.baseline - lb.baseline).abs() > 1.0, "{} {}", la.baseline, lb.baseline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_recomposes_when_a_wrap_layer_is_hidden() {
|
||||
let (mut d, sid, _) = doc_with(&[LOREM; 2].join(" "), Rect::new(0.0, 0.0, 300.0, 800.0), ParaAttrs::default());
|
||||
let lid = d.default_layer();
|
||||
let id = ItemId(d.alloc());
|
||||
let mut it =
|
||||
designcraft_doc::Item::new(id, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(Rect::new(0.0, 0.0, 120.0, 60.0)));
|
||||
it.wrap.mode = WrapMode::BoundingBox;
|
||||
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
|
||||
let cache = Cache::new();
|
||||
let a = cache.get(&d, sid, None);
|
||||
assert!(a.frames[0].lines[0].x0 >= 120.0 - 1e-6);
|
||||
d.layer_mut(lid).unwrap().visible = false;
|
||||
let b = cache.get(&d, sid, None);
|
||||
assert!(!Arc::ptr_eq(&a, &b));
|
||||
assert!(b.frames[0].lines[0].x0 < 1.0, "{}", b.frames[0].lines[0].x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_recomposes_page_numbers_when_a_section_is_renumbered() {
|
||||
let text = story::PAGE_NUMBER.to_string();
|
||||
let (mut d, sid, _) = doc_with(&text, Rect::new(0.0, 0.0, 300.0, 100.0), ParaAttrs::default());
|
||||
let cache = Cache::new();
|
||||
let a = cache.get(&d, sid, None);
|
||||
assert_eq!(a.frames[0].lines[0].glyphs.len(), 1);
|
||||
d.sections[0].start_number = Some(1000);
|
||||
let b = cache.get(&d, sid, None);
|
||||
assert!(!Arc::ptr_eq(&a, &b));
|
||||
assert_eq!(b.frames[0].lines[0].glyphs.len(), 4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user