diff --git a/ROADMAP.md b/ROADMAP.md index bb7789964..b81dbd029 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~15 points (60% → 75% ready for real work) and ~150–230 h away -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** trivial (Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: progress log: a text box that keeps the keyboard no longer resets the input method every frame, #2986; previously trivial: progress log: recovery copies and .json files store pictures as base64, #586 follow-up; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: progress log: legacy menu keys, #1697; previously trivial: progress log: shape gradients and transparency, #1500; previously trivial: progress log: Focus fills the screen, and Full Screen View shows the pages alone, #1678; previously trivial: progress log: text above a float's anchor wraps around it, #2048; previously trivial: progress log: File › Recover follows the app theme, #3243; previously trivial: progress log: caret inside ligatures, #1974; 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: long navigation headings wrap onto further lines within the pane, #3015; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: progress log: a header-cell picture watermark sits behind the text, #1775; previously trivial: symbols keep their font when the text font changes, #2036; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: Add Font imports every selected font file, #1789); previously trivial (RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: Read Aloud speaks each sentence in its own language, #1843; previously trivial: hint-reliant CJK outlines; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: progress log: Turkish case mapping in AutoCorrect, #1923; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: progress log: Hebrew field results and smart quotes, #1670; previously trivial: Backspace removes a selected table, rows or columns, #1844; previously trivial: progress log: mixed Hebrew/Latin text keeps its order in Word, #1543; previously trivial: progress log: web typing in non-Latin text no longer repeats, #1480; previously trivial: progress log: font family caches bounded and math font lookups follow font changes, #1354; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: progress log: Columns on selected text, #2024; previously trivial: progress log: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout fills the window width, #1876; previously trivial: progress log: Different First Page headers reachable from page 1, #2826; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: progress log: web preferences kept between visits, #2132; previously trivial: progress log: undrawable private-use list bullets show the level's default bullet, #1683; previously trivial: progress log: `w:eastAsianLayout` kept on save; previously trivial: table style editor formats every part, #714); previously trivial: VML freeforms read and kept on save; previously trivial: progress log: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** trivial (progress log: banded rows shade as in Word, #3415; previously trivial: progress log: the table handles show while the caret is in the table, #2428; previously trivial: progress log: a click below a floating table reaches the text after it, #2038; previously trivial: progress log: Delete on selected table cells leaves one empty paragraph in each, #2802; previously trivial: progress log: lam-alef in exported PDFs extracts whole with fonts that draw it as two glyphs, #2310; previously trivial: Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459); previously trivial: Russian sorting keeps ё with е and recognises decomposed ё/й; previously trivial: progress log: a text box that keeps the keyboard no longer resets the input method every frame, #2986; previously trivial: progress log: recovery copies and .json files store pictures as base64, #586 follow-up; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: progress log: legacy menu keys, #1697; previously trivial: progress log: shape gradients and transparency, #1500; previously trivial: progress log: Focus fills the screen, and Full Screen View shows the pages alone, #1678; previously trivial: progress log: text above a float's anchor wraps around it, #2048; previously trivial: progress log: File › Recover follows the app theme, #3243; previously trivial: progress log: caret inside ligatures, #1974; 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: long navigation headings wrap onto further lines within the pane, #3015; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: progress log: a header-cell picture watermark sits behind the text, #1775; previously trivial: symbols keep their font when the text font changes, #2036; previously trivial: opening a DOCX no longer copies its pictures; previously trivial: Add Font imports every selected font file, #1789); previously trivial (RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: Read Aloud speaks each sentence in its own language, #1843; previously trivial: hint-reliant CJK outlines; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: progress log: Turkish case mapping in AutoCorrect, #1923; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: progress log: Hebrew field results and smart quotes, #1670; previously trivial: Backspace removes a selected table, rows or columns, #1844; previously trivial: progress log: mixed Hebrew/Latin text keeps its order in Word, #1543; previously trivial: progress log: web typing in non-Latin text no longer repeats, #1480; previously trivial: progress log: font family caches bounded and math font lookups follow font changes, #1354; previously trivial: `-->` AutoCorrect replacement, #2223; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: progress log: Columns on selected text, #2024; previously trivial: progress log: legacy outline, shadow, emboss and imprint text effects drawn, #2534; previously trivial: progress log: Indonesian proofing, #498; previously trivial: progress log: DOCX glossary documents (building blocks); previously trivial: progress log: Read Mode keeps the keyboard and shows whole, unobscured columns, #1772); previously trivial: progress log: font list entries of tall-ascent fonts no longer cut off, #2570); previously trivial: progress log: Split Cells and Convert Text to Table dialogs follow the Word check, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout fills the window width, #1876; previously trivial: progress log: Different First Page headers reachable from page 1, #2826; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717); previously trivial: progress log: web preferences kept between visits, #2132; previously trivial: progress log: undrawable private-use list bullets show the level's default bullet, #1683; previously trivial: progress log: `w:eastAsianLayout` kept on save; previously trivial: table style editor formats every part, #714); previously trivial: VML freeforms read and kept on save; previously trivial: progress log: consistent preferred cell widths; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: progress log: grouped `flowChartOr` shapes draw their circle and crossing spokes; previously trivial: progress log: copies keep their formatting in other programs, #1523; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** trivial (English dialects in proofing, #1281; previously trivial: triple borders draw three lines, #1740; previously trivial: progress log: double-clicking a later page's footer no longer scrolls up, #2103; previously trivial: long navigation headings wrap onto further lines within the pane, #3015; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: a word after a slash wraps whole, like Word, #2963; previously trivial: RTF export writes sections (gap 19); previously trivial: ODT export writes sections (gap 19); previously trivial: progress log: pasted blockquotes are indented paragraphs, #1270; previously trivial: progress log: print-area strings translated in every catalog; previously trivial: progress log: comments readable in the dark theme, #2045; previously trivial: progress log: ODT sections, #1379; previously trivial: progress log: table deletes, Change Case and Unlink Fields respect Track Changes and locked content controls, #1329; previously trivial: progress log: rounded rectangle corners, #1476; previously trivial: progress log: Save As type list, #1399; previously trivial: progress log: privacy options on save, #1062; previously trivial: progress log: equation pictures in theme colours on Windows, #1002; previously trivial: symbol fonts, #776; previously trivial: the save prompt answers from the keyboard, #861; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously trivial: formatting revisions landed, #41; previously trivial: Draw tab ink, #307, and Draw Table and Eraser, #303, landed: catalog 402/434; previously minor: readiness table with hours per audience; full number back to the additive weighted sum, 60%; mainstream and essentials numbers added; alpha gate checked; previously major: full re-measure against Word for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 > **Printing update:** 2026-10-12 · direct desktop printing through craft-print; details below. @@ -139,12 +139,16 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target on interactive Save (#2445). Save before Close waits for that write; cancel or failure keeps the draft and original file. This does not add binary Word output. Late Save As answers are rejected before writing if automation has replaced the document. - 2026-10-21: File › Recover follows the app theme (#3243, part of #2348): no black page or forced white text on the light theme +- 2026-10-21: Table styles shade the same banded rows in Word and WordCraft: our banded styles (Grid Table 4…) and new table styles write their band sizes (`w:tblStyleRowBandSize`, `w:tblStyleColBandSize`), which Word otherwise reads as zero and leaves unbanded, and a file's style without them is unbanded here too (#3415). +- 2026-10-20: A click below a floating table (one dragged by its move handle, or from a .docx) goes to the paragraph beside or after it, as in Word, instead of a cell of its last row, so the text after a moved table can be reached with the mouse again (#2038). - 2026-10-19: Restrict Editing: with Filling in forms enforced, inserting content controls, their Properties and Remove Content Control are refused, as in Word (it greys them out), so a form's fields stay as they are and only their content is filled in. - 2026-10-19: Content controls: text typed over a placeholder takes the control's own run properties (`w:sdtPr/w:rPr`, ECMA-376 §17.5.2.27), as in Word, and the placeholder style is recognised by its built-in name "Placeholder Text" whatever its localized id (German Word: `Platzhaltertext`), so entries in forms from localized Word no longer stay grey. - 2026-10-15: Find opens Navigation on Results; editing the query restores Results after manual Headings/Pages navigation. Typed and selected-text searches retain document contents (#2096). | Date | What landed | +| 2026-10-20 | The move and resize handles of a table show while the caret is in it, not only under the pointer, as in Word (#2428) | | 2026-10-19 | Personal templates (#2852): File › New lists the `.dotx` and `.dotm` files in your templates folder under Personal (a subfolder per category; the search box covers them), and one opens as a new untitled document attached to it, as in Word. Save As › Save as Word Template starts in that folder, made on first use in the settings folder; File › Options › Advanced › File locations › User templates points it elsewhere. Agents list them with `file.listTemplates`. | +| 2026-10-19 | Delete with whole table cells selected (rows, columns, the table or one cell) empties each cell to one paragraph, as in Word: no blank paragraphs are left behind, the paragraph keeps the formatting of the cell's last one, and tables nested in the cells go too. One undo brings everything back (#2802). | | 2026-10-15 | A picture anchored further down a page but placed higher up it (fixed on the page, or moved up from its paragraph) has the paragraphs above its anchor wrap around it too, instead of running behind it, also when that wrap pushes the anchor onto the next page (#2048) | | 2026-10-19 | Typing on Linux no longer stops after clicking Search (#2986): Search, Go To's page box, the Icons search and a new caption label asked for the keyboard on every frame, and each request reset the input method; they now ask only while they lack it. | | 2026-10-19 | An equation from LibreOffice in an `.odt` file no longer shows blank (#3099): it's embedded as a `draw:object` formula sub-document holding MathML, with a `draw:image` replacement preview in LibreOffice's own metafile format WordCraft can't decode as a picture; the formula's own MathML is read instead, through the same reader as HTML equations. | @@ -313,6 +317,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | 2026-10-14 | RTF keeps tracked changes (gap 19): insertions and deletions open as tracked changes with their authors and dates (`\revtbl`, `\revised`, `\deleted`, `\revdttm`) instead of plain text, so deleted text no longer reappears as normal text; saving to RTF writes them back, and Accept All / Reject All work after reopening | | 2026-10-14 | Alignment guides: moving a picture, shape or text box snaps its edges or centre to the page's edges and centre, the margins and the other floating objects on the page, with a thin line across the page for each guide while dragging; Alt moves freely; File › Options › Advanced › Editing turns them off (`ui.alignmentGuides`). Our own design: Word for Mac shows no guides | | 2026-10-14 | ODT files open with their sections (#1379): a paragraph that switches to another page style starts a new section on a new page, with that page's size, orientation, margins and columns and, when the file asks, page numbering restarting; a two-column `text:section` becomes a continuous two-column section. Before, the whole document was one portrait section, so landscape pages, columns and restarts were lost on open and on save to DOCX | +| 2026-10-16 | Arabic words with lam-alef copy and search whole in exported PDFs with fonts that draw لا as a lam and a zero-width alef at one spot, such as the static Noto Naskh Arabic builds (#2310): السلام no longer reads back as السالم. Clusters drawn at the same place now go in reverse text order, so viewers reverse the run as a whole again. The page looks the same | | 2026-10-14 | Hebrew and Arabic in exported PDFs copy and search as whole words (#1383): right-to-left glyphs are written left to right as they appear, the order Chrome and Edge (PDFium) and poppler read text back from (checked by hand), instead of in reading order, which they reversed (`שלום` came back as `םולש`, Arabic in pieces). Lam-alef stays whole and vowel marks follow their letters, though Chrome and poppler can still break a fully vowelled word with spaces. The page looks the same. Readers that take glyphs strictly in file order get only plain Hebrew runs right, through `/ReversedChars`; screen readers are unverified | | 2026-10-14 | HTML import (#1381): header cells (``) are bold unless their own CSS says otherwise, and `text-align` on a cell, its row, row group or table aligns the cell's paragraphs (a paragraph's own alignment wins), so `wordcraft-cli convert page.html out.docx` writes the bold header and `w:jc` right-aligned cells. HTML, Markdown and plain text, which give no page size, open on the region's paper (A4 in Italy, Letter in the US) like a new blank document, in the app and in `wordcraft-cli`; documents with their own page size keep it | | 2026-10-14 | Custom page sizes (#1433): Layout › Size ends with More Paper Sizes…, which opens Page Setup's new Paper group — a Paper size list (the named sizes, or Custom size) and Width and Height in the measurement unit; OK sets the size only when it changed, and a wider-than-tall size comes out landscape. `layout.size` now clamps each side to Word's 0.1"–22" and refuses a page the margins don't fit (the margins rule: 36 pt of text room), and Page Setup checks the size and margins together before applying either | @@ -524,9 +529,13 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | Date | Change | Summary | |---|---|---| | 2026-10-21 | trivial | Progress log: File › Recover follows the app theme (#3243) | -| 2026-10-19 | trivial | Progress log: typing no longer stops after clicking Search (#2986) | -| 2026-10-20 | trivial | Russian sorting keeps ё with е and recognises decomposed ё/й; synthetic ordering, case and index-heading regressions; broader parity estimates unchanged | | 2026-10-21 | trivial | Bullets and Numbering toggle off style-inherited lists at their effective level, including basedOn chains and numbered ListParagraph styles; other paragraphs and list definitions stay unchanged (related to #3459) | +| 2026-10-21 | trivial | Progress log: table style band sizes, banded rows as in Word (#3415) | +| 2026-10-20 | trivial | Russian sorting keeps ё with е and recognises decomposed ё/й; synthetic ordering, case and index-heading regressions; broader parity estimates unchanged | +| 2026-10-20 | trivial | Progress log: a click below a floating table goes to the text after it, not its last row (#2038) | +| 2026-10-20 | trivial | Progress log: the table handles show while the caret is in the table (#2428) | +| 2026-10-19 | trivial | Progress log: typing no longer stops after clicking Search (#2986) | +| 2026-10-19 | trivial | Progress log: Delete on selected table cells leaves one empty paragraph in each (#2802) | | 2026-10-19 | trivial | Progress log: forms protection keeps its content controls: no inserting, Properties or Remove Content Control | | 2026-10-19 | trivial | Progress log: text typed into a content control takes the control's formatting; localized placeholder styles recognised | | 2026-10-17 | trivial | Progress log: #2048 entry covers anchors pushed onto the next page | @@ -666,6 +675,7 @@ Footer page-table height and body wrapping are corrected. Other pagination gaps | 2026-10-14 | trivial | Progress log: alignment guides while dragging pictures and shapes | | 2026-10-14 | trivial | Progress log: ODT sections on import (#1379) | | 2026-10-14 | trivial | Progress log: right-to-left text in exported PDFs extracts as whole words (#1383) | +| 2026-10-16 | trivial | Progress log: lam-alef drawn as two glyphs at one x extracts whole from exported PDFs (#2310) | | 2026-10-14 | trivial | Progress log: HTML header cells, cell alignment and region paper for opened HTML/Markdown/text (#1381) | | 2026-10-14 | trivial | Progress log: custom page size from More Paper Sizes… and Page Setup (#1433) | | 2026-10-13 | trivial | Progress log: highlighted text on a dark page (#1252) | diff --git a/crates/doc/src/encoding.rs b/crates/doc/src/encoding.rs index ee70d4151..111839171 100644 --- a/crates/doc/src/encoding.rs +++ b/crates/doc/src/encoding.rs @@ -1,7 +1,8 @@ //! Windows-1252 (the byte encoding legacy Word text pieces use) and the other single-byte Windows //! code pages RTF files name with `\ansicpg` or a font's `\fcharset` (Cyrillic 1251, Central -//! European 1250, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai), and Mac OS Roman -//! (10000, RTF `\mac`). +//! European 1250, Greek, Turkish, Hebrew, Arabic, Baltic, Vietnamese, Thai), Mac OS Roman +//! (10000, RTF `\mac`), and the East Asian multibyte code pages (932 Shift-JIS, 936 GBK, 949 UHC, +//! 950 Big5), whose bytes decode as runs rather than one at a time. /// Windows-1252 code points for 0x80..=0x9F. const CP1252: [u16; 32] = [ @@ -286,13 +287,47 @@ pub fn code_page_byte(code_page: u16, b: u8) -> char { if b >= 0x80 && OEM_CODE_PAGES.contains(&code_page) { oem_byte(code_page, b) } else { windows_byte(code_page, b) } } +/// The East Asian multibyte code pages a document format names with `\ansicpg` or a font's `\cpg`: +/// 932 Shift-JIS (Japanese), 936 GBK (Simplified Chinese), 949 UHC (Korean) and 950 Big5 +/// (Traditional Chinese). +pub const MULTIBYTE_CODE_PAGES: [u16; 4] = [932, 936, 949, 950]; + +/// Whether `code_page` is one of the East Asian multibyte code pages ([`MULTIBYTE_CODE_PAGES`]): +/// its bytes decode as a run, a lead byte together with its trail byte. +pub fn is_multibyte_code_page(code_page: u16) -> bool { + multibyte_encoding_name(code_page).is_some() +} + +/// The WHATWG encoding name of an East Asian multibyte code page (see [`MULTIBYTE_CODE_PAGES`]), +/// for `encoding_rs::Encoding::for_label`; `None` for the single-byte code pages, whose bytes +/// decode one at a time, and anything else. +pub fn multibyte_encoding_name(code_page: u16) -> Option<&'static str> { + Some(match code_page { + 932 => "shift_jis", + 936 => "gbk", + 949 => "euc-kr", + 950 => "big5", + _ => return None, + }) +} + +/// Whether a document format can name `code_page` for its text: a single-byte Windows, DOS or Mac +/// one ([`is_byte_code_page`]) or an East Asian multibyte one ([`is_multibyte_code_page`]). +pub fn is_text_code_page(code_page: u16) -> bool { + is_byte_code_page(code_page) || is_multibyte_code_page(code_page) +} + /// The code page of an RTF or Word font charset (`\fcharset`): 204 Russian → 1251, 238 Eastern -/// European → 1250, 77 Mac → 10000, 254 PC / 255 OEM → 437… `None` for the ANSI and default charsets (the -/// document's code page applies), symbol fonts and the double-byte East Asian charsets. +/// European → 1250, 77 Mac → 10000, 254 PC / 255 OEM → 437, 128 Japanese → 932, 129 Korean → 949, +/// 134 Simplified Chinese → 936, 136 Traditional Chinese → 950… `None` for the ANSI and default +/// charsets (the document's code page applies) and symbol fonts. pub fn charset_code_page(charset: i32) -> Option { Some(match charset { 77 => MAC_ROMAN_CODE_PAGE, - 254 | 255 => 437, + 128 => 932, + 129 => 949, + 134 => 936, + 136 => 950, 161 => 1253, 162 => 1254, 163 => 1258, @@ -302,6 +337,7 @@ pub fn charset_code_page(charset: i32) -> Option { 204 => 1251, 222 => 874, 238 => 1250, + 254 | 255 => 437, _ => return None, }) } @@ -347,6 +383,25 @@ mod tests { assert!(!is_byte_code_page(932) && !is_byte_code_page(0) && !SINGLE_BYTE_CODE_PAGES.contains(&10000)); } + /// #1653: the East Asian charsets and code pages RTF names are multibyte: their bytes decode + /// as runs (a lead byte and its trail byte together), not one at a time. + #[test] + fn east_asian_code_pages_are_multibyte() { + assert_eq!(charset_code_page(128), Some(932), "Japanese"); + assert_eq!(charset_code_page(129), Some(949), "Korean"); + assert_eq!(charset_code_page(134), Some(936), "Simplified Chinese"); + assert_eq!(charset_code_page(136), Some(950), "Traditional Chinese"); + assert_eq!(charset_code_page(255), Some(437), "OEM is single-byte"); + assert_eq!(MULTIBYTE_CODE_PAGES, [932, 936, 949, 950]); + for cp in MULTIBYTE_CODE_PAGES { + assert!(is_multibyte_code_page(cp) && is_text_code_page(cp), "{cp}"); + assert!(!is_byte_code_page(cp) && !SINGLE_BYTE_CODE_PAGES.contains(&cp), "{cp}"); + assert!(multibyte_encoding_name(cp).is_some(), "{cp}"); + } + assert!(is_text_code_page(1251) && is_text_code_page(437) && is_text_code_page(10000)); + assert!(!is_text_code_page(0) && !is_text_code_page(9999)); + } + #[test] fn oem_code_pages() { let decode = |cp: u16, bytes: &[u8]| bytes.iter().map(|&b| oem_byte(cp, b)).collect::(); diff --git a/crates/doc/src/styles.rs b/crates/doc/src/styles.rs index 59416ef9f..f9932bf38 100644 --- a/crates/doc/src/styles.rs +++ b/crates/doc/src/styles.rs @@ -73,8 +73,13 @@ pub struct TableStyleParts { pub band_chr: CharProps, /// Cell borders of the odd row bands (`band1Horz` region's `w:tcBorders`). pub band_borders: Option, - /// Rows per band (`w:tblStyleRowBandSize`); one when unset. + /// Rows per band (`w:tblStyleRowBandSize`). Unset (in the whole based-on chain) or zero, the + /// row bands are off: Word reads a missing size as zero, where ECMA-376 says one (MS-OE376 + /// §2.7.5.7, #3415). Our own banded styles set one. pub band_size: Option, + /// Columns per band (`w:tblStyleColBandSize`); unset or zero, the column bands are off, as + /// in Word (MS-OI29500 §17.7.6.5). + pub col_band_size: Option, pub first_col_chr: CharProps, pub total_chr: CharProps, pub total_border_top: Option, @@ -179,17 +184,24 @@ pub struct PartSet(u16); impl PartSet { /// The parts of the cell at row `ri` of `nrows`, grid column `g` spanning `span` of `ncols`, - /// in a table with options `look` and row bands of `band_size` rows. - pub fn of_cell(look: &TableLook, band_size: u32, (ri, nrows): (usize, usize), (g, span, ncols): (usize, usize, usize)) -> PartSet { + /// in a table with options `look`, row bands of `row_size` rows and column bands of + /// `col_size` columns (a size of zero turns those bands off, as in Word). + pub fn of_cell( + look: &TableLook, + (row_size, col_size): (u32, u32), + (ri, nrows): (usize, usize), + (g, span, ncols): (usize, usize, usize), + ) -> PartSet { let header = look.header_row && ri == 0; let total = look.total_row && ri + 1 == nrows && nrows > 1; let first_col = look.first_column && g == 0; let last_col = look.last_column && g.saturating_add(span) >= ncols && ncols > 1; - let size = band_size.clamp(1, 1000) as usize; - let row_band = look.banded_rows && !header; - let odd_row = (ri.saturating_sub(usize::from(look.header_row)) / size).is_multiple_of(2); - let col_band = look.banded_columns && !first_col; - let odd_col = g.saturating_sub(usize::from(look.first_column)).is_multiple_of(2); + let rows = row_size.min(1000) as usize; + let cols = col_size.min(1000) as usize; + let row_band = look.banded_rows && !header && rows > 0; + let odd_row = (ri.saturating_sub(usize::from(look.header_row)) / rows.max(1)).is_multiple_of(2); + let col_band = look.banded_columns && !first_col && cols > 0; + let odd_col = (g.saturating_sub(usize::from(look.first_column)) / cols.max(1)).is_multiple_of(2); let mut bits = TablePart::WholeTable.bit(); for (on, p) in [ (col_band && odd_col, TablePart::Band1Vert), @@ -270,6 +282,7 @@ impl TableStyleParts { self.header_borders = merge(self.header_borders, patch.header_borders); self.band_borders = merge(self.band_borders, patch.band_borders); self.band_size = patch.band_size.or(self.band_size); + self.col_band_size = patch.col_band_size.or(self.col_band_size); self.band_chr.overlay(&patch.band_chr); self.fill = patch.fill.or(self.fill); self.cell_margins = patch.cell_margins.or(self.cell_margins); @@ -846,6 +859,7 @@ fn p_style() -> Style { /// A few table styles of our own (grid, plain, light/medium accents). fn table_styles() -> Vec