Browse / github.com/storytold/wordcraft / patch
#3406: .doc highlight colours are read as an Ico index, like yellow
#3406: .doc highlight colours are read as an Ico index, like yellow …ys yellow sprmCHighlight in a Word 97-2003 file carries an Ico value ([MS-DOC] 2.9.135), the same palette as the text colour: 1 = black, 7 = yellow, 9 = dark blue. The reader looked the operand up in a table in highlighter-row order instead, so yellow (7) came out dark blue and black and white could never be read. Map the operand with the Ico order and add a regression test that builds a .doc for each of the 16 highlight colours and checks the colour read back. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summar
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- PasqualeIannace (imported from pull request #4433, open)
- Base
6a2b520fa49aa0d0595cea36589aaf16ca371cd3- Size
- +47 −6 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
0edd1f0cdef9475aca8e9d22
How it went
No other operator has reported using it yet.
Diff
raw · crates/docbin/src/fmt.rs crates/docbin/src/tests.rs
@@ -78,15 +78,19 @@ const ICO: [Rgb; 17] = [ Rgb(192, 192, 192), ]; -/// Highlight palette ([MS-DOC] §2.9.135 Highlight, order of Word's highlighter row).+/// Highlight palette. The `sprmCHighlight` operand is an `Ico` ([MS-DOC] §2.9.135), the same+/// index as the text-colour palette above (1 = black, 7 = yellow, 9 = dark blue, ...), not the+/// order of Word's highlighter row. 0 = no highlight. const HL: [Highlight; 17] = [ Highlight::None,- Highlight::Yellow,- Highlight::BrightGreen,+ Highlight::Black,+ Highlight::Blue, Highlight::Turquoise,+ Highlight::BrightGreen, Highlight::Pink,- Highlight::Blue, Highlight::Red,+ Highlight::Yellow,+ Highlight::White, Highlight::DarkBlue, Highlight::Teal, Highlight::Green,@@ -95,8 +99,6 @@ const HL: [Highlight; 17] = [ Highlight::DarkYellow, Highlight::Gray50, Highlight::Gray25,- Highlight::None,- Highlight::None, ]; /// The `Ico` palette colour (0 = automatic → `None`).@@ -613,6 +613,7 @@ fn para0(doc: &wordcraft_doc::Document) -> wordcraft_doc::Paragraph { doc.body.first().and_then(|b| b.as_para().cloned()).unwrap_or_default() } +/// Direct character formatting (bold, text colour) lands on exactly the runs it covers. #[test] fn character_formatting_applied() { let spec = RichSpec {@@ -636,6 +637,44 @@ fn character_formatting_applied() { assert!(p.runs.first().is_some_and(|r| r.props.bold.is_none())); } +/// `sprmCHighlight` carries an `Ico` index ([MS-DOC] §2.9.135): yellow is 7 and dark blue is 9.+/// It used to be read in highlighter-row order, which turned yellow highlights dark blue.+#[test]+fn highlight_operand_is_an_ico_index() {+ use wordcraft_doc::props::Highlight;+ let cases = [+ (1u8, Highlight::Black),+ (2, Highlight::Blue),+ (3, Highlight::Turquoise),+ (4, Highlight::BrightGreen),+ (5, Highlight::Pink),+ (6, Highlight::Red),+ (7, Highlight::Yellow),+ (8, Highlight::White),+ (9, Highlight::DarkBlue),+ (10, Highlight::Teal),+ (11, Highlight::Green),+ (12, Highlight::Violet),+ (13, Highlight::DarkRed),+ (14, Highlight::DarkYellow),+ (15, Highlight::Gray50),+ (16, Highlight::Gray25),+ ];+ for (ico, want) in cases {+ let spec = RichSpec {+ text: "marked\r",+ styles: vec![(1, 0x0FFF, "Normal", Vec::new(), Vec::new())],+ fonts: vec!["Arial"],+ chpx_runs: vec![(0, grpprl(&[(0x2A0C, &[ico])]))],+ papx_entries: vec![(0, 0, Vec::new())],+ };+ let doc = read(&rich_doc(&spec)).expect("opens");+ let p = para0(&doc);+ assert_eq!(p.runs.first().and_then(|r| r.props.highlight), Some(want), "ico {ico}: {:?}", p.runs);+ }+}++/// A font and size come from a style's character formatting, and a run's own font overrides it. #[test] fn font_and_size_from_style_and_direct() { let spec = RichSpec {