1use super::fmt::{parse_brc80, parse_brc97, Border, Shading};
6use super::sprm::{self, Sprm, SGC_TAB};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct TabStop {
10 pub dxa: i16,
11 pub jc: u8,
12 pub leader: u8,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct LineSpacing {
17 pub dya_line: i16,
18 pub f_mult: bool,
19}
20
21#[derive(Debug, Default, Clone, PartialEq)]
22pub struct Pap {
23 pub istd: u16,
24 pub jc: Option<u8>,
25 pub dxa_left: Option<i16>,
26 pub dxa_right: Option<i16>,
27 pub dxa_left1: Option<i16>,
28 pub dya_before: Option<u16>,
29 pub dya_after: Option<u16>,
30 pub before_auto: Option<bool>,
31 pub after_auto: Option<bool>,
32 pub lspd: Option<LineSpacing>,
33 pub contextual_spacing: Option<bool>,
34 pub f_keep: Option<bool>,
35 pub f_keep_follow: Option<bool>,
36 pub f_page_break_before: Option<bool>,
37 pub f_widow_control: Option<bool>,
38 pub ilvl: Option<u8>,
39 pub ilfo: Option<u16>,
40 pub outline_lvl: Option<u8>,
41 pub f_bidi: Option<bool>,
42 pub brc_top: Option<Border>,
43 pub brc_left: Option<Border>,
44 pub brc_bottom: Option<Border>,
45 pub brc_right: Option<Border>,
46 pub brc_between: Option<Border>,
47 pub shd: Option<Shading>,
48 pub tabs_deleted: Vec<i16>,
49 pub tabs_added: Vec<TabStop>,
50 pub f_in_table: bool,
52 pub f_ttp: bool,
53 pub itap: u32,
54 pub f_inner_table_cell: bool,
55 pub f_inner_ttp: bool,
56 pub table_grpprl: Vec<u8>,
59}
60
61impl Pap {
62 pub fn apply_papx(&mut self, grpprl: &[u8]) {
63 for sprm in sprm::iterate(grpprl) {
64 if sprm.sgc() == SGC_TAB {
65 self.table_grpprl.extend_from_slice(&sprm_bytes(&sprm));
67 continue;
68 }
69 self.apply_sprm(&sprm);
70 }
71 }
72
73 fn apply_sprm(&mut self, sprm: &Sprm<'_>) {
74 match sprm.opcode {
75 0x4600 => self.istd = sprm.u16_operand(),
76 0x2403 | 0x2461 => self.jc = Some(sprm.byte()),
77 0x2405 => self.f_keep = sprm.bool_operand(),
78 0x2406 => self.f_keep_follow = sprm.bool_operand(),
79 0x2407 => self.f_page_break_before = sprm.bool_operand(),
80 0x2431 => self.f_widow_control = sprm.bool_operand(),
81 0x260A => self.ilvl = Some(sprm.byte()),
82 0x460B => self.ilfo = Some(sprm.u16_operand()),
83 0x840F | 0x845E => self.dxa_left = Some(sprm.i16_operand()),
84 0x840E | 0x845D => self.dxa_right = Some(sprm.i16_operand()),
85 0x8411 | 0x8460 => self.dxa_left1 = Some(sprm.i16_operand()),
86 0x6412 => {
87 if sprm.operand.len() >= 4 {
88 self.lspd = Some(LineSpacing {
89 dya_line: i16::from_le_bytes([sprm.operand[0], sprm.operand[1]]),
90 f_mult: i16::from_le_bytes([sprm.operand[2], sprm.operand[3]]) != 0,
91 });
92 }
93 }
94 0xA413 => self.dya_before = Some(sprm.u16_operand()),
95 0xA414 => self.dya_after = Some(sprm.u16_operand()),
96 0x245B => self.before_auto = sprm.bool_operand(),
97 0x245C => self.after_auto = sprm.bool_operand(),
98 0x246D => self.contextual_spacing = sprm.bool_operand(),
99 0x2640 => self.outline_lvl = Some(sprm.byte()),
100 0x2441 => self.f_bidi = sprm.bool_operand(),
101 0x2416 => self.f_in_table = sprm.byte() == 1,
102 0x2417 => self.f_ttp = sprm.byte() == 1,
103 0x6649 => self.itap = sprm.u32_operand(),
104 0x244B => self.f_inner_table_cell = sprm.byte() == 1,
105 0x244C => self.f_inner_ttp = sprm.byte() == 1,
106 0x6424 => self.brc_top = parse_brc80(sprm.operand),
107 0x6425 => self.brc_left = parse_brc80(sprm.operand),
108 0x6426 => self.brc_bottom = parse_brc80(sprm.operand),
109 0x6427 => self.brc_right = parse_brc80(sprm.operand),
110 0x6428 => self.brc_between = parse_brc80(sprm.operand),
111 0xC64E => self.brc_top = parse_brc97(sprm.operand),
112 0xC64F => self.brc_left = parse_brc97(sprm.operand),
113 0xC650 => self.brc_bottom = parse_brc97(sprm.operand),
114 0xC651 => self.brc_right = parse_brc97(sprm.operand),
115 0xC652 => self.brc_between = parse_brc97(sprm.operand),
116 0x442D => self.shd = super::fmt::parse_shd80(sprm.u16_operand()),
117 0xC64D => self.shd = super::fmt::parse_shd_operand(sprm.operand),
118 0xC60D | 0xC615 => self.apply_tab_operand(sprm.operand, sprm.opcode == 0xC615),
119 _ => {}
120 }
121 }
122
123 fn apply_tab_operand(&mut self, operand: &[u8], has_close_array: bool) {
125 let mut pos = 0usize;
126 let Some(&del_count) = operand.first() else {
127 return;
128 };
129 pos += 1;
130 for index in 0..del_count as usize {
131 let offset = pos + index * 2;
132 if offset + 2 > operand.len() {
133 return;
134 }
135 self.tabs_deleted
136 .push(i16::from_le_bytes([operand[offset], operand[offset + 1]]));
137 }
138 pos += del_count as usize * 2;
139 if has_close_array {
140 pos += del_count as usize * 2; }
142 let Some(&add_count) = operand.get(pos) else {
143 return;
144 };
145 pos += 1;
146 let dxa_base = pos;
147 let tbd_base = pos + add_count as usize * 2;
148 for index in 0..add_count as usize {
149 let dxa_offset = dxa_base + index * 2;
150 let tbd_offset = tbd_base + index;
151 if dxa_offset + 2 > operand.len() || tbd_offset >= operand.len() {
152 return;
153 }
154 let tbd = operand[tbd_offset];
155 self.tabs_added.push(TabStop {
156 dxa: i16::from_le_bytes([operand[dxa_offset], operand[dxa_offset + 1]]),
157 jc: tbd & 0x07,
158 leader: (tbd >> 3) & 0x07,
159 });
160 }
161 }
162
163 pub fn to_ppr_inner_xml(&self, style_id: &impl Fn(u16) -> Option<String>) -> String {
166 let mut inner = String::new();
167
168 if self.istd != 0 {
169 if let Some(id) = style_id(self.istd) {
170 inner.push_str(&format!(
171 r#"<w:pStyle w:val="{}"/>"#,
172 super::fmt::escape_xml(&id)
173 ));
174 }
175 }
176 if let Some(keep) = self.f_keep {
177 inner.push_str(if keep {
178 "<w:keepLines/>"
179 } else {
180 r#"<w:keepLines w:val="0"/>"#
181 });
182 }
183 if let Some(keep_next) = self.f_keep_follow {
184 inner.push_str(if keep_next {
185 "<w:keepNext/>"
186 } else {
187 r#"<w:keepNext w:val="0"/>"#
188 });
189 }
190 if let Some(page_break) = self.f_page_break_before {
191 inner.push_str(if page_break {
192 "<w:pageBreakBefore/>"
193 } else {
194 r#"<w:pageBreakBefore w:val="0"/>"#
195 });
196 }
197 if let Some(widow) = self.f_widow_control {
198 inner.push_str(if widow {
199 "<w:widowControl/>"
200 } else {
201 r#"<w:widowControl w:val="0"/>"#
202 });
203 }
204
205 if let Some(numbering) = self.num_pr_xml() {
206 inner.push_str(&numbering);
207 }
208
209 let borders: Vec<(&str, &Option<Border>)> = vec![
210 ("w:top", &self.brc_top),
211 ("w:left", &self.brc_left),
212 ("w:bottom", &self.brc_bottom),
213 ("w:right", &self.brc_right),
214 ("w:between", &self.brc_between),
215 ];
216 if borders.iter().any(|(_, border)| border.is_some()) {
217 inner.push_str("<w:pBdr>");
218 for (tag, border) in borders {
219 if let Some(border) = border {
220 inner.push_str(&border.to_xml(tag));
221 }
222 }
223 inner.push_str("</w:pBdr>");
224 }
225
226 if let Some(shd) = &self.shd {
227 inner.push_str(&shd.to_xml());
228 }
229
230 if !self.tabs_deleted.is_empty() || !self.tabs_added.is_empty() {
231 inner.push_str("<w:tabs>");
232 for dxa in &self.tabs_deleted {
233 inner.push_str(&format!(r#"<w:tab w:val="clear" w:pos="{dxa}"/>"#));
234 }
235 for tab in &self.tabs_added {
236 let val = match tab.jc {
237 1 => "center",
238 2 => "right",
239 3 => "decimal",
240 4 => "bar",
241 _ => "left",
242 };
243 let leader = match tab.leader {
244 1 => Some("dot"),
245 2 => Some("hyphen"),
246 3 => Some("underscore"),
247 4 => Some("heavy"),
248 5 => Some("middleDot"),
249 _ => None,
250 };
251 let mut tab_xml = format!(r#"<w:tab w:val="{val}" w:pos="{}""#, tab.dxa);
252 if let Some(leader) = leader {
253 tab_xml.push_str(&format!(r#" w:leader="{leader}""#));
254 }
255 tab_xml.push_str("/>");
256 inner.push_str(&tab_xml);
257 }
258 inner.push_str("</w:tabs>");
259 }
260
261 let has_spacing = self.dya_before.is_some()
262 || self.dya_after.is_some()
263 || self.lspd.is_some()
264 || self.before_auto.is_some()
265 || self.after_auto.is_some();
266 if has_spacing {
267 let mut spacing = String::from("<w:spacing");
268 if let Some(before) = self.dya_before {
269 spacing.push_str(&format!(r#" w:before="{before}""#));
270 }
271 if let Some(automatic) = self.before_auto {
272 spacing.push_str(if automatic {
273 r#" w:beforeAutospacing="1""#
274 } else {
275 r#" w:beforeAutospacing="0""#
276 });
277 }
278 if let Some(after) = self.dya_after {
279 spacing.push_str(&format!(r#" w:after="{after}""#));
280 }
281 if let Some(automatic) = self.after_auto {
282 spacing.push_str(if automatic {
283 r#" w:afterAutospacing="1""#
284 } else {
285 r#" w:afterAutospacing="0""#
286 });
287 }
288 if let Some(lspd) = self.lspd {
289 let (line, rule) = if lspd.f_mult {
290 (lspd.dya_line as i32, "auto")
291 } else if lspd.dya_line >= 0 {
292 (lspd.dya_line as i32, "atLeast")
293 } else {
294 (-(lspd.dya_line as i32), "exact")
295 };
296 spacing.push_str(&format!(r#" w:line="{line}" w:lineRule="{rule}""#));
297 }
298 spacing.push_str("/>");
299 inner.push_str(&spacing);
300 }
301
302 if self.contextual_spacing == Some(true) {
303 inner.push_str("<w:contextualSpacing/>");
304 }
305
306 if self.dxa_left.is_some() || self.dxa_right.is_some() || self.dxa_left1.is_some() {
307 let mut ind = String::from("<w:ind");
308 if let Some(left) = self.dxa_left {
309 ind.push_str(&format!(r#" w:left="{left}""#));
310 }
311 if let Some(right) = self.dxa_right {
312 ind.push_str(&format!(r#" w:right="{right}""#));
313 }
314 if let Some(first) = self.dxa_left1 {
315 if first >= 0 {
316 ind.push_str(&format!(r#" w:firstLine="{first}""#));
317 } else {
318 ind.push_str(&format!(r#" w:hanging="{}""#, -(first as i32)));
319 }
320 }
321 ind.push_str("/>");
322 inner.push_str(&ind);
323 }
324
325 if let Some(jc) = self.jc {
326 let val = match jc {
327 1 => "center",
328 2 => "right",
329 3 => "both",
330 4 => "distribute",
331 _ => "left",
332 };
333 inner.push_str(&format!(r#"<w:jc w:val="{val}"/>"#));
334 }
335
336 if let Some(level) = self.outline_lvl {
337 if level < 9 {
338 inner.push_str(&format!(r#"<w:outlineLvl w:val="{level}"/>"#));
339 }
340 }
341 if self.f_bidi == Some(true) {
342 inner.push_str("<w:bidi/>");
343 }
344
345 inner
346 }
347
348 pub fn num_pr_xml(&self) -> Option<String> {
351 let ilfo = self.ilfo?;
352 if ilfo == 0 || ilfo == 0xF801 {
353 return Some(r#"<w:numPr><w:numId w:val="0"/></w:numPr>"#.to_string());
354 }
355 let ilfo = if ilfo >= 0xF802 {
356 ilfo.wrapping_neg()
357 } else {
358 ilfo
359 };
360 let ilvl = self.ilvl.unwrap_or(0);
361 Some(format!(
362 r#"<w:numPr><w:ilvl w:val="{ilvl}"/><w:numId w:val="{ilfo}"/></w:numPr>"#
363 ))
364 }
365}
366
367fn sprm_bytes(sprm: &Sprm<'_>) -> Vec<u8> {
368 let mut bytes = sprm.opcode.to_le_bytes().to_vec();
369 let spra = (sprm.opcode >> 13) & 0x7;
370 if spra == 6 {
371 if sprm.opcode == 0xD608 || sprm.opcode == 0xD606 {
372 bytes.extend_from_slice(&((sprm.operand.len() + 1) as u16).to_le_bytes());
373 } else {
374 bytes.push(sprm.operand.len() as u8);
375 }
376 }
377 bytes.extend_from_slice(sprm.operand);
378 bytes
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn automatic_spacing_preserves_explicit_false_and_absence() {
387 assert!(!Pap::default()
388 .to_ppr_inner_xml(&|_| None)
389 .contains("Autospacing"));
390 let mut pap = Pap::default();
391 for value in [1u8, 0u8] {
392 pap.apply_papx(&[0x5B, 0x24, value, 0x5C, 0x24, value]);
393 let xml = pap.to_ppr_inner_xml(&|_| None);
394 let spacing = crate::parse::parse_paragraph_spacing_from_xml(&xml).unwrap();
395 assert_eq!(spacing.before_auto_spacing, Some(value != 0));
396 assert_eq!(spacing.after_auto_spacing, Some(value != 0));
397 }
398 }
399
400 #[test]
401 fn applies_indent_and_justification() {
402 let grpprl: Vec<u8> = vec![
403 0x03, 0x24, 0x01, 0x0F, 0x84, 0xD0, 0x02, 0x11, 0x84, 0x30, 0xFE, ];
407 let mut pap = Pap::default();
408 pap.apply_papx(&grpprl);
409 let xml = pap.to_ppr_inner_xml(&|_| None);
410 assert!(xml.contains(r#"<w:jc w:val="center"/>"#));
411 assert!(xml.contains(r#"w:left="720""#));
412 assert!(xml.contains(r#"w:hanging="464""#));
413 }
414
415 #[test]
416 fn separates_table_sprms() {
417 let grpprl: Vec<u8> = vec![
418 0x16, 0x24, 0x01, 0x17, 0x24, 0x01, 0x07, 0x94, 0x40, 0x01, ];
422 let mut pap = Pap::default();
423 pap.apply_papx(&grpprl);
424 assert!(pap.f_in_table);
425 assert!(pap.f_ttp);
426 assert_eq!(pap.table_grpprl, vec![0x07, 0x94, 0x40, 0x01]);
427 }
428
429 #[test]
430 fn line_spacing_rules() {
431 let mut pap = Pap::default();
432 pap.lspd = Some(LineSpacing {
433 dya_line: 480,
434 f_mult: true,
435 });
436 let xml = pap.to_ppr_inner_xml(&|_| None);
437 assert!(xml.contains(r#"w:line="480" w:lineRule="auto""#));
438
439 let mut exact = Pap::default();
440 exact.lspd = Some(LineSpacing {
441 dya_line: -240,
442 f_mult: false,
443 });
444 let xml = exact.to_ppr_inner_xml(&|_| None);
445 assert!(xml.contains(r#"w:line="240" w:lineRule="exact""#));
446 }
447}