From 16f0b6ed2523fc15cbbdb2eabb1ff6dd1a7ba908 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 13:42:25 +0000 Subject: [PATCH] Gap 19: ODT headers and footers keep their drawings on save Review of the objects commit found two gaps. - The header and footer reader wasn't given the package's entry list, so embedded objects there were still lost on open. It now shares the body reader's list and manifest, borrowed rather than copied. - Headers and footers were flattened for writing without drawings, so shapes, text boxes, charts and kept objects in them were dropped on save. They now go out as in the body. The full LibreOffice round-trip harness shows no change on the other test documents. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GBVQLyDnrG8Tn1yDQcUabC --- crates/formats/src/odt/body.rs | 10 +++++----- crates/formats/src/odt/import.rs | 6 +++--- crates/formats/src/odt/objects.rs | 4 ++-- crates/formats/src/odt/package.rs | 3 ++- crates/formats/src/odt/page_parts.rs | 10 +++++++++- crates/formats/src/odt/tests_more/objects.rs | 21 ++++++++++++++++++++ docs/file-format-parity.md | 3 ++- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/crates/formats/src/odt/body.rs b/crates/formats/src/odt/body.rs index 3678f3d7ee..f6619bfb4b 100644 --- a/crates/formats/src/odt/body.rs +++ b/crates/formats/src/odt/body.rs @@ -27,8 +27,8 @@ pub(super) struct Body<'a> { styles: &'a Styles, zip: &'a mut dyn FnMut(&str) -> Option>, /// The package's entry names and its manifest's media types, for embedded objects. - pub(super) entries: Vec, - pub(super) manifest: HashMap, + pub(super) entries: &'a [String], + pub(super) manifest: Option<&'a HashMap>, containers: Vec>, fmt: Vec, para: Option, @@ -121,8 +121,8 @@ impl<'a> Body<'a> { Body { styles, zip, - entries: Vec::new(), - manifest: HashMap::new(), + entries: &[], + manifest: None, containers: vec![Vec::new()], fmt: Vec::new(), para: None, @@ -759,7 +759,7 @@ impl Body<'_> { } } if fr.4.is_none() && fr.5.is_none() { - fr.6 = super::objects::gather(href, ole, &self.entries, &self.manifest, &mut *self.zip); + fr.6 = super::objects::gather(href, ole, self.entries, self.manifest, &mut *self.zip); // A replacement picture read before the object. if let Some(o) = &mut fr.6 { o.replacement = fr.2.clone().map(std::sync::Arc::new); diff --git a/crates/formats/src/odt/import.rs b/crates/formats/src/odt/import.rs index 634fa99fe4..c5310fbde1 100644 --- a/crates/formats/src/odt/import.rs +++ b/crates/formats/src/odt/import.rs @@ -432,10 +432,10 @@ fn parse_tracked(bytes: &[u8]) -> Result { // A frame may point at any entry, as often as it likes: `Pictures` keeps that bounded. pictures.get(&mut z, path) }; - let mut parts = styles_xml.as_deref().map(|x| page_parts::read(x, &styles, &mut pages, &mut fetch)).unwrap_or_default(); + let objects = (entries.as_slice(), &manifest); + let mut parts = styles_xml.as_deref().map(|x| page_parts::read(x, &styles, &mut pages, &mut fetch, objects)).unwrap_or_default(); let mut body = Body::new(&styles, &pages, &mut fetch); - body.entries = entries; - body.manifest = manifest; + (body.entries, body.manifest) = (objects.0, Some(objects.1)); events(&content, |ev| body.feed(ev)); let mut blocks = body.take_blocks(); let (mut named_odt, mut named) = custom_styles(&mut blocks, &styles, Vec::new()); diff --git a/crates/formats/src/odt/objects.rs b/crates/formats/src/odt/objects.rs index 897608e1d0..31dbd4e159 100644 --- a/crates/formats/src/odt/objects.rs +++ b/crates/formats/src/odt/objects.rs @@ -43,7 +43,7 @@ pub(in crate::odt) fn gather( href: &str, ole: bool, entries: &[String], - manifest: &HashMap, + manifest: Option<&HashMap>, fetch: &mut dyn FnMut(&str) -> Option>, ) -> Option { let path = inside(href)?; @@ -64,7 +64,7 @@ pub(in crate::odt) fn gather( if files.is_empty() { return None; } - let mime = manifest.get(&format!("{path}/")).or_else(|| manifest.get(path)).cloned().unwrap_or_else(|| { + let mime = manifest.and_then(|m| m.get(&format!("{path}/")).or_else(|| m.get(path))).cloned().unwrap_or_else(|| { if ole { OLE_MIME.to_string() } else { diff --git a/crates/formats/src/odt/package.rs b/crates/formats/src/odt/package.rs index da021078f9..ee42d73063 100644 --- a/crates/formats/src/odt/package.rs +++ b/crates/formats/src/odt/package.rs @@ -238,7 +238,8 @@ fn page_parts(doc: &Document, sec: &SectionProps, w: &mut Writer) -> PageParts { let blocks = model::part_flow( doc, &part.blocks, - model::FlowOpts { fields: true, lists: true, chars: true, paras: true, ..Default::default() }, + // Pictures, shapes, text boxes, charts and objects too, as in the body. + model::FlowOpts { fields: true, lists: true, chars: true, paras: true, boxes: true, ..Default::default() }, ); w.blocks(&blocks, &mut body, 0); } diff --git a/crates/formats/src/odt/page_parts.rs b/crates/formats/src/odt/page_parts.rs index 2791a21a56..82e6219489 100644 --- a/crates/formats/src/odt/page_parts.rs +++ b/crates/formats/src/odt/page_parts.rs @@ -66,7 +66,14 @@ impl Parts { /// Read each visible header and footer of the master pages in `pages`, and name it on its master /// page's section as part id 1, 2, … in order, so the sections the body starts carry them. /// [`add`] puts those parts into the document. -pub(super) fn read(xml: &[u8], styles: &Styles, pages: &mut Pages, fetch: &mut dyn FnMut(&str) -> Option>) -> Parts { +/// `objects`: the package's entry names and manifest media types, for embedded objects. +pub(super) fn read( + xml: &[u8], + styles: &Styles, + pages: &mut Pages, + fetch: &mut dyn FnMut(&str) -> Option>, + objects: (&[String], &std::collections::HashMap), +) -> Parts { let mut found: Vec<(String, Slot, Vec)> = Vec::new(); let mut master: Option = None; // The slot being read, its nesting depth and whether it is kept. @@ -116,6 +123,7 @@ pub(super) fn read(xml: &[u8], styles: &Styles, pages: &mut Pages, fetch: &mut d for (name, kind, evs) in found { let mut reader = Body::new(styles, &none, fetch); reader.in_text = true; + (reader.entries, reader.manifest) = (objects.0, Some(objects.1)); for ev in evs { reader.feed(ev); } diff --git a/crates/formats/src/odt/tests_more/objects.rs b/crates/formats/src/odt/tests_more/objects.rs index 57e2b1bd12..97909f721d 100644 --- a/crates/formats/src/odt/tests_more/objects.rs +++ b/crates/formats/src/odt/tests_more/objects.rs @@ -132,3 +132,24 @@ fn a_flat_document_holds_its_objects_and_their_pictures_inline() { let ole = crate::odt::tests::entry(&saved, "content.xml"); assert!(ole.contains("Logo "# + ); + let content = format!( + r#"Body."# + ); + let pkg = zip_of(&[ + ("META-INF/manifest.xml", MANIFEST.as_bytes()), + ("content.xml", content.as_bytes()), + ("styles.xml", styles.as_bytes()), + ("Object 1/content.xml", SHEET.as_bytes()), + ("ObjectReplacements/Object 1", METAFILE), + ]); + let saved = export(&import(&pkg).unwrap()).unwrap(); + let z = zip::ZipArchive::new(std::io::Cursor::new(&saved)).unwrap(); + assert!(z.file_names().any(|n| n.ends_with("/content.xml") && n.starts_with("Object M")), "{:?}", z.file_names().collect::>()); + assert!(entry(&saved, "styles.xml").contains(" **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** trivial (ODT embedded spreadsheets and OLE objects kept whole instead of lost; flat ODT sub-documents valid `office:document`s); previously trivial (ODT charts in drawing groups drawn on open); previously trivial (ODT charts opened and saved as editable charts; flat ODT sub-documents no longer end the body); previously trivial (ODT leading read as an at-least spacing); previously trivial (ODT tracked formatting changes; changed regions named by `xml:id`); previously trivial (ODT drop caps; tab stops no longer drop a paragraph's background); previously trivial (flat ODT `.fodt` opened and saved; ODT fidelity re-estimated at ~80%); previously trivial (ODT page settings; a footnote no longer takes a footer's text); previously trivial (ODT dates in their formats, sequences, chapters and references); previously trivial (ODT bookmarks over text); previously trivial (ODT tables of contents and indexes; ODT fidelity re-estimated); previously trivial (ODT polygons, polylines and paths); previously trivial (ODT arrowheads, dashes and gradients); previously trivial (ODT groups of shapes and pictures); previously trivial (ODT rotated and mirrored shapes and pictures); previously trivial (ODT drawing shapes read and written); previously trivial (ODT text box frames keep their line, fill, padding and fit); previously trivial (ODT Asian and complex-script fonts); previously trivial (ODT right-to-left paragraphs); previously trivial (ODT text boxes kept); previously trivial (ODT unnumbered headings detached from the outline); previously trivial (ODT contextual spacing and layout settings); previously trivial (ODT custom character styles kept); previously trivial (ODT list level positions); previously trivial (ODT custom paragraph styles kept); previously trivial (ODT equations written as formula objects); previously trivial (ODT numbered headings written as headings); previously trivial (ODT floating pictures); previously trivial (ODT paragraph borders and background); previously trivial (ODT heading numbering); previously trivial (ODT font-face names); previously trivial (ODT table cell borders and padding); previously trivial (ODT heading, title, quote and preformatted styles keep their look); previously trivial (ODT tab stops read and written); previously trivial (ODT character formatting read and written); previously trivial (ODT lists keep their number formats, bullets and identity); previously trivial (ODT comments read and written); previously trivial (ODT footnotes and endnotes read and written); previously trivial (ODT headers, footers and text fields written; fields read); previously trivial (ODT paragraph spacing, indents and line spacing read and written); previously trivial (a paragraph naming an undefined style is formatted as Normal, as in Word, #5077); previously trivial (a hyperlinked TOC writes its page numbers as `PAGEREF _Toc… \h` fields, as Word does, #5097); previously trivial (HTML import ends the empty comments and at once, #5040); previously trivial (chart and SmartArt text the font lacks is drawn in a fallback face, so Chinese labels no longer show boxes, #4720); previously trivial (bibliography sources keep a corporate author (`b:Corporate`): read, written, cited and listed whole, #4848); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (note conversion preserves content with an ordered reference/run walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (built-in heading styles have their outline level without `w:outlineLvl`, #4696); previously trivial (DOCX comment ranges inside footnotes and endnotes are kept on read, #4548); previously trivial (RTF import and export keep Windows and Enhanced Metafile pictures (`\wmetafile`, `\emfblip`), #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (DOCX parts stored under another casing keep their embedded fonts through a save, and a private save of a kept glossary escapes the attribute values it rewrites; previously trivial: the `.doc` reader logs what its copy budget leaves out, once a kind per document; importers bound hostile expansion: font names, links, ODS repeats, LaTeX wrappers, Markdown delimiters, #3692/#3713; previously trivial: repeated DOCX style IDs: save/reopen regression; previously trivial: Markdown code spans keep line-broken text literal, #3738; previously trivial: Arabic list labels: `arabicAlpha` and `arabicAbjad` draw the 28 Arabic letters (alphabetical and abjad order), doubling past the last letter; Hebrew, Hindi, Thai and Vietnamese still show decimal (`doc::section::tests::arabic_letters_count_in_both_orders`); previously trivial: localized TOC style ids read as the TOC styles, and a TOC in columns keeps its layout through Update Table, #3889; previously trivial: cu20we: outline caps; previously trivial: a relationship the markup names without the prefix is read as it wrote: an unqualified `id` on an element that carries a relationship names that relationship, and the writer keeps the attribute with the id the new package holds the part under; an ordinary `id` — a VML shape's, a drawing's — names no part; previously trivial: the reader takes the relationship ids the markup refers to from the relationship attributes of the main document it has already parsed, so a document with many relationships opens in the time its part takes, #3411; previously trivial: a table cell span from a `.json` file stays within 63 columns through Insert Column and Merge Cells, #3637; previously trivial: the file commands that change the document are checked on a protected document, the protection of a protected document needs an explicit stop, the session author can't change through `file.setAuthor` under protection (the CLI's `--author` uses the same protection check), and the post-check compares the settings and the metadata; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: `majorBidi`/`minorBidi` theme fonts follow the settings' theme font language, as measured in Word; previously trivial: Word signature lines are kept on save, still signable; previously trivial: Times New Roman keeps Liberation Serif under the roman-font rule; previously trivial: shape gradients and transparency, #1500; previously trivial: a folder that keeps no Unix modes, such as a NAS share mounted by GVFS, still takes a save, #3003; previously trivial: a text frame in the paragraph defaults frames nothing, #2360; previously trivial: table style band sizes written, a style without them unbanded as in Word, #3415; previously trivial: HTML export writes ruby (furigana, phonetic guides) and HTML import reads it back, #1681; previously trivial: RTF import decodes the East Asian multibyte code pages, #1653; previously trivial: PDF export: lam-alef drawn as two glyphs at one x extracts in reading order, #2310; previously trivial: native .json stores pictures and kept parts as base64, #586 follow-up; previously trivial: ODT headers and footers read from each master page, #863; previously trivial: RTF import compares heading and code formatting with the file's stylesheet; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: Word XML and web pages saved as `.doc` open by what they hold, #2853; previously trivial: a VML shape in a header cell is always placed on the page, user-checked in Word for Mac, #1775; previously trivial: `w:sym` characters keep their symbol font when the text's font changes and are written back as `w:sym`, #2036; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: two lines in one and horizontal in vertical (`w:eastAsianLayout`) kept on save; previously trivial: PDF outline targets follow leading page breaks; previously trivial: positional tabs `w:ptab` read and kept on save, #2367; previously trivial: DOCX: mixed right-to-left runs split and marked `w:rtl` with their language, #1543; previously trivial: `w:doNotTrackMoves` round-trips and turns move tracking off; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: DOCX glossary documents (building blocks) read, kept and written; previously trivial: cached field results keep their visible run formatting; previously trivial: cached REF results retain hyperlink destinations; previously trivial: CLI conversions can explicitly refresh fields; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: a pasted HTML `
` is an indented paragraph, not the Quote style, #1270); previously trivial: every table style part round-trips, #714; previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** trivial (ODT headers and footers keep their shapes, text boxes, charts and embedded objects on save); previously trivial (ODT embedded spreadsheets and OLE objects kept whole instead of lost; flat ODT sub-documents valid `office:document`s); previously trivial (ODT charts in drawing groups drawn on open); previously trivial (ODT charts opened and saved as editable charts; flat ODT sub-documents no longer end the body); previously trivial (ODT leading read as an at-least spacing); previously trivial (ODT tracked formatting changes; changed regions named by `xml:id`); previously trivial (ODT drop caps; tab stops no longer drop a paragraph's background); previously trivial (flat ODT `.fodt` opened and saved; ODT fidelity re-estimated at ~80%); previously trivial (ODT page settings; a footnote no longer takes a footer's text); previously trivial (ODT dates in their formats, sequences, chapters and references); previously trivial (ODT bookmarks over text); previously trivial (ODT tables of contents and indexes; ODT fidelity re-estimated); previously trivial (ODT polygons, polylines and paths); previously trivial (ODT arrowheads, dashes and gradients); previously trivial (ODT groups of shapes and pictures); previously trivial (ODT rotated and mirrored shapes and pictures); previously trivial (ODT drawing shapes read and written); previously trivial (ODT text box frames keep their line, fill, padding and fit); previously trivial (ODT Asian and complex-script fonts); previously trivial (ODT right-to-left paragraphs); previously trivial (ODT text boxes kept); previously trivial (ODT unnumbered headings detached from the outline); previously trivial (ODT contextual spacing and layout settings); previously trivial (ODT custom character styles kept); previously trivial (ODT list level positions); previously trivial (ODT custom paragraph styles kept); previously trivial (ODT equations written as formula objects); previously trivial (ODT numbered headings written as headings); previously trivial (ODT floating pictures); previously trivial (ODT paragraph borders and background); previously trivial (ODT heading numbering); previously trivial (ODT font-face names); previously trivial (ODT table cell borders and padding); previously trivial (ODT heading, title, quote and preformatted styles keep their look); previously trivial (ODT tab stops read and written); previously trivial (ODT character formatting read and written); previously trivial (ODT lists keep their number formats, bullets and identity); previously trivial (ODT comments read and written); previously trivial (ODT footnotes and endnotes read and written); previously trivial (ODT headers, footers and text fields written; fields read); previously trivial (ODT paragraph spacing, indents and line spacing read and written); previously trivial (a paragraph naming an undefined style is formatted as Normal, as in Word, #5077); previously trivial (a hyperlinked TOC writes its page numbers as `PAGEREF _Toc… \h` fields, as Word does, #5097); previously trivial (HTML import ends the empty comments and at once, #5040); previously trivial (chart and SmartArt text the font lacks is drawn in a fallback face, so Chinese labels no longer show boxes, #4720); previously trivial (bibliography sources keep a corporate author (`b:Corporate`): read, written, cited and listed whole, #4848); previously trivial (custom-property bookmark-link preservation, #4767); previously trivial (note conversion preserves content with an ordered reference/run walk, #4750); previously trivial (DOCX comment-ID save lookups); previously trivial (built-in heading styles have their outline level without `w:outlineLvl`, #4696); previously trivial (DOCX comment ranges inside footnotes and endnotes are kept on read, #4548); previously trivial (RTF import and export keep Windows and Enhanced Metafile pictures (`\wmetafile`, `\emfblip`), #4259); previously trivial (#4658 bounds nested Markdown reference-label work); previously trivial (HTML import keeps preformatted lines with CR or CRLF input newlines, #4810); previously trivial (DOCX parts stored under another casing keep their embedded fonts through a save, and a private save of a kept glossary escapes the attribute values it rewrites; previously trivial: the `.doc` reader logs what its copy budget leaves out, once a kind per document; importers bound hostile expansion: font names, links, ODS repeats, LaTeX wrappers, Markdown delimiters, #3692/#3713; previously trivial: repeated DOCX style IDs: save/reopen regression; previously trivial: Markdown code spans keep line-broken text literal, #3738; previously trivial: Arabic list labels: `arabicAlpha` and `arabicAbjad` draw the 28 Arabic letters (alphabetical and abjad order), doubling past the last letter; Hebrew, Hindi, Thai and Vietnamese still show decimal (`doc::section::tests::arabic_letters_count_in_both_orders`); previously trivial: localized TOC style ids read as the TOC styles, and a TOC in columns keeps its layout through Update Table, #3889; previously trivial: cu20we: outline caps; previously trivial: a relationship the markup names without the prefix is read as it wrote: an unqualified `id` on an element that carries a relationship names that relationship, and the writer keeps the attribute with the id the new package holds the part under; an ordinary `id` — a VML shape's, a drawing's — names no part; previously trivial: the reader takes the relationship ids the markup refers to from the relationship attributes of the main document it has already parsed, so a document with many relationships opens in the time its part takes, #3411; previously trivial: a table cell span from a `.json` file stays within 63 columns through Insert Column and Merge Cells, #3637; previously trivial: the file commands that change the document are checked on a protected document, the protection of a protected document needs an explicit stop, the session author can't change through `file.setAuthor` under protection (the CLI's `--author` uses the same protection check), and the post-check compares the settings and the metadata; previously trivial: a document with many content controls saves in the time the controls take, #13; previously trivial: `majorBidi`/`minorBidi` theme fonts follow the settings' theme font language, as measured in Word; previously trivial: Word signature lines are kept on save, still signable; previously trivial: Times New Roman keeps Liberation Serif under the roman-font rule; previously trivial: shape gradients and transparency, #1500; previously trivial: a folder that keeps no Unix modes, such as a NAS share mounted by GVFS, still takes a save, #3003; previously trivial: a text frame in the paragraph defaults frames nothing, #2360; previously trivial: table style band sizes written, a style without them unbanded as in Word, #3415; previously trivial: HTML export writes ruby (furigana, phonetic guides) and HTML import reads it back, #1681; previously trivial: RTF import decodes the East Asian multibyte code pages, #1653; previously trivial: PDF export: lam-alef drawn as two glyphs at one x extracts in reading order, #2310; previously trivial: native .json stores pictures and kept parts as base64, #586 follow-up; previously trivial: ODT headers and footers read from each master page, #863; previously trivial: RTF import compares heading and code formatting with the file's stylesheet; previously trivial: text typed into a content control takes the control's formatting; localized placeholder styles recognised; previously trivial: forms protection keeps its content controls: no inserting, Properties or Remove Content Control; previously trivial: Word XML and web pages saved as `.doc` open by what they hold, #2853; previously trivial: a VML shape in a header cell is always placed on the page, user-checked in Word for Mac, #1775; previously trivial: `w:sym` characters keep their symbol font when the text's font changes and are written back as `w:sym`, #2036; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: two lines in one and horizontal in vertical (`w:eastAsianLayout`) kept on save; previously trivial: PDF outline targets follow leading page breaks; previously trivial: positional tabs `w:ptab` read and kept on save, #2367; previously trivial: DOCX: mixed right-to-left runs split and marked `w:rtl` with their language, #1543; previously trivial: `w:doNotTrackMoves` round-trips and turns move tracking off; previously trivial: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: DOCX glossary documents (building blocks) read, kept and written; previously trivial: cached field results keep their visible run formatting; previously trivial: cached REF results retain hyperlink destinations; previously trivial: CLI conversions can explicitly refresh fields; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: a pasted HTML `
` is an indented paragraph, not the Quote style, #1270); previously trivial: every table style part round-trips, #714; previously trivial: VML freeforms read and kept on save; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: `flowChartOr` draws its circle and cross spokes, as in Word; previously trivial: RTF: Mac OS Roman and DOS text, #1653; previously trivial: VML text boxes keep their internal margins, fit to their text and vertical alignment; hidden drawings not shown; previously trivial: PDF export draws horizontal and vertical lines inside groups, #1474; previously trivial: symbol-font text exported as Unicode, #776; previously trivial: text-box margins, Resize shape to fit text and freeforms in groups, #743; previously trivial: formatted paste from the system clipboard's HTML and RTF, #687; previously trivial: independent complex-script formatting inheritance and round-trip regression tests; previously trivial: import/export fixes for TXT, RTF, ODT, HTML and LaTeX, #433–#440; previously major: first version; every format Word reads or writes, measured from the readers and writers on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4, plus Word for Windows' extra formats Every format Word opens or saves, what WordCraft does with it, and how it's tested. Fidelity is the share of a typical real-world file's content and formatting that survives (read: shows correctly; @@ -279,6 +279,7 @@ Custom document properties retain their bookmark link targets through DOCX saves | Date | Change | Summary | |---|---|---| +| 2026-11-01 | trivial | ODT: shapes, text boxes, charts and embedded objects in headers and footers were dropped on save (header and footer text was written without drawings other than pictures), and embedded objects there were lost on open; both now as in the body (`odt::tests_more::objects::an_object_in_a_header_is_kept_too`) | | 2026-11-01 | trivial | Flat ODT: an inline sub-document (a chart since the chart change) lacked the `office:mimetype` an `office:document` must have, and the schema rejected the file; sub-documents now go inline with their media type, styles and settings, objects' replacement pictures inline rather than linked to files a flat document doesn't have, OLE objects as base64 (`odt::tests_more::objects::a_flat_document_holds_its_objects_and_their_pictures_inline`) | | 2026-11-01 | trivial | ODT: embedded objects WordCraft doesn't edit — spreadsheets and other sub-documents (`draw:object`), OLE files (`draw:object-ole`) — were lost on open when their replacement picture was in LibreOffice's own metafile format (as LibreOffice writes it); they are now kept whole with their files, media type and replacement picture, shown by that picture or a placeholder WordCraft draws, and saved back as the same objects (LibreOffice draws a round-tripped spreadsheet identically); DOCX gets the picture alone (`odt::tests_more::objects`, `odt_objects.rs`) | | 2026-11-01 | trivial | ODT: a chart that is a member of a drawing group (`draw:g`) is drawn on open instead of showing blank (`odt_charts.rs::a_chart_in_a_group_opens_drawn`) |