diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index 56571e4c46..c39d15625b 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -7,7 +7,7 @@ use super::settings::{CoreProps, CustomProp, Settings}; use super::text_walk::{TextMode, notes_and_boxes, story_word_count_of, text_of, walk}; use std::sync::Arc; -use super::{BTreeMap, CharProps, Document, InlineObject, Numbering, Paragraph, SectionProps, StyleSheet, Table, edit, numbering, para, section}; +use super::{BTreeMap, CharProps, Document, InlineObject, Paragraph, SectionProps, StyleSheet, Table, edit, numbering, para, section}; /// [`Document::media`] by content, so adding media is linear in the bytes added rather than a /// scan of every earlier image (CWE-407). Rebuilt whenever `media` changed size behind its back; @@ -62,8 +62,8 @@ impl Document { body: vec![para_block(Paragraph::new())], last_section: SectionProps::default(), parts: BTreeMap::new(), - styles: StyleSheet::builtin(), - numbering: Numbering::default(), + styles: Arc::new(StyleSheet::builtin()), + numbering: Arc::default(), comments: BTreeMap::new(), revisions: Vec::new(), settings: Settings::default(), diff --git a/crates/doc/src/edit.rs b/crates/doc/src/edit.rs index 689d338dcd..265a563596 100644 --- a/crates/doc/src/edit.rs +++ b/crates/doc/src/edit.rs @@ -1054,7 +1054,7 @@ impl Document { let mut default_style = None; if keep_styles { if src.styles.get("Normal").is_none() { - src.styles.upsert(crate::styles::Style { id: "Normal".into(), name: "Normal".into(), ..Default::default() }); + Arc::make_mut(&mut src.styles).upsert(crate::styles::Style { id: "Normal".into(), name: "Normal".into(), ..Default::default() }); } // Ids given out here are taken too: two styles never get the same new one. let mut given = std::collections::HashSet::new(); @@ -1089,7 +1089,7 @@ impl Document { abs_map.insert(a.id, id); let mut a = crate::numbering::AbstractNum { id, ..a.clone() }; a.levels.iter_mut().for_each(|l| restyle(&mut l.style)); - self.numbering.abstracts.push(a); + Arc::make_mut(&mut self.numbering).abstracts.push(a); } let next_num = self.numbering.nums.iter().map(|n| n.id.saturating_add(1)).max().unwrap_or(1).max(1); let mut nums = BTreeMap::new(); @@ -1099,7 +1099,7 @@ impl Document { let abstract_id = abs_map.get(&n.abstract_id).copied().unwrap_or(n.abstract_id); let mut n = crate::numbering::Num { id, abstract_id, ..n.clone() }; n.level_overrides.iter_mut().for_each(|(_, l)| restyle(&mut l.style)); - self.numbering.nums.push(n); + Arc::make_mut(&mut self.numbering).nums.push(n); } let renumber = |n: &mut Option| { if let Some(n) = n.as_mut() @@ -1138,7 +1138,7 @@ impl Document { } if self.styles.get(&st.id).is_none() { renumber(&mut st.para.numbering); - self.styles.upsert(st); + Arc::make_mut(&mut self.styles).upsert(st); } } let rev = u32::try_from(self.revisions.len()).unwrap_or(u32::MAX); diff --git a/crates/doc/src/edit/tests.rs b/crates/doc/src/edit/tests.rs index 430de05c9c..ae7f7d64af 100644 --- a/crates/doc/src/edit/tests.rs +++ b/crates/doc/src/edit/tests.rs @@ -692,19 +692,19 @@ fn adopting_a_document_renumbers_what_it_refers_to() { let mut ours = doc(&["ours"]); let our_note = ours.add_part(PartKind::Footnote, vec![para_block(Paragraph::with_text("our note", CharProps::default()))]); ours.comments.insert(0, Comment { part: our_note, ..Comment::default() }); - let our_list = ours.numbering.add_list(crate::numbering::ListKind::Bullet); + let our_list = Arc::make_mut(&mut ours.numbering).add_list(crate::numbering::ListKind::Bullet); ours.revisions.push(Revision { kind: RevisionKind::Insert, ..Revision::default() }); let mut theirs = doc(&["theirs"]); let note = theirs.add_part(PartKind::Footnote, vec![para_block(Paragraph::with_text("their note", CharProps::default()))]); let said = theirs.add_part(PartKind::Comment, vec![para_block(Paragraph::with_text("their comment", CharProps::default()))]); theirs.comments.insert(0, Comment { author: "Them".into(), part: said, durable_id: Some("0A0B0C0D".into()), ..Comment::default() }); - let list = theirs.numbering.add_list(crate::numbering::ListKind::Bullet); + let list = Arc::make_mut(&mut theirs.numbering).add_list(crate::numbering::ListKind::Bullet); assert_eq!(list, our_list); theirs.revisions.push(Revision { kind: RevisionKind::Delete, ..Revision::default() }); let mut style = crate::styles::Style { id: "TheirList".into(), name: "Their List".into(), ..Default::default() }; style.para.numbering = Some(crate::props::NumRef { num: list, level: 0 }); - theirs.styles.upsert(style); + Arc::make_mut(&mut theirs.styles).upsert(style); let p = theirs.para_mut(StoryRef::Body, &Path::top(0)).unwrap(); p.props.numbering = Some(crate::props::NumRef { num: list, level: 0 }); p.runs[0].props.del = Some(0); @@ -748,11 +748,11 @@ fn a_kept_character_style_leaves_what_it_doesnt_set_to_the_paragraph_style() { use crate::styles::{Style, StyleKind, StyleSheet}; let mut ours = doc(&["ours"]); let mut theirs = doc(&["theirs"]); - theirs.styles = StyleSheet::empty(); - theirs.styles.default_chr = CharProps { size: Some(9.0), ..Default::default() }; + theirs.styles = Arc::new(StyleSheet::empty()); + Arc::make_mut(&mut theirs.styles).default_chr = CharProps { size: Some(9.0), ..Default::default() }; let big = CharProps { size: Some(20.0), ..Default::default() }; - theirs.styles.upsert(Style { id: "Big".into(), name: "Big".into(), chr: big, ..Default::default() }); - theirs.styles.upsert(Style { id: "Link".into(), name: "Link".into(), kind: StyleKind::Character, ..Default::default() }); + Arc::make_mut(&mut theirs.styles).upsert(Style { id: "Big".into(), name: "Big".into(), chr: big, ..Default::default() }); + Arc::make_mut(&mut theirs.styles).upsert(Style { id: "Link".into(), name: "Link".into(), kind: StyleKind::Character, ..Default::default() }); let p = theirs.para_mut(StoryRef::Body, &Path::top(0)).unwrap(); p.props.style = Some("Big".into()); p.runs[0].props.style = Some("Link".into()); @@ -766,15 +766,15 @@ fn a_kept_character_style_leaves_what_it_doesnt_set_to_the_paragraph_style() { fn kept_styles_renamed_on_adoption_are_renamed_in_list_levels_too() { use crate::styles::Style; let mut ours = doc(&["ours"]); - ours.styles.upsert(Style { id: "Heading1".into(), name: "heading 1".into(), ..Default::default() }); + Arc::make_mut(&mut ours.styles).upsert(Style { id: "Heading1".into(), name: "heading 1".into(), ..Default::default() }); let mut theirs = doc(&["theirs"]); - theirs.styles.upsert(Style { id: "Heading1".into(), name: "heading 1".into(), ..Default::default() }); - let list = theirs.numbering.add_list(crate::numbering::ListKind::Numbered); + Arc::make_mut(&mut theirs.styles).upsert(Style { id: "Heading1".into(), name: "heading 1".into(), ..Default::default() }); + let list = Arc::make_mut(&mut theirs.numbering).add_list(crate::numbering::ListKind::Numbered); let abs = theirs.numbering.nums.iter().find(|n| n.id == list).map(|n| n.abstract_id).unwrap(); - let a = theirs.numbering.abstracts.iter_mut().find(|a| a.id == abs).unwrap(); + let a = Arc::make_mut(&mut theirs.numbering).abstracts.iter_mut().find(|a| a.id == abs).unwrap(); a.levels[0].style = Some("Heading1".into()); let level = a.levels[0].clone(); - theirs.numbering.nums.iter_mut().find(|n| n.id == list).unwrap().level_overrides.push((1, level)); + Arc::make_mut(&mut theirs.numbering).nums.iter_mut().find(|n| n.id == list).unwrap().level_overrides.push((1, level)); let before = ours.numbering.abstracts.len(); ours.adopt_with(theirs, true); @@ -814,10 +814,10 @@ fn an_adopted_tracked_format_change_keeps_its_old_styles_and_list() { use crate::{Revision, RevisionKind}; for keep in [false, true] { let mut ours = doc(&["ours"]); - let our_list = ours.numbering.add_list(crate::numbering::ListKind::Bullet); + let our_list = Arc::make_mut(&mut ours.numbering).add_list(crate::numbering::ListKind::Bullet); let mut theirs = doc(&["theirs"]); theirs.revisions.push(Revision { kind: RevisionKind::Format, ..Revision::default() }); - let list = theirs.numbering.add_list(crate::numbering::ListKind::Bullet); + let list = Arc::make_mut(&mut theirs.numbering).add_list(crate::numbering::ListKind::Bullet); assert_eq!(list, our_list); let p = theirs.para_mut(StoryRef::Body, &Path::top(0)).unwrap(); // Live and old props name the same styles (ours has them too) and their list. @@ -845,8 +845,8 @@ fn adopted_styles_never_become_our_defaults() { let mut ours = doc(&["ours"]); let mut theirs = doc(&["theirs"]); // A localized default character style, and one whose id we have too. - theirs.styles.upsert(Style { id: "Absatz".into(), kind: StyleKind::Character, default: true, ..Default::default() }); - theirs.styles.upsert(Style { id: "Strong".into(), kind: StyleKind::Character, default: true, ..Default::default() }); + Arc::make_mut(&mut theirs.styles).upsert(Style { id: "Absatz".into(), kind: StyleKind::Character, default: true, ..Default::default() }); + Arc::make_mut(&mut theirs.styles).upsert(Style { id: "Strong".into(), kind: StyleKind::Character, default: true, ..Default::default() }); ours.adopt_with(theirs, keep); assert!(ours.styles.styles.iter().all(|s| !s.default), "keep_styles {keep}"); } diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index bddd3f31bc..4c42be6446 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -2,9 +2,9 @@ //! //! A [`Document`] holds the main story (`body`) and secondary stories (`parts`: headers, //! footers, notes, comments, text boxes). A story is a list of [`Block`]s: paragraphs and tables; -//! table cells hold block lists of their own. Blocks are `Arc`-shared, so cloning a document is -//! cheap (undo snapshots, handing a frozen copy to the renderer) and edits copy only what they -//! touch (`Arc::make_mut`). +//! table cells hold block lists of their own. Blocks, the style sheet and the list definitions +//! are `Arc`-shared, so cloning a document is cheap (undo snapshots, handing a frozen copy to the +//! renderer) and edits copy only what they touch (`Arc::make_mut`). //! //! Positions ([`Pos`]) name a story, a [`Path`] to a paragraph and a byte offset in it. Paths //! compare in document order. @@ -100,8 +100,12 @@ pub struct Document { /// Properties of the last section (OOXML's body-level `w:sectPr`). pub last_section: SectionProps, pub parts: BTreeMap, - pub styles: StyleSheet, - pub numbering: Numbering, + /// Shared, like the blocks: an undo step copies it only when a command changes it (a full + /// copy is kilobytes a style, so typing into a document with hundreds of styles cost megabytes + /// a step). + pub styles: Arc, + /// Shared like [`Document::styles`]. + pub numbering: Arc, pub comments: BTreeMap, pub revisions: Vec, pub settings: Settings, diff --git a/crates/doc/src/tests.rs b/crates/doc/src/tests.rs index 12189ac5a4..cca055a5e5 100644 --- a/crates/doc/src/tests.rs +++ b/crates/doc/src/tests.rs @@ -1,4 +1,5 @@ use super::*; +use std::sync::Arc; mod note_conversion; @@ -35,8 +36,8 @@ fn word_count_counts_each_chinese_and_japanese_character() { #[test] fn word_count_includes_numbered_list_labels_but_not_bullets() { let mut d = Document::new(); - let num_id = d.numbering.add_list(ListKind::Numbered); - let bul_id = d.numbering.add_list(ListKind::Bullet); + let num_id = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); + let bul_id = Arc::make_mut(&mut d.numbering).add_list(ListKind::Bullet); let mut p1 = Paragraph::with_text("Apple", CharProps::default()); p1.props.numbering = Some(props::NumRef { num: num_id, level: 0 }); @@ -60,9 +61,9 @@ fn word_count_includes_numbered_list_labels_but_not_bullets() { #[test] fn word_count_includes_numbered_labels_with_nothing_suffix() { let mut d = Document::new(); - let num_id = d.numbering.add_list(ListKind::Numbered); + let num_id = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); let aid = d.numbering.num(num_id).expect("list num exists").abstract_id; - let a = d.numbering.abstracts.iter_mut().find(|a| a.id == aid).expect("abstract num exists"); + let a = Arc::make_mut(&mut d.numbering).abstracts.iter_mut().find(|a| a.id == aid).expect("abstract num exists"); a.levels[0].suffix = numbering::LevelSuffix::Nothing; let mut p1 = Paragraph::with_text("Apple", CharProps::default()); @@ -81,9 +82,9 @@ fn word_count_includes_numbered_labels_with_nothing_suffix() { #[test] fn word_count_keeps_a_cjk_list_label_one_word() { let mut d = Document::new(); - let num_id = d.numbering.add_list(ListKind::Numbered); + let num_id = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); let aid = d.numbering.num(num_id).expect("list num exists").abstract_id; - let a = d.numbering.abstracts.iter_mut().find(|a| a.id == aid).expect("abstract num exists"); + let a = Arc::make_mut(&mut d.numbering).abstracts.iter_mut().find(|a| a.id == aid).expect("abstract num exists"); a.levels[0].format = section::NumFormat::ChineseCounting; a.levels[0].text = "第%1章".into(); a.levels[0].start = 12; @@ -187,7 +188,7 @@ fn parts_and_lists_after_the_largest_ids_take_free_ones() { assert_eq!(d.add_part(PartKind::Footer, Vec::new()), 3); assert!(parts.iter().all(|(k, p)| d.parts.get(k) == Some(p))); - let n = &mut d.numbering; + let n = Arc::make_mut(&mut d.numbering); n.add_list(ListKind::Bullet); n.add_list(ListKind::Bullet); n.abstracts[1].id = u32::MAX; @@ -229,11 +230,11 @@ fn same_content_sees_every_change() { assert!(!d.same_content(&e), "the formatting changed"); // The styles changed. let mut e = d.clone(); - e.styles.get_mut("Normal").unwrap().chr.bold = Some(true); + Arc::make_mut(&mut e.styles).get_mut("Normal").unwrap().chr.bold = Some(true); assert!(!d.same_content(&e), "a style changed"); // The lists changed. let mut e = d.clone(); - e.numbering.add_list(ListKind::Numbered); + Arc::make_mut(&mut e.numbering).add_list(ListKind::Numbered); assert!(!d.same_content(&e), "a list was added"); // The settings changed. let mut e = d.clone(); @@ -452,9 +453,9 @@ fn box_para(story: u32, id: u32) -> Blocks { fn adopting_relinks_lists_in_deeply_nested_tables() { // As #1339: a list paragraph 20 tables down takes the list it is brought in with. let mut ours = Document::from_text("ours"); - let our_list = ours.numbering.add_list(numbering::ListKind::Bullet); + let our_list = Arc::make_mut(&mut ours.numbering).add_list(numbering::ListKind::Bullet); let mut theirs = Document::from_text("theirs"); - let list = theirs.numbering.add_list(numbering::ListKind::Bullet); + let list = Arc::make_mut(&mut theirs.numbering).add_list(numbering::ListKind::Bullet); assert_eq!(list, our_list); let mut p = Paragraph::with_text("item", CharProps::default()); p.props.numbering = Some(props::NumRef { num: list, level: 0 }); diff --git a/crates/docbin/src/lib.rs b/crates/docbin/src/lib.rs index 856a307c24..0c0f839d52 100644 --- a/crates/docbin/src/lib.rs +++ b/crates/docbin/src/lib.rs @@ -25,6 +25,7 @@ mod stsh; mod table; use std::io::{Read, Seek}; +use std::sync::Arc; use wordcraft_doc::link::hyperlink_target; use wordcraft_doc::para::InlineObject; @@ -249,9 +250,9 @@ pub fn read_with_password(bytes: &[u8], password: Option<&str>) -> Result { if !sheet.styles.iter().any(|s| s.quick) { sheet.add_default_gallery(); } - self.doc.styles = sheet; + self.doc.styles = Arc::new(sheet); Ok(()) } @@ -298,8 +299,8 @@ impl Reader<'_> { a.levels = levels; } } - self.doc.numbering.abstracts = abstracts; - self.doc.numbering.nums = nums; + Arc::make_mut(&mut self.doc.numbering).abstracts = abstracts; + Arc::make_mut(&mut self.doc.numbering).nums = nums; Ok(()) } diff --git a/crates/docx/tests/it/bidi_runs.rs b/crates/docx/tests/it/bidi_runs.rs index 067918f26b..96d7b05e0b 100644 --- a/crates/docx/tests/it/bidi_runs.rs +++ b/crates/docx/tests/it/bidi_runs.rs @@ -7,6 +7,7 @@ //! in a right-to-left paragraph. use std::io::Read; +use std::sync::Arc; use wordcraft_doc::props::CharProps; use wordcraft_doc::{Document, Paragraph, para_block}; @@ -189,6 +190,6 @@ fn a_language_the_run_already_has_is_kept() { #[test] fn an_inherited_bidi_language_is_not_repeated() { let mut d = doc_of("سلام WordCraft دنیا", true, arial()); - d.styles.default_chr.lang_bidi = Some("fa-IR".into()); + Arc::make_mut(&mut d.styles).default_chr.lang_bidi = Some("fa-IR".into()); assert_eq!(langs(&d), vec![(true, None), (false, None), (true, None)]); } diff --git a/crates/docx/tests/it/default_size.rs b/crates/docx/tests/it/default_size.rs index b6c4c6d143..65d5a1f9fa 100644 --- a/crates/docx/tests/it/default_size.rs +++ b/crates/docx/tests/it/default_size.rs @@ -2,6 +2,7 @@ //! reopen. An unset default is resolved as `DEFAULT_FONT_SIZE`; the writer must say so, because //! the reader reads an omitted size as 10 pt. use std::io::{Read, Write}; +use std::sync::Arc; use wordcraft_doc::Document; use wordcraft_doc::props::{CharProps, DEFAULT_FONT_SIZE}; fn resolved(d: &Document) -> f32 { @@ -19,7 +20,7 @@ fn defaults(xml: &str) -> &str { #[test] fn an_unset_default_size_keeps_its_effective_size_across_save_and_reopen() { let mut d = Document::new(); - d.styles.default_chr.size = None; + Arc::make_mut(&mut d.styles).default_chr.size = None; assert_eq!(resolved(&d), DEFAULT_FONT_SIZE); let bytes = wordcraft_docx::write(&d).unwrap(); assert!(defaults(&styles_xml(&bytes)).contains(r#""#)); @@ -33,7 +34,7 @@ fn an_unset_default_size_keeps_its_effective_size_across_save_and_reopen() { fn an_explicit_default_size_is_written_as_it_is() { for size in [8.0, 10.0, 11.0, 12.5, 18.0] { let mut d = Document::new(); - d.styles.default_chr.size = Some(size); + Arc::make_mut(&mut d.styles).default_chr.size = Some(size); let bytes = wordcraft_docx::write(&d).unwrap(); assert_eq!(resolved(&wordcraft_docx::read(&bytes).unwrap()), size); } @@ -59,8 +60,8 @@ fn an_unset_default_size_does_not_take_a_styles_size() { // The written default is the resolver's own fallback, not what the Normal style resolves to, // so a style that sets a size keeps it and one that does not still falls back to the default. let mut d = Document::new(); - d.styles.default_chr.size = None; - if let Some(normal) = d.styles.get_mut("Normal") { + Arc::make_mut(&mut d.styles).default_chr.size = None; + if let Some(normal) = Arc::make_mut(&mut d.styles).get_mut("Normal") { normal.chr.size = Some(14.0); } let reopened = wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap(); diff --git a/crates/docx/tests/it/fixtures.rs b/crates/docx/tests/it/fixtures.rs index b7ef00c704..f6f0b50441 100644 --- a/crates/docx/tests/it/fixtures.rs +++ b/crates/docx/tests/it/fixtures.rs @@ -1,6 +1,7 @@ //! Hand-written minimal WordprocessingML fixtures (written for these tests from the spec). use std::io::Write; +use std::sync::Arc; use wordcraft_doc::fill::Fill; use wordcraft_doc::para::{Anchor, FloatAlign, NoteKind, ShapeKind, Wrap}; @@ -252,7 +253,7 @@ fn omitted_paragraph_defaults_can_be_edited() { format!(r#""#); let mut d = wordcraft_docx::read(&docx("", &[("rId1", "styles", "styles.xml")], &[("word/styles.xml", &styles)])).unwrap(); assert!(!d.styles.default_para_present); - d.styles.default_para.space_after = Some(12.0); + Arc::make_mut(&mut d.styles).default_para.space_after = Some(12.0); let again = wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap(); assert!(again.styles.default_para_present); assert_eq!(again.styles.default_para.space_after, Some(12.0)); diff --git a/crates/docx/tests/it/glossary.rs b/crates/docx/tests/it/glossary.rs index d784375947..7b234542a0 100644 --- a/crates/docx/tests/it/glossary.rs +++ b/crates/docx/tests/it/glossary.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use std::io::{Read, Write}; +use std::sync::Arc; use wordcraft_doc::{Block, BuildingBlock, CharProps, Document, Paragraph, para_block}; @@ -191,7 +192,7 @@ fn glossary_entries_are_read_into_the_model() { fn edited_glossary_is_written_from_the_model() { let mut doc = wordcraft_docx::read(&with_glossary()).unwrap(); doc.glossary[0].name = "Sign-off".into(); - let num = doc.numbering.add_list(wordcraft_doc::ListKind::Bullet); + let num = Arc::make_mut(&mut doc.numbering).add_list(wordcraft_doc::ListKind::Bullet); let mut listed = Paragraph::with_text("Second line", CharProps::default()); listed.props.numbering = Some(wordcraft_doc::props::NumRef { num, level: 0 }); doc.glossary.push(BuildingBlock { diff --git a/crates/docx/tests/it/passthrough.rs b/crates/docx/tests/it/passthrough.rs index cedb8d12ac..6a04043ed6 100644 --- a/crates/docx/tests/it/passthrough.rs +++ b/crates/docx/tests/it/passthrough.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use std::io::{Read, Write}; +use std::sync::Arc; const W: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; const R_NS: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; @@ -272,7 +273,7 @@ fn comment_references_use_the_documents_annotation_reference_style() { assert!(!body.contains(r#"w:val="CommentReference""#), "{body}"); // A document without that style keeps the English id. let mut doc = wordcraft_doc::Document::new(); - doc.styles.styles.retain(|s| !s.name.eq_ignore_ascii_case("annotation reference")); + Arc::make_mut(&mut doc.styles).styles.retain(|s| !s.name.eq_ignore_ascii_case("annotation reference")); let rebuilt = wordcraft_docx::read(&comments_package(true)).unwrap(); doc.body = rebuilt.body.clone(); doc.parts = rebuilt.parts.clone(); @@ -314,7 +315,7 @@ fn list_identities_survive_and_a_copied_definition_gets_none() { // A copy of the definition (as the engine makes when restarting a list) doesn't reuse the nsid. let mut copy = doc.numbering.abstracts[0].clone(); copy.id = 7; - doc.numbering.abstracts.push(copy); + Arc::make_mut(&mut doc.numbering).abstracts.push(copy); let n = text(&save(&doc), "word/numbering.xml"); assert_eq!(n.matches("1F2E3D4C").count(), 1, "{n}"); } diff --git a/crates/docx/tests/it/roundtrip.rs b/crates/docx/tests/it/roundtrip.rs index ddd674105d..13018aeac2 100644 --- a/crates/docx/tests/it/roundtrip.rs +++ b/crates/docx/tests/it/roundtrip.rs @@ -249,7 +249,7 @@ fn every_para_prop_round_trips() { #[test] fn styles_and_lists_round_trip() { let mut d = Document::new(); - d.styles.upsert(Style { + Arc::make_mut(&mut d.styles).upsert(Style { id: "MyStyle".into(), name: "My Style".into(), kind: StyleKind::Paragraph, @@ -266,7 +266,7 @@ fn styles_and_lists_round_trip() { default: false, table: None, }); - d.styles.upsert(Style { + Arc::make_mut(&mut d.styles).upsert(Style { id: "MyStyleChar".into(), name: "My Style Char".into(), kind: StyleKind::Character, @@ -275,10 +275,10 @@ fn styles_and_lists_round_trip() { hidden: true, ..Default::default() }); - let bullets = d.numbering.add_list(ListKind::Bullet); - let nums = d.numbering.add_list(ListKind::Legal); - let outline = d.numbering.add_list(ListKind::Outline); - if let Some(n) = d.numbering.nums.iter_mut().find(|n| n.id == outline) { + let bullets = Arc::make_mut(&mut d.numbering).add_list(ListKind::Bullet); + let nums = Arc::make_mut(&mut d.numbering).add_list(ListKind::Legal); + let outline = Arc::make_mut(&mut d.numbering).add_list(ListKind::Outline); + if let Some(n) = Arc::make_mut(&mut d.numbering).nums.iter_mut().find(|n| n.id == outline) { n.start_overrides.push((0, 4)); } let mut ps = Vec::new(); @@ -568,7 +568,7 @@ fn table_style_formatting_round_trips() { band_size: Some(3), ..Default::default() }; - d.styles.upsert(Style { + Arc::make_mut(&mut d.styles).upsert(Style { id: "Whole".into(), name: "Whole".into(), kind: StyleKind::Table, @@ -621,7 +621,7 @@ fn every_table_style_part_round_trips() { }; st.set_table_part(part, f); } - d.styles.upsert(st.clone()); + Arc::make_mut(&mut d.styles).upsert(st.clone()); let r = rt(&d); let got = r.styles.get("Parts").unwrap(); for part in TablePart::ALL { @@ -1709,7 +1709,7 @@ fn kitchen_sink() -> Document { let mut d = Document::new(); let png = tiny_png(); let key = d.add_media(png, "png"); - let num = d.numbering.add_list(ListKind::Numbered); + let num = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); let h = d.add_part(PartKind::Header, vec![para_block(Paragraph::with_text("Head", CharProps::default()))]); let f = d.add_part(PartKind::Footnote, vec![para_block(Paragraph::with_text("Note", CharProps::default()))]); let c = d.add_part(PartKind::Comment, vec![para_block(Paragraph::with_text("Comment", CharProps::default()))]); @@ -1941,10 +1941,10 @@ fn list_level_overrides_round_trip() { use wordcraft_doc::numbering::{Counters, Level}; use wordcraft_doc::section::NumFormat; let mut d = Document::new(); - let num = d.numbering.add_list(ListKind::Numbered); - let restart = d.numbering.restart(num).unwrap(); + let num = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); + let restart = Arc::make_mut(&mut d.numbering).restart(num).unwrap(); let own = Level { format: NumFormat::DecimalZero, text: "%1.%2".into(), indent: 26.5, hanging: 26.5, ..Level::default() }; - if let Some(n) = d.numbering.nums.iter_mut().find(|n| n.id == restart) { + if let Some(n) = Arc::make_mut(&mut d.numbering).nums.iter_mut().find(|n| n.id == restart) { n.level_overrides = vec![(1, own.clone())]; } let back = wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap(); diff --git a/crates/docx/tests/it/wordml2003_write.rs b/crates/docx/tests/it/wordml2003_write.rs index 039fd65f1b..18bb5ee0a3 100644 --- a/crates/docx/tests/it/wordml2003_write.rs +++ b/crates/docx/tests/it/wordml2003_write.rs @@ -32,7 +32,7 @@ fn sample() -> Document { d.core.title = "Garden & more".into(); d.core.creator = "Ada".into(); let key = d.add_media(tiny_png(), "png"); - let num = d.numbering.add_list(ListKind::Numbered); + let num = Arc::make_mut(&mut d.numbering).add_list(ListKind::Numbered); let head = d.add_part(PartKind::Header, vec![para_block(Paragraph::with_text("Head", CharProps::default()))]); let foot = d.add_part(PartKind::Footnote, vec![para_block(Paragraph::with_text("Sown in May.", CharProps::default()))]); let com = d.add_part(PartKind::Comment, vec![para_block(Paragraph::with_text("Too short?", CharProps::default()))]); diff --git a/crates/engine/src/cmd/citations/generated.rs b/crates/engine/src/cmd/citations/generated.rs index 44a3dd8e08..5600533c58 100644 --- a/crates/engine/src/cmd/citations/generated.rs +++ b/crates/engine/src/cmd/citations/generated.rs @@ -6,6 +6,7 @@ use super::{ Block, CharProps, CmdError, CmdResult, Document, FieldCode, InlineObject, Paragraph, Path, Pos, Session, SortKey, StoryRef, TabAlign, TabLeader, TabStop, Value, delete_selection, heading_letter, p, sel_result, sort_key, }; +use std::sync::Arc; /// The TOC field for `references.tableOfFigures` (ECMA-376 §17.16.5.68): `\c "