1use crate::elements::barcode_128::{Barcode128, Barcode128WithData, BarcodeMode};
2use crate::elements::barcode_2of5::{Barcode2of5, Barcode2of5WithData};
3use crate::elements::barcode_39::{Barcode39, Barcode39WithData};
4use crate::elements::barcode_ean13::{BarcodeEan13, BarcodeEan13WithData};
5use crate::elements::field_orientation::FieldOrientation;
6use crate::elements::font::FontInfo;
7use crate::elements::graphic_box::GraphicBox;
8use crate::elements::label_element::LabelElement;
9use crate::elements::label_info::LabelInfo;
10use crate::elements::label_position::LabelPosition;
11use crate::elements::line_color::LineColor;
12use crate::elements::reverse_print::ReversePrint;
13use crate::elements::text_field::TextField;
14
15pub struct EplParser;
16
17impl Default for EplParser {
18 fn default() -> Self {
19 Self
20 }
21}
22
23impl EplParser {
24 pub fn new() -> Self {
25 EplParser
26 }
27
28 pub fn parse(&self, epl_data: &[u8]) -> Result<Vec<LabelInfo>, String> {
29 let data_str = String::from_utf8_lossy(epl_data);
30 let lines: Vec<&str> = data_str.split('\n').collect();
31
32 let mut results = Vec::new();
33 let mut current_elements: Vec<LabelElement> = Vec::new();
34 let mut ref_x = 0i32;
35 let mut ref_y = 0i32;
36
37 for raw_line in &lines {
38 let line = raw_line.trim_end_matches('\r').trim();
39 if line.is_empty() {
40 continue;
41 }
42
43 if line == "N" {
44 current_elements.clear();
45 ref_x = 0;
46 ref_y = 0;
47 continue;
48 }
49
50 if is_epl_reference_point(line) {
51 let parts: Vec<&str> = line[1..].splitn(2, ',').collect();
52 if let Some(s) = parts.first() {
53 ref_x = s.trim().parse().unwrap_or(0);
54 }
55 if let Some(s) = parts.get(1) {
56 ref_y = s.trim().parse().unwrap_or(0);
57 }
58 continue;
59 }
60
61 if line.starts_with('A') {
62 if let Some(el) = parse_epl_text(line, ref_x, ref_y)? {
63 current_elements.push(el);
64 }
65 continue;
66 }
67
68 if line.starts_with('B') {
69 if let Some(el) = parse_epl_barcode(line, ref_x, ref_y)? {
70 current_elements.push(el);
71 }
72 continue;
73 }
74
75 if line.starts_with("LO") {
76 if let Some(el) = parse_epl_line(line, ref_x, ref_y)? {
77 current_elements.push(el);
78 }
79 continue;
80 }
81
82 if is_epl_print_command(line) {
83 if !current_elements.is_empty() {
84 results.push(LabelInfo {
85 print_width: 0,
86 inverted: false,
87 elements: current_elements.clone(),
88 });
89 }
90 current_elements.clear();
91 }
92 }
93
94 if !current_elements.is_empty() {
96 results.push(LabelInfo {
97 print_width: 0,
98 inverted: false,
99 elements: current_elements,
100 });
101 }
102
103 Ok(results)
104 }
105}
106
107fn is_epl_reference_point(line: &str) -> bool {
108 let bytes = line.as_bytes();
109 bytes.len() > 1 && bytes[0] == b'R' && bytes[1].is_ascii_digit()
110}
111
112fn is_epl_print_command(line: &str) -> bool {
113 let bytes = line.as_bytes();
114 if bytes.is_empty() || bytes[0] != b'P' {
115 return false;
116 }
117 if bytes.len() == 1 {
118 return true;
119 }
120 bytes[1..].iter().all(|b| b.is_ascii_digit())
121}
122
123fn epl_rotation(rotation: i32) -> FieldOrientation {
124 match rotation {
125 1 => FieldOrientation::Rotated90,
126 2 => FieldOrientation::Rotated180,
127 3 => FieldOrientation::Rotated270,
128 _ => FieldOrientation::Normal,
129 }
130}
131
132static EPL_FONT_SIZES: &[(i32, i32, i32)] = &[
133 (1, 8, 12),
135 (2, 10, 16),
136 (3, 12, 20),
137 (4, 14, 24),
138 (5, 32, 48),
139];
140
141fn epl_font_size(font_num: i32) -> (i32, i32) {
142 for &(n, w, h) in EPL_FONT_SIZES {
143 if n == font_num {
144 return (w, h);
145 }
146 }
147 (8, 12) }
149
150fn parse_epl_text(line: &str, ref_x: i32, ref_y: i32) -> Result<Option<LabelElement>, String> {
151 let data_start = line.find('"');
152 let data_end = line.rfind('"');
153 match (data_start, data_end) {
154 (Some(s), Some(e)) if e > s => {
155 let text = &line[s + 1..e];
156 if text.is_empty() {
157 return Ok(None);
158 }
159
160 let param_str = line[1..s].trim_end_matches(',');
161 let parts: Vec<&str> = param_str.split(',').collect();
162
163 if parts.len() < 7 {
164 return Err(format!(
165 "EPL A command requires at least 7 parameters, got {}",
166 parts.len()
167 ));
168 }
169
170 let x: i32 = parts[0].trim().parse().unwrap_or(0);
171 let y: i32 = parts[1].trim().parse().unwrap_or(0);
172 let rotation: i32 = parts[2].trim().parse().unwrap_or(0);
173 let font_num: i32 = parts[3].trim().parse().unwrap_or(1);
174 let h_mult: i32 = parts[4].trim().parse::<i32>().unwrap_or(1).max(1);
175 let v_mult: i32 = parts[5].trim().parse::<i32>().unwrap_or(1).max(1);
176 let reverse = parts[6].trim();
177
178 let (base_w, base_h) = epl_font_size(font_num);
179
180 let font_height = (base_h * v_mult) as f64;
181 let font_width = if h_mult != v_mult {
182 font_height * (h_mult * base_w) as f64 / (v_mult * base_h) as f64
183 } else {
184 font_height
185 };
186
187 Ok(Some(LabelElement::Text(TextField {
188 reverse_print: ReversePrint {
189 value: reverse == "R",
190 },
191 font: FontInfo {
192 name: "0".to_string(),
193 width: font_width,
194 height: font_height,
195 orientation: epl_rotation(rotation),
196 },
197 position: LabelPosition {
198 x: x + ref_x,
199 y: y + ref_y,
200 ..Default::default()
201 },
202 text: text.to_string(),
203 alignment: Default::default(),
204 block: None,
205 })))
206 }
207 _ => Ok(None),
208 }
209}
210
211fn parse_epl_barcode(line: &str, ref_x: i32, ref_y: i32) -> Result<Option<LabelElement>, String> {
212 let data_start = line.find('"');
213 let data_end = line.rfind('"');
214 match (data_start, data_end) {
215 (Some(s), Some(e)) if e > s => {
216 let data = &line[s + 1..e];
217 if data.is_empty() {
218 return Ok(None);
219 }
220
221 let param_str = line[1..s].trim_end_matches(',');
222 let parts: Vec<&str> = param_str.split(',').collect();
223
224 if parts.len() < 8 {
225 return Err(format!(
226 "EPL B command requires at least 8 parameters, got {}",
227 parts.len()
228 ));
229 }
230
231 let x: i32 = parts[0].trim().parse().unwrap_or(0);
232 let y: i32 = parts[1].trim().parse().unwrap_or(0);
233 let rotation: i32 = parts[2].trim().parse().unwrap_or(0);
234 let bc_type = parts[3].trim();
235 let narrow_bar: i32 = parts[4].trim().parse::<i32>().unwrap_or(1).max(1);
236 let wide_bar: i32 = parts[5].trim().parse().unwrap_or(2);
237 let height: i32 = parts[6].trim().parse::<i32>().unwrap_or(10).max(1);
238 let human_readable = parts[7].trim();
239
240 let pos = LabelPosition {
241 x: x + ref_x,
242 y: y + ref_y,
243 ..Default::default()
244 };
245 let orient = epl_rotation(rotation);
246 let show_line = human_readable == "B";
247 let width_ratio = (wide_bar as f64 / narrow_bar as f64).max(2.0);
248
249 let el = match bc_type {
250 "0" => LabelElement::Barcode39(Barcode39WithData {
251 reverse_print: ReversePrint::default(),
252 barcode: Barcode39 {
253 orientation: orient,
254 height,
255 line: show_line,
256 line_above: false,
257 check_digit: false,
258 },
259 width: narrow_bar,
260 width_ratio,
261 position: pos,
262 data: data.to_string(),
263 }),
264 "B" => LabelElement::BarcodeEan13(BarcodeEan13WithData {
265 reverse_print: ReversePrint::default(),
266 barcode: BarcodeEan13 {
267 orientation: orient,
268 height,
269 line: show_line,
270 line_above: false,
271 },
272 width: narrow_bar,
273 position: pos,
274 data: data.to_string(),
275 }),
276 "G" | "H" => LabelElement::Barcode2of5(Barcode2of5WithData {
277 reverse_print: ReversePrint::default(),
278 barcode: Barcode2of5 {
279 orientation: orient,
280 height,
281 line: show_line,
282 line_above: false,
283 check_digit: false,
284 },
285 width: narrow_bar,
286 width_ratio,
287 position: pos,
288 data: data.to_string(),
289 }),
290 _ => {
291 LabelElement::Barcode128(Barcode128WithData {
293 reverse_print: ReversePrint::default(),
294 barcode: Barcode128 {
295 orientation: orient,
296 height,
297 line: show_line,
298 line_above: false,
299 check_digit: false,
300 mode: BarcodeMode::Automatic,
301 },
302 width: narrow_bar,
303 position: pos,
304 data: data.to_string(),
305 })
306 }
307 };
308
309 Ok(Some(el))
310 }
311 _ => Ok(None),
312 }
313}
314
315fn parse_epl_line(line: &str, ref_x: i32, ref_y: i32) -> Result<Option<LabelElement>, String> {
316 let param_str = &line[2..]; let parts: Vec<&str> = param_str.split(',').collect();
318
319 if parts.len() < 4 {
320 return Err(format!(
321 "EPL LO command requires 4 parameters, got {}",
322 parts.len()
323 ));
324 }
325
326 let x: i32 = parts[0].trim().parse().unwrap_or(0);
327 let y: i32 = parts[1].trim().parse().unwrap_or(0);
328 let width: i32 = parts[2].trim().parse::<i32>().unwrap_or(1).max(1);
329 let height: i32 = parts[3].trim().parse::<i32>().unwrap_or(1).max(1);
330
331 Ok(Some(LabelElement::GraphicBox(GraphicBox {
332 position: LabelPosition {
333 x: x + ref_x,
334 y: y + ref_y,
335 ..Default::default()
336 },
337 width,
338 height,
339 border_thickness: width.min(height),
340 corner_rounding: 0,
341 line_color: LineColor::Black,
342 reverse_print: ReversePrint::default(),
343 })))
344}