1use super::fmt::nfc_to_num_fmt;
6use super::sprm::{self, Sprm};
7
8#[derive(Debug, Clone)]
9pub struct Sep {
10 pub bkc: u8,
11 pub f_title_page: bool,
12 pub ccol_m1: u16,
13 pub dxa_columns: u16,
14 pub f_evenly_spaced: bool,
15 pub col_widths: Vec<(u8, u16)>,
16 pub nfc_pgn: Option<u8>,
17 pub f_pgn_restart: bool,
18 pub pgn_start: u16,
19 pub xa_page: u32,
20 pub ya_page: u32,
21 pub orientation_landscape: bool,
22 pub dxa_left: u32,
23 pub dxa_right: u32,
24 pub dya_top: i32,
25 pub dya_bottom: i32,
26 pub dza_gutter: u32,
27 pub dya_hdr_top: u32,
28 pub dya_hdr_bottom: u32,
29 pub clm: u16,
30 pub dya_line_pitch: u16,
31 pub dxt_char_space: u32,
32}
33
34impl Default for Sep {
35 fn default() -> Sep {
36 Sep {
38 bkc: 2,
39 f_title_page: false,
40 ccol_m1: 0,
41 dxa_columns: 708,
42 f_evenly_spaced: true,
43 col_widths: Vec::new(),
44 nfc_pgn: None,
45 f_pgn_restart: false,
46 pgn_start: 1,
47 xa_page: 12240,
48 ya_page: 15840,
49 orientation_landscape: false,
50 dxa_left: 1800,
51 dxa_right: 1800,
52 dya_top: 1440,
53 dya_bottom: 1440,
54 dza_gutter: 0,
55 dya_hdr_top: 720,
56 dya_hdr_bottom: 720,
57 clm: 0,
58 dya_line_pitch: 0,
59 dxt_char_space: 0,
60 }
61 }
62}
63
64impl Sep {
65 pub fn apply_sepx(&mut self, grpprl: &[u8]) {
66 for sprm in sprm::iterate(grpprl) {
67 self.apply_sprm(&sprm);
68 }
69 }
70
71 fn apply_sprm(&mut self, sprm: &Sprm<'_>) {
72 match sprm.opcode {
73 0x3009 => self.bkc = sprm.byte(),
74 0x300A => self.f_title_page = sprm.byte() == 1,
75 0x500B => self.ccol_m1 = sprm.u16_operand(),
76 0x900C => self.dxa_columns = sprm.u16_operand(),
77 0x3005 => self.f_evenly_spaced = sprm.byte() == 1,
78 0xF203 => {
79 if sprm.operand.len() >= 3 {
80 self.col_widths.push((
81 sprm.operand[0],
82 u16::from_le_bytes([sprm.operand[1], sprm.operand[2]]),
83 ));
84 }
85 }
86 0x300E => self.nfc_pgn = Some(sprm.byte()),
87 0x3011 => self.f_pgn_restart = sprm.byte() == 1,
88 0x501C => self.pgn_start = sprm.u16_operand(),
89 0xB01F => self.xa_page = sprm.u16_operand() as u32,
90 0xB020 => self.ya_page = sprm.u16_operand() as u32,
91 0x301D => self.orientation_landscape = sprm.byte() == 2,
92 0xB021 => self.dxa_left = sprm.u16_operand() as u32,
93 0xB022 => self.dxa_right = sprm.u16_operand() as u32,
94 0x9023 => self.dya_top = sprm.i16_operand() as i32,
95 0x9024 => self.dya_bottom = sprm.i16_operand() as i32,
96 0xB025 => self.dza_gutter = sprm.u16_operand() as u32,
97 0xB017 => self.dya_hdr_top = sprm.u16_operand() as u32,
98 0xB018 => self.dya_hdr_bottom = sprm.u16_operand() as u32,
99 0x5032 => self.clm = sprm.u16_operand(),
100 0x9031 => self.dya_line_pitch = sprm.u16_operand(),
101 0x7030 => self.dxt_char_space = sprm.u32_operand(),
102 _ => {}
103 }
104 }
105
106 pub fn to_sect_pr_inner_xml(&self) -> String {
109 let mut inner = String::new();
110
111 let break_type = match self.bkc {
112 0 => Some("continuous"),
113 1 => Some("nextColumn"),
114 3 => Some("evenPage"),
115 4 => Some("oddPage"),
116 _ => None, };
118 if let Some(val) = break_type {
119 inner.push_str(&format!(r#"<w:type w:val="{val}"/>"#));
120 }
121
122 inner.push_str(&format!(
123 r#"<w:pgSz w:w="{}" w:h="{}"{}/>"#,
124 self.xa_page,
125 self.ya_page,
126 if self.orientation_landscape {
127 r#" w:orient="landscape""#
128 } else {
129 ""
130 }
131 ));
132 inner.push_str(&format!(
133 r#"<w:pgMar w:top="{}" w:right="{}" w:bottom="{}" w:left="{}" w:header="{}" w:footer="{}" w:gutter="{}"/>"#,
134 self.dya_top,
135 self.dxa_right,
136 self.dya_bottom,
137 self.dxa_left,
138 self.dya_hdr_top,
139 self.dya_hdr_bottom,
140 self.dza_gutter
141 ));
142
143 if self.nfc_pgn.is_some() || self.f_pgn_restart {
144 let mut pgn = String::from("<w:pgNumType");
145 if let Some(nfc) = self.nfc_pgn {
146 pgn.push_str(&format!(r#" w:fmt="{}""#, nfc_to_num_fmt(nfc)));
147 }
148 if self.f_pgn_restart {
149 pgn.push_str(&format!(r#" w:start="{}""#, self.pgn_start));
150 }
151 pgn.push_str("/>");
152 inner.push_str(&pgn);
153 }
154
155 if self.ccol_m1 > 0 {
156 let mut cols = format!(
157 r#"<w:cols w:num="{}" w:space="{}""#,
158 self.ccol_m1 + 1,
159 self.dxa_columns
160 );
161 if !self.f_evenly_spaced && !self.col_widths.is_empty() {
162 cols.push_str(r#" w:equalWidth="0">"#);
163 for (_, width) in &self.col_widths {
164 cols.push_str(&format!(
165 r#"<w:col w:w="{width}" w:space="{}"/>"#,
166 self.dxa_columns
167 ));
168 }
169 cols.push_str("</w:cols>");
170 } else {
171 cols.push_str("/>");
172 }
173 inner.push_str(&cols);
174 }
175
176 if self.f_title_page {
177 inner.push_str("<w:titlePg/>");
178 }
179
180 if self.clm != 0 || self.dya_line_pitch != 0 {
181 let grid_type = match self.clm {
182 1 => "linesAndChars",
183 2 => "lines",
184 3 => "snapToChars",
185 _ => "default",
186 };
187 let mut grid = format!(r#"<w:docGrid w:type="{grid_type}""#);
188 if self.dya_line_pitch != 0 {
189 grid.push_str(&format!(r#" w:linePitch="{}""#, self.dya_line_pitch));
190 }
191 if self.dxt_char_space != 0 {
192 grid.push_str(&format!(r#" w:charSpace="{}""#, self.dxt_char_space));
193 }
194 grid.push_str("/>");
195 inner.push_str(&grid);
196 }
197
198 inner
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn emits_page_size_and_grid() {
208 let grpprl: Vec<u8> = vec![
209 0x1F, 0xB0, 0xB0, 0x29, 0x20, 0xB0, 0x84, 0x3D, 0x32, 0x50, 0x01, 0x00, 0x31, 0x90, 0x6F, 0x01, ];
214 let mut sep = Sep::default();
215 sep.apply_sepx(&grpprl);
216 let xml = sep.to_sect_pr_inner_xml();
217 assert!(xml.contains(r#"<w:pgSz w:w="10672" w:h="15748"/>"#));
218 assert!(xml.contains(r#"<w:docGrid w:type="linesAndChars" w:linePitch="367"/>"#));
219 }
220
221 #[test]
222 fn continuous_break_and_columns() {
223 let grpprl: Vec<u8> = vec![
224 0x09, 0x30, 0x00, 0x0B, 0x50, 0x01, 0x00, ];
227 let mut sep = Sep::default();
228 sep.apply_sepx(&grpprl);
229 let xml = sep.to_sect_pr_inner_xml();
230 assert!(xml.contains(r#"<w:type w:val="continuous"/>"#));
231 assert!(xml.contains(r#"<w:cols w:num="2" w:space="708"/>"#));
232 }
233}