mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-19 02:10:15 -05:00
* #2853: Word XML, web pages and MHTML saved as .doc open by what they hold A Word 2003 XML Document named `.doc` went to the Word 97-2003 reader, which refused it ("Invalid CFB file"), and the window showed a blank page. Word XML (2003 or Flat OPC), a web page and a Single File Web Page (MHTML) under a `.doc`, `.docx` or `.rtf` name now open by what they hold, as in Word. MHTML is read for the first time (`.mht`/`.mhtml` too): the MIME parts are split and decoded, the page is imported as HTML in its charset and its pictures load from the other parts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #2853: leave MHTML to #1104, keep Word XML and web pages saved as .doc #1104 adds an MHT reader (`wordcraft_formats::mht`), so this branch drops its own MHTML reader and the `.mht`/`.mhtml` extensions instead of adding a second one. A Word XML Document or a web page under a `.doc`, `.docx` or `.rtf` name still opens by what it holds (the #2853 fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #2853: a web page's signature needs its tag name to end (`<htmlish>` is other XML) CodeRabbit: `starts_with("<html")` took XML with a root like `<htmlish>` under a `.doc` name for a web page, though XML that isn't a Word document stays an error. `<html` and `<!DOCTYPE html` now need white space, `>` or `/` after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #2853: rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
384d5ef307
commit
2d0169e718
3 files changed
+110
-15
No files matched your search
+62
-13
@@ -252,31 +252,75 @@ pub(crate) fn fit_pictures(doc: &mut Document) {
|
||||
|
||||
/// Does the file hold a different document format than its Word or RTF extension says? An RTF
|
||||
/// file saved as `.doc` (as accounting systems and report generators often save their
|
||||
/// documents), a `.docx` package renamed `.doc` or a Word 97-2003 file renamed `.docx`: Word opens
|
||||
/// documents), a `.docx` package renamed `.doc`, a Word 97-2003 file renamed `.docx`, a Word XML
|
||||
/// Document (2003 or Flat OPC) or a web page saved as `.doc` (#2853): Word opens
|
||||
/// each by what it holds, so they go through [`open_by_content`] instead of failing.
|
||||
fn signature_contradicts(ext: &str, bytes: &[u8]) -> bool {
|
||||
let held = if bytes.starts_with(ZIP_SIG) {
|
||||
"zip"
|
||||
} else if bytes.starts_with(OLE_SIG) {
|
||||
"ole"
|
||||
} else if bytes.starts_with(b"{\\rtf") {
|
||||
"rtf"
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let expected = match ext {
|
||||
"docx" | "docm" | "dotx" | "dotm" => "zip",
|
||||
"doc" | "dot" => "ole",
|
||||
"rtf" => "rtf",
|
||||
_ => return false,
|
||||
};
|
||||
let held = if bytes.starts_with(ZIP_SIG) {
|
||||
"zip"
|
||||
} else if bytes.starts_with(OLE_SIG) {
|
||||
"ole"
|
||||
} else if bytes.starts_with(b"{\\rtf") {
|
||||
"rtf"
|
||||
} else if is_word_xml(bytes) {
|
||||
"xml"
|
||||
} else if looks_like_web_page(bytes) {
|
||||
"html"
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
held != expected
|
||||
}
|
||||
|
||||
/// The bytes after a UTF-8 byte-order mark and leading white space.
|
||||
fn after_bom(bytes: &[u8]) -> &[u8] {
|
||||
let b = bytes.strip_prefix(&[0xEF, 0xBB, 0xBF][..]).unwrap_or(bytes);
|
||||
let start = b.iter().position(|c| !c.is_ascii_whitespace()).unwrap_or(b.len());
|
||||
b.get(start..).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Is this a Word XML Document: Word 2003 XML (`w:wordDocument`) or Flat OPC (`pkg:package`)?
|
||||
/// Only text that starts with markup is parsed (UTF-16 has its byte-order mark).
|
||||
fn is_word_xml(bytes: &[u8]) -> bool {
|
||||
let utf16 = bytes.starts_with(&[0xFF, 0xFE]) || bytes.starts_with(&[0xFE, 0xFF]);
|
||||
(utf16 || after_bom(bytes).starts_with(b"<")) && !matches!(wordcraft_docx::sniff_xml(bytes), wordcraft_docx::XmlKind::Other)
|
||||
}
|
||||
|
||||
/// Does the file start like a web page (`<!DOCTYPE html …>` or `<html …>`, after an XML
|
||||
/// declaration and comments)? Word's Save as Web Page and many report generators write HTML
|
||||
/// under a `.doc` name, and Word opens it as a web page.
|
||||
fn looks_like_web_page(bytes: &[u8]) -> bool {
|
||||
let head = wordcraft_formats::txt::decode(bytes.get(..bytes.len().min(4096)).unwrap_or(bytes)).to_ascii_lowercase();
|
||||
let mut t = head.trim_start_matches(['\u{FEFF}', ' ', '\t', '\r', '\n']);
|
||||
for _ in 0..64 {
|
||||
let skip = if t.starts_with("<!--") {
|
||||
t.find("-->").map(|i| i + 3)
|
||||
} else if t.starts_with("<?") {
|
||||
t.find("?>").map(|i| i + 2)
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
let Some(rest) = skip.and_then(|i| t.get(i..)) else { return false };
|
||||
t = rest.trim_start();
|
||||
}
|
||||
// The tag name ends there: `<htmlish>` is some other XML, not a web page.
|
||||
let tag = |name: &str| {
|
||||
t.strip_prefix(name).is_some_and(|rest| rest.is_empty() || rest.starts_with(|c: char| c.is_ascii_whitespace() || c == '>' || c == '/'))
|
||||
};
|
||||
tag("<!doctype html") || tag("<html")
|
||||
}
|
||||
|
||||
/// A file whose extension names no format WordCraft knows (`.m3u`, `.log`, `.ini`, none…),
|
||||
/// opened by what it holds, as Word does: a Word document or RTF file by its signature, any
|
||||
/// other text through the plain-text import with encoding detection (#569). Anything else is
|
||||
/// refused with a clear error rather than shown as garbage.
|
||||
/// opened by what it holds, as Word does: a Word document or RTF file by its signature, a Word
|
||||
/// XML Document by its root element, a web page under a Word or RTF name as one
|
||||
/// (#2853), any other text through the plain-text import with encoding detection (#569).
|
||||
/// Anything else is refused with a clear error rather than shown as garbage.
|
||||
fn open_by_content(
|
||||
name: &str,
|
||||
ext: &str,
|
||||
@@ -293,6 +337,11 @@ fn open_by_content(
|
||||
.map_err(|e| if needs_password(&e) || wrong_password(&e) { e } else { format!("{name}: not a document WordCraft can open ({e})") })?
|
||||
} else if bytes.starts_with(b"{\\rtf") {
|
||||
(wordcraft_formats::rtf::import(bytes)?, false)
|
||||
} else if is_word_xml(bytes) {
|
||||
(wordcraft_docx::read_xml(bytes).map_err(|e| format!("{name}: {e}"))?, false)
|
||||
} else if matches!(ext, "doc" | "dot" | "docx" | "docm" | "dotx" | "dotm" | "rtf") && looks_like_web_page(bytes) {
|
||||
let opened = crate::io_ext::open("html", bytes, page).unwrap_or_else(|| Err(format!("{name}: can't open web pages")));
|
||||
(opened?, false)
|
||||
} else if wordcraft_formats::txt::is_text(bytes) {
|
||||
(wordcraft_formats::txt::import_on(bytes, page), false)
|
||||
} else if ext.is_empty() {
|
||||
|
||||
@@ -5548,6 +5548,51 @@ fn documents_open_by_content_when_the_extension_is_wrong() {
|
||||
assert!(crate::io::open_bytes("broken.doc", b"\x00\x01not a document").is_err());
|
||||
}
|
||||
|
||||
/// #2853: a Word XML Document (Word 2003 XML or Flat OPC) or a web page saved under a `.doc`
|
||||
/// name opens by what it holds, as in Word; it went to the Word 97-2003 reader, which refused it,
|
||||
/// and the window showed a blank page.
|
||||
#[test]
|
||||
fn word_xml_and_web_pages_saved_as_doc_open_by_content() {
|
||||
let text = |doc: &wordcraft_doc::Document| doc.plain_text(StoryRef::Body);
|
||||
let mut s = Session::new(wordcraft_doc::Document::new());
|
||||
run(&mut s, "text.insert", json!({"text": "Truyện ngắn, chương một"}));
|
||||
run(&mut s, "format.bold", json!({}));
|
||||
let word2003 = crate::io::ExportOptions { word2003: true, ..Default::default() };
|
||||
let xml2003 = crate::io::save_bytes_opts("a.xml", &s.doc, &word2003).unwrap();
|
||||
let flat = crate::io::save_bytes("a.xml", &s.doc).unwrap();
|
||||
assert!(xml2003.windows(14).any(|w| w == b"w:wordDocument") && flat.windows(11).any(|w| w == b"pkg:package"));
|
||||
for (what, bytes) in [("Word 2003 XML", &xml2003), ("Flat OPC", &flat)] {
|
||||
for name in ["soulsoul.txt.doc", "Story.DOC", "story.dot", "story.docx", "story.rtf"] {
|
||||
let doc = crate::io::open_bytes(name, bytes).unwrap_or_else(|e| panic!("{what} as {name}: {e}"));
|
||||
assert!(text(&doc).contains("Truyện ngắn, chương một"), "{what} as {name}: {:?}", text(&doc));
|
||||
}
|
||||
}
|
||||
// With a byte-order mark and blank lines before the declaration.
|
||||
let bom = [&[0xEF, 0xBB, 0xBF][..], b"\r\n ", &xml2003].concat();
|
||||
assert!(text(&crate::io::open_bytes("bom.doc", &bom).unwrap()).contains("chương một"));
|
||||
|
||||
// Web pages: Word's Save as Web Page, and a report generator's HTML under a `.doc` name.
|
||||
let pages: [&str; 3] = [
|
||||
"<html xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\">\r\n<head><meta charset=utf-8></head>\r\n<body lang=VI><p class=MsoNormal><b>Báo cáo</b> tháng 9</p></body></html>",
|
||||
"\u{FEFF}<!DOCTYPE html>\n<!-- generated -->\n<html><body><h1>Báo cáo</h1><p>tháng 9</p></body></html>",
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"x\"><HTML><BODY><P>Báo cáo tháng 9</P></BODY></HTML>",
|
||||
];
|
||||
for (i, page) in pages.iter().enumerate() {
|
||||
for name in ["report.doc", "report.docx", "report.rtf"] {
|
||||
let doc = crate::io::open_bytes(name, page.as_bytes()).unwrap_or_else(|e| panic!("page {i} as {name}: {e}"));
|
||||
let t = text(&doc);
|
||||
assert!(t.contains("Báo cáo") && t.contains("tháng 9") && !t.contains('<'), "page {i} as {name}: {t:?}");
|
||||
}
|
||||
}
|
||||
// A web page in an unknown extension is still plain text, other XML still no Word document.
|
||||
assert!(text(&crate::io::open_bytes("page.log", pages[1].as_bytes()).unwrap()).contains("<html>"));
|
||||
assert!(crate::io::open_bytes("note.doc", b"<?xml version=\"1.0\"?><note>hi</note>").is_err());
|
||||
// Nor is XML whose root only starts like `html`.
|
||||
for xml in [&b"<htmlish>text</htmlish>"[..], b"<!DOCTYPE htmlish><htmlish>text</htmlish>"] {
|
||||
assert!(crate::io::open_bytes("other.doc", xml).is_err(), "{:?}", String::from_utf8_lossy(xml));
|
||||
}
|
||||
}
|
||||
|
||||
/// #569: a playlist (.m3u / .m3u8) — or any file with an extension WordCraft doesn't know —
|
||||
/// opens as plain text with its encoding detected, like Word's text import; a binary file is a
|
||||
/// clean error, never garbage or a panic.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File-format parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-18 · **Change:** 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 `<blockquote>` 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-10-18 · **Change:** 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 `<blockquote>` 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;
|
||||
@@ -44,7 +44,7 @@ Weights are the share of a Word user's file traffic (estimate). Read/write: ✅
|
||||
| `.rtf` Rich Text Format | 3% | R/W | 🟡 | 🟡 | ~50% | 10–15 | `crates/formats/src/tests.rs` | ~1,500 lines: text, character/paragraph formatting, styles, lists, tables, pictures (`\shppict`, #154), fields, bookmarks. 8-bit text in the document's code page (`\ansicpg`) or the current font's (`\fcharset`, `\cpg`): Cyrillic, Central European, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai (was Windows-1252 only), Mac OS Roman (`\mac`, `\fcharset77`) and DOS code pages (`\pc` 437, `\pca` 850, `\cpg` 437/737/850/852/857/866, `\fcharset254`) (#1653). Double-byte East Asian code pages (Japanese `\fcharset128`, Chinese, Korean) not decoded yet. Pasted from the system clipboard where the platform offers it (Windows, Wayland) when there is no HTML (#687). Tracked insertions and deletions read and written (`\revtbl`, `\revised`/`\deleted`, authors, `\revdttm` dates), so Accept/Reject All work after opening or a round trip; tracked paragraph marks (`\par` inside `\revised`/`\deleted` groups, including a `\sect` inside a deletion) and character and paragraph formatting revisions (`\crauth`/`\crdate` with `\oldcprops`, `\prauth`/`\prdate` with `\oldpprops`) read and written; section and table formatting revisions (`\oldsprops`, `\oldtprops`) not yet. Sections on import (#1378): each `\sect` ends a section with its own page size and orientation, margins, header/footer distance, columns, break type, page-number restart, start and format, title page and headers/footers (those it leaves out link to the previous); export still writes one section. Thin: comments, notes, section export, RTL |
|
||||
| `.odt` OpenDocument Text | 2% | R/W | 🟡 | 🟡 | ~50% | 8–12 | `crates/formats/src/tests.rs`, `crates/formats/src/odt.rs` unit tests, `crates/engine/tests/odt_sections.rs` | Styles, lists (#87), tables, pictures, headers/footers, page breaks (#86), keep with next (#2813), page size, orientation and margins (#810), sections: one per master-page change on a paragraph or table (next page, with its own page size, orientation, margins, columns and page-numbering restart) and per columned `text:section` (continuous, with its columns and separator line) (#1379); export writes them back: a master page and page layout per new-page section (size, orientation, margins, columns, page-numbering restart on the first paragraph or table), a `text:section` with its columns per continuous section whose columns differ from the page's (one column included), a list cut by a section break goes on numbering. Limits: a continuous section's page-numbering restart isn't written (ODF applies it only with a master page); a continuous section with the page's columns merges into the section before it on reopening; even and odd page starts are written as plain new pages; a tracked deletion of a section break is lost; an empty last section is dropped; tracked insertions and deletions with author and date, inserted and deleted paragraph marks and text inserted then deleted included (ODF change tracking; format changes read as none; pictures and tables inside a tracked deletion are dropped). No footnotes, comments, fields or RTL |
|
||||
| `.rtf` Rich Text Format | 3% | R/W | 🟡 | 🟡 | ~50% | 10–15 | `crates/formats/src/tests.rs` | ~1,500 lines: text, character/paragraph formatting, styles, lists, tables, pictures (`\shppict`, #154), fields, bookmarks. 8-bit text in the document's code page (`\ansicpg`) or the current font's (`\fcharset`, `\cpg`): Cyrillic, Central European, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai (was Windows-1252 only), Mac OS Roman (`\mac`, `\fcharset77`) and DOS code pages (`\pc` 437, `\pca` 850, `\cpg` 437/737/850/852/857/866, `\fcharset254`) (#1653). Double-byte East Asian code pages (Japanese `\fcharset128`, Chinese, Korean) not decoded yet. Pasted from the system clipboard where the platform offers it (Windows, Wayland) when there is no HTML (#687). Tracked insertions and deletions read and written (`\revtbl`, `\revised`/`\deleted`, authors, `\revdttm` dates), so Accept/Reject All work after opening or a round trip; paragraph-mark and formatting revisions not yet. Sections on import (#1378): each `\sect` ends a section with its own page size and orientation, margins, header/footer distance, columns, break type, page-number restart, start and format, title page and headers/footers (those it leaves out link to the previous); export writes every section too (break type, page size and orientation, margins, header/footer distance, columns, page numbering, title page; not headers/footers). Thin: comments, notes, RTL |
|
||||
| `.txt` Plain text | 2% | R/W | ✅ | ✅ | ~95% | 1–2 | unit tests | Encoding detection. Files with other extensions (`.m3u`/`.m3u8` playlists, logs, config files, no extension) open as text when they are text, Word and RTF files by their signature; binary files are refused (#569). A Word or RTF extension that contradicts the content also opens by signature, as in Word: RTF saved as `.doc` (hh.ru résumés, accounting exports), a `.docx` renamed `.doc`, a Word 97-2003 file renamed `.docx`. Word's encoding choice dialog and line-ending options missing; such files save only through Save As |
|
||||
| `.txt` Plain text | 2% | R/W | ✅ | ✅ | ~95% | 1–2 | unit tests | Encoding detection. Files with other extensions (`.m3u`/`.m3u8` playlists, logs, config files, no extension) open as text when they are text, Word and RTF files by their signature; binary files are refused (#569). A Word or RTF extension that contradicts the content also opens by signature, as in Word: RTF saved as `.doc` (hh.ru résumés, accounting exports), a `.docx` renamed `.doc`, a Word 97-2003 file renamed `.docx`; a Word XML Document (Word 2003 XML or Flat OPC) or a web page (`<html>`, `<!DOCTYPE html>`) under a `.doc`, `.docx` or `.rtf` name opens as what it is (#2853, `tests::word_xml_and_web_pages_saved_as_doc_open_by_content`). Word's encoding choice dialog and line-ending options missing; such files save only through Save As |
|
||||
| `.htm/.html` Web Page and Web Page, Filtered | 1% | R/W | 🟡 | 🟡 | ~60% | 6–10 | `crates/formats/src/tests.rs` | Tables (#102), relative pictures embedded (#113), missing `</head>` tolerated (#154). Header cells (`<th>`) bold unless their CSS says otherwise; `text-align` on a cell, or inherited from its row, row group or table, aligns the cell's paragraphs (#1381). HTML gives no page size, so it opens on the region's paper (A4 or Letter), as Markdown and plain text do; `@page` isn't read (#1381). No Word "Web Page" (with `_files` folder) round-trip. The same reader takes HTML from the system clipboard: Paste keeps another program's formatting (a Word document, a web page; Windows' "HTML Format" header understood), Merge Formatting keeps its emphasis (#687). A pasted `<blockquote>` (LibreOffice's paragraph indented on both sides) becomes a Normal paragraph indented by the page's `blockquote` margins, else 0.5 in each side; opening an .html file keeps it as the Quote style (#1270). Equations are written as MathML (with the linear format as a UnicodeMath annotation) and read back from MathML: our annotation, else the presentation elements, else a TeX annotation (#816). A copy of one equation alone also goes in the clipboard's MathML formats (Windows "MathML" and "MathML Presentation", macOS `public.mathml.presentation` and `public.mathml`; W3C MathML 3 §6.2.4), which Microsoft documents Word reading first when it pastes math (#1297; the paste into Word is not yet verified) |
|
||||
| `.mht/.mhtml` Single File Web Page | <1% | R/W | ❌ | ❌ | 0% | 3–5 | — | |
|
||||
| `.xml` Word XML Document (Flat OPC) | <1% | R/W | ✅ | ✅ | ~65% | — | `crates/docx/tests/word_xml.rs`, `roundtrip.rs` (`flat_opc_round_trip_equals_docx`) | The DOCX package in one XML file, read and written by the DOCX code (#548); File › Export › Word XML Document. `.xml` files are told apart by their root element |
|
||||
@@ -233,6 +233,7 @@ Imported text fields use the first nonempty cached result run's formatting on th
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-18 | trivial | A Word 2003 XML or Flat OPC document, or a web page, saved under a `.doc`, `.docx` or `.rtf` name opens by what it holds, as in Word, instead of going to the Word 97-2003 reader and showing a blank page (#2853) |
|
||||
| 2026-10-18 | trivial | `w:sym`: the symbol's `w:font` is kept apart from the run font (ECMA-376 §17.3.3.30), so changing the text's font (or Clear Formatting) keeps Wingdings/Symbol characters showing, and they are written back as `<w:sym w:font=… w:char=…/>`; Insert › Symbol in a symbol font does the same (#2036). A private-use character with no symbol font of its own in a non-symbol run is not guessed |
|
||||
| 2026-10-18 | trivial | Manage Sources imports Word's `Sources.xml` (`b:Sources`) into the document's bibliography sources, sharing the reader of a document's own bibliography part (#2640) |
|
||||
| 2026-10-14 | trivial | RTF export writes sections: `\sectd` formatting and `\sect` per section, round-tripping through the reader (gap 19) |
|
||||
|
||||
Reference in new issue
Block a user