This commit is contained in:
Brandon Thomas
2026-10-14 22:33:52 -04:00
3 changed files with 69 additions and 2 deletions
+1
View File
@@ -183,6 +183,7 @@ Newest first.
- **2026-10-13** · Path edits (`path.setAnchor`, `path.moveAnchors`, `path.appendAnchor`, `path.reverse`, `path.close`, anchor add/delete/convert, Erase, Smooth and Scissors) refuse a path that is locked, sits in a locked group or on a locked layer (#735). Properties links to the Stroke, Character, Paragraph, Effects, Text Wrap and Info panels open them in a customized panel layout too (#736). No readiness percentage change.
- **2026-10-13** · IDML export keeps parent item overrides: each page lists its overridden parent items and the local copy keeps its link to the parent item, so a reopened IDML file shows only the local copy (#725). No readiness percentage change.
- **2026-10-13** · macOS: a command chosen with the mouse in the menu bar runs at once. It used to wait for the next mouse move or key press over the window, so File › Open, Place and Save As seemed to take 10–20 seconds to open their dialogs. No readiness percentage change.
- **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** · Text strokes can sit behind the fill: the stroke lies wholly outside the letters at its full weight on screen, in PNG and in PDF, set from the Properties panel or Character Style Options. IDML `StrokeAlignment` on text imports and exports as this setting, so imported text with outside-aligned strokes keeps its letter shapes (#619). No readiness percentage change.
- **2026-10-12** · Placed Word documents keep their bullets and numbering (#626): `numbering.xml` levels become bullet or numbered paragraphs with Word's number formats, label text, start numbers, restarts and list indents, also when a paragraph style carries the list. No readiness percentage change.
+24 -2
View File
@@ -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()
}
@@ -261,8 +272,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 {
@@ -281,6 +301,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());
}
}
+44
View File
@@ -4182,3 +4182,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);
}