Opening documents: index lookups in Document::check and the format-1 style upgrade (#2218)

* fix(doc): index items, threads and styles when opening a document

* Apply review fixes

- crates/doc/src/tests.rs

* address review: borrow style names in pin_para_default

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: autocraft-bot[bot] <354215646+autocraft-bot[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-11-11 16:20:34 -05:00
1 parent 15bea9fa7a
commit eba00a0dec
5 files changed
+78 -10

No files matched your search

+6 -3
View File
@@ -312,12 +312,14 @@ fn upgrade(doc: &mut Document, from: u32) {
/// is missing.
fn pin_para_default<T: Clone>(doc: &mut Document, field: fn(&mut ParaAttrs) -> &mut Option<T>, old: T) {
let unset = |a: &ParaAttrs| field(&mut a.clone()).is_none();
// Every style is looked up by name: through one index, not a search of all styles each time.
let index = doc.styles.para_index();
let roots: std::collections::HashSet<String> = doc
.styles
.paragraph
.iter()
.filter_map(|s| {
let chain = doc.styles.para_chain(&s.name);
let chain = designcraft_doc::Styles::para_chain_by(&s.name, |n| index.get(n).copied());
if chain.iter().all(|c| unset(&c.para)) { chain.first().map(|c| c.name.clone()) } else { None }
})
.collect();
@@ -326,9 +328,10 @@ fn pin_para_default<T: Clone>(doc: &mut Document, field: fn(&mut ParaAttrs) -> &
*field(&mut s.para) = Some(old.clone());
}
}
let styles = doc.styles.clone();
// Borrowed names: one entry per style already in memory, so no copy and no extra bound needed.
let names: std::collections::HashSet<&str> = doc.styles.paragraph.iter().map(|s| s.name.as_str()).collect();
let mut pin = |st: &mut Story| {
for p in st.paras.iter_mut().filter(|p| styles.para(&p.style).is_none()) {
for p in st.paras.iter_mut().filter(|p| !names.contains(p.style.as_str())) {
let v = field(&mut p.para);
if v.is_none() {
*v = Some(old.clone());
+17
View File
@@ -355,6 +355,23 @@ fn format_1_keep_lines_without_mode_stays_all_lines() {
assert!(!designcraft_doc::ParaProps::default().keep_all_lines);
}
/// A format-1 file with many styles upgrades in time linear in them (each style's chain was
/// found by searching every style, twice over).
#[test]
fn format_1_upgrade_of_many_styles_is_linear() {
let mut d = Document::new(&NewDocument::default());
let st = d.styles_mut();
for i in 0..20_000 {
st.paragraph.push(para_style(&format!("S{i}"), (i % 2 == 1).then(|| format!("S{}", i - 1)).as_deref(), None));
}
let bytes = save_v1(&d);
let t = std::time::Instant::now();
let back = load(&bytes).unwrap();
assert!(t.elapsed() < std::time::Duration::from_secs(10), "{:?}", t.elapsed());
assert_eq!(back.styles.para("S0").unwrap().para.keep_all_lines, Some(true));
assert_eq!(back.styles.para("S1").unwrap().para.keep_all_lines, None, "based on S0, which is pinned");
}
/// Format 1 wrote paragraph rules, text frame options and numbered lists in full, so their
/// old defaults (black rules, auto-size from the top centre, lists continuing across stories)
/// load as saved. New documents take InDesign's.