Skip to main content

extend_docx/doc/
sprm.rs

1//! Sprm ([MS-DOC] 2.2.5.1): single property modifiers. Formatting in the
2//! binary format is stored as grpprls — byte arrays of sprms, each a 16-bit
3//! opcode followed by an operand whose size is encoded in the opcode's `spra`
4//! bits.
5
6/// Property category (`sgc` bits of the opcode).
7pub const SGC_PAR: u8 = 1;
8pub const SGC_CHR: u8 = 2;
9pub const SGC_PIC: u8 = 3;
10pub const SGC_SEC: u8 = 4;
11pub const SGC_TAB: u8 = 5;
12
13#[derive(Debug, Clone, Copy)]
14pub struct Sprm<'a> {
15    pub opcode: u16,
16    pub operand: &'a [u8],
17}
18
19impl<'a> Sprm<'a> {
20    pub fn sgc(&self) -> u8 {
21        ((self.opcode >> 10) & 0x7) as u8
22    }
23
24    pub fn byte(&self) -> u8 {
25        self.operand.first().copied().unwrap_or(0)
26    }
27
28    pub fn bool_operand(&self) -> Option<bool> {
29        match self.byte() {
30            0 => Some(false),
31            1 => Some(true),
32            _ => None, // 0x80/0x81 toggle semantics handled by the caller
33        }
34    }
35
36    pub fn u16_operand(&self) -> u16 {
37        self.operand
38            .get(..2)
39            .map(|b| u16::from_le_bytes([b[0], b[1]]))
40            .unwrap_or(0)
41    }
42
43    pub fn i16_operand(&self) -> i16 {
44        self.u16_operand() as i16
45    }
46
47    pub fn u32_operand(&self) -> u32 {
48        self.operand
49            .get(..4)
50            .map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
51            .unwrap_or(0)
52    }
53}
54
55/// Iterates over the sprms of a grpprl, tolerating truncated trailing data.
56pub struct SprmIter<'a> {
57    grpprl: &'a [u8],
58    pos: usize,
59}
60
61pub fn iterate(grpprl: &[u8]) -> SprmIter<'_> {
62    SprmIter { grpprl, pos: 0 }
63}
64
65impl<'a> Iterator for SprmIter<'a> {
66    type Item = Sprm<'a>;
67
68    fn next(&mut self) -> Option<Sprm<'a>> {
69        if self.pos + 2 > self.grpprl.len() {
70            return None;
71        }
72        let opcode = u16::from_le_bytes([self.grpprl[self.pos], self.grpprl[self.pos + 1]]);
73        let spra = (opcode >> 13) & 0x7;
74        let mut operand_start = self.pos + 2;
75        let operand_len = match spra {
76            0 | 1 => 1,
77            2 | 4 | 5 => 2,
78            3 => 4,
79            7 => 3,
80            6 => {
81                // Variable size: one cb byte, except sprmTDefTable/sprmTDefTable10
82                // where cb is a u16 equal to the remaining size + 1.
83                if opcode == 0xD608 || opcode == 0xD606 {
84                    if operand_start + 2 > self.grpprl.len() {
85                        return None;
86                    }
87                    let cb = u16::from_le_bytes([
88                        self.grpprl[operand_start],
89                        self.grpprl[operand_start + 1],
90                    ]) as usize;
91                    operand_start += 2;
92                    cb.saturating_sub(1)
93                } else {
94                    if operand_start + 1 > self.grpprl.len() {
95                        return None;
96                    }
97                    let cb = self.grpprl[operand_start] as usize;
98                    operand_start += 1;
99                    if opcode == 0xC615 && cb == 255 {
100                        // sprmPChgTabs with cb == 255 has an internally-described
101                        // size we do not decode; stop parsing this grpprl.
102                        return None;
103                    }
104                    cb
105                }
106            }
107            _ => unreachable!(),
108        };
109        let operand_end = operand_start + operand_len;
110        if operand_end > self.grpprl.len() {
111            return None;
112        }
113        let sprm = Sprm {
114            opcode,
115            operand: &self.grpprl[operand_start..operand_end],
116        };
117        self.pos = operand_end;
118        Some(sprm)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn iterates_fixed_and_variable_operands() {
128        // sprmCFBold (0x0835, spra=0, 1 byte) = 1
129        // sprmCHps (0x4A43, spra=2, 2 bytes) = 24 half-points
130        // sprmPDyaLine (0x6412, spra=3, 4 bytes)
131        let grpprl: Vec<u8> = vec![
132            0x35, 0x08, 0x01, //
133            0x43, 0x4A, 0x18, 0x00, //
134            0x12, 0x64, 0xF0, 0x00, 0x01, 0x00,
135        ];
136        let sprms: Vec<_> = iterate(&grpprl).collect();
137        assert_eq!(sprms.len(), 3);
138        assert_eq!(sprms[0].opcode, 0x0835);
139        assert_eq!(sprms[0].byte(), 1);
140        assert_eq!(sprms[1].opcode, 0x4A43);
141        assert_eq!(sprms[1].u16_operand(), 24);
142        assert_eq!(sprms[2].opcode, 0x6412);
143        assert_eq!(sprms[2].sgc(), SGC_PAR);
144    }
145
146    #[test]
147    fn handles_tdef_table_u16_size() {
148        // sprmTDefTable 0xD608 with cb = 8 -> operand is 7 bytes.
149        let mut grpprl: Vec<u8> = vec![0x08, 0xD6];
150        grpprl.extend_from_slice(&8u16.to_le_bytes());
151        grpprl.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7]);
152        grpprl.extend_from_slice(&[0x35, 0x08, 0x01]); // trailing sprmCFBold
153        let sprms: Vec<_> = iterate(&grpprl).collect();
154        assert_eq!(sprms.len(), 2);
155        assert_eq!(sprms[0].opcode, 0xD608);
156        assert_eq!(sprms[0].operand.len(), 7);
157        assert_eq!(sprms[1].opcode, 0x0835);
158    }
159
160    #[test]
161    fn stops_on_truncated_grpprl() {
162        let grpprl: Vec<u8> = vec![0x43, 0x4A, 0x18]; // sprmCHps missing a byte
163        assert_eq!(iterate(&grpprl).count(), 0);
164    }
165}