1use hardless_escpos::{Align, Node};
4
5use crate::codes;
6use crate::html::symbology_name;
7use crate::layout::{layout, Block, Segment};
8use crate::RenderOptions;
9
10pub fn to_text(nodes: &[Node]) -> String {
25 to_text_with(nodes, &RenderOptions::default())
26}
27
28pub fn to_text_with(nodes: &[Node], options: &RenderOptions) -> String {
30 let (columns_a, columns_b) = options.paper.columns();
31 let mut out = String::new();
32 for block in layout(nodes, options.paper, false) {
33 match block {
34 Block::Line(line) => {
35 let text: String = line
36 .segments
37 .iter()
38 .filter_map(|s| match s {
39 Segment::Text { text, .. } => Some(text.as_str()),
40 Segment::Unknown { .. } => None,
41 })
42 .collect();
43 let used = line.columns.div_ceil(columns_b.max(1));
46 push_aligned(&mut out, line.align, &text, used, columns_a);
47 }
48 Block::Cut { .. } => {}
49 Block::Qr { align, text, .. } => {
50 let label = format!("[QR: {}]", printable(&text));
51 push_label(&mut out, align, &label, columns_a);
52 }
53 Block::Barcode {
54 align,
55 symbology,
56 data,
57 ..
58 } => {
59 let label = format!(
60 "[BARCODE {}: {}]",
61 symbology_name(symbology),
62 printable(&codes::barcode_text(symbology, &data))
63 );
64 push_label(&mut out, align, &label, columns_a);
65 }
66 Block::Image {
67 align,
68 width_bytes,
69 height,
70 double_width,
71 double_height,
72 ..
73 } => {
74 let w = u32::from(width_bytes) * 8 * if double_width { 2 } else { 1 };
75 let h = u32::from(height) * if double_height { 2 } else { 1 };
76 push_label(&mut out, align, &format!("[IMAGE {w}x{h}]"), columns_a);
77 }
78 }
79 }
80 out
81}
82
83fn push_label(out: &mut String, align: Align, label: &str, columns: u32) {
84 let used = u32::try_from(label.chars().count()).unwrap_or(u32::MAX);
85 push_aligned(out, align, label, used, columns);
86}
87
88fn push_aligned(out: &mut String, align: Align, text: &str, used: u32, columns: u32) {
89 let free = columns.saturating_sub(used);
90 let pad = match align {
91 Align::Left => 0,
92 Align::Center => free / 2,
93 Align::Right => free,
94 };
95 let line = format!("{}{}", " ".repeat(pad as usize), text);
96 out.push_str(line.trim_end());
97 out.push('\n');
98}
99
100fn printable(text: &str) -> String {
102 text.chars()
103 .map(|c| if c.is_control() { '\u{fffd}' } else { c })
104 .collect()
105}