1#[cfg(not(feature = "std"))]
4#[allow(unused_imports)]
5use alloc::{
6 borrow::ToOwned,
7 format,
8 string::{String, ToString},
9 vec,
10 vec::Vec,
11};
12
13use crate::{Canvas as RenderCanvas, Pixel, RenderError, check_buffer_size, checked_area};
14use qrcode_core::Color;
15
16pub trait Element: Copy {
21 fn default_color(color: Color) -> Self;
23 fn strlen(self) -> usize;
27 fn append_to_string(self, string: &mut String);
29}
30
31impl Element for char {
32 fn default_color(color: Color) -> Self {
33 color.select('\u{2588}', ' ')
34 }
35
36 fn strlen(self) -> usize {
37 self.len_utf8()
38 }
39
40 fn append_to_string(self, string: &mut String) {
41 string.push(self);
42 }
43}
44
45impl Element for &str {
46 fn default_color(color: Color) -> Self {
47 color.select("\u{2588}", " ")
48 }
49
50 fn strlen(self) -> usize {
51 self.len()
52 }
53
54 fn append_to_string(self, string: &mut String) {
55 string.push_str(self);
56 }
57}
58
59#[doc(hidden)]
60pub struct Canvas<P: Element> {
61 buffer: Vec<P>,
62 width: usize,
63 dark_pixel: P,
64 dark_byte_len: usize,
65 capacity: usize,
66}
67
68fn layout<P: Element>(width: u32, height: u32, dark: P, light: P) -> Result<(usize, usize), RenderError> {
69 let area = checked_area(width, height)?;
70 let newlines = if area == 0 { 0 } else { height as usize - 1 };
71 let buffer_bytes = area.checked_mul(core::mem::size_of::<P>()).ok_or(RenderError::OutputTooLarge)?;
72 let output_bytes = area
73 .checked_mul(dark.strlen().max(light.strlen()))
74 .and_then(|bytes| bytes.checked_add(newlines))
75 .ok_or(RenderError::OutputTooLarge)?;
76 check_buffer_size(buffer_bytes.checked_add(output_bytes).ok_or(RenderError::OutputTooLarge)?)?;
77 let capacity = area
78 .checked_mul(light.strlen())
79 .and_then(|bytes| bytes.checked_add(newlines))
80 .ok_or(RenderError::OutputTooLarge)?;
81 Ok((area, capacity))
82}
83
84impl<P: Element> Pixel for P {
85 type Image = String;
86 type Canvas = Canvas<Self>;
87
88 fn default_unit_size() -> (u32, u32) {
89 (1, 1)
90 }
91
92 fn default_color(color: Color) -> Self {
93 <Self as Element>::default_color(color)
94 }
95}
96
97impl<P: Element> RenderCanvas for Canvas<P> {
98 type Pixel = P;
99 type Image = String;
100
101 fn new(width: u32, height: u32, dark_pixel: P, light_pixel: P) -> Self {
102 let (area, capacity) = layout(width, height, dark_pixel, light_pixel).unwrap_or_else(|error| panic!("{error}"));
103 Self {
104 buffer: vec![light_pixel; area],
105 width: width as usize,
106 dark_pixel,
107 dark_byte_len: dark_pixel.strlen(),
108 capacity,
109 }
110 }
111
112 fn validate_dimensions(width: u32, height: u32, dark: &P, light: &P) -> Result<(), RenderError> {
113 layout(width, height, *dark, *light).map(|_| ())
114 }
115
116 fn draw_dark_pixel(&mut self, x: u32, y: u32) {
117 let pixel = &mut self.buffer[x as usize + y as usize * self.width];
118 self.capacity = self.capacity - pixel.strlen() + self.dark_byte_len;
119 *pixel = self.dark_pixel;
120 }
121
122 fn into_image(self) -> String {
123 let mut result = String::with_capacity(self.capacity);
124 if self.buffer.is_empty() {
125 return result;
126 }
127 for (row_index, row) in self.buffer.chunks(self.width).enumerate() {
128 if row_index != 0 {
129 result.push('\n');
130 }
131 for &pixel in row {
132 pixel.append_to_string(&mut result);
133 }
134 }
135 result
136 }
137}
138
139#[test]
140fn test_render_to_string() {
141 use crate::Renderer;
142
143 let colors = &[Color::Dark, Color::Light, Color::Light, Color::Dark];
144 let image: String = Renderer::<char>::new(colors, 2, 1).build();
145 assert_eq!(&image, " \n \u{2588} \n \u{2588} \n ");
146
147 let image2 = Renderer::new(colors, 2, 1).light_color("A").dark_color("!B!").module_dimensions(2, 2).build();
148
149 assert_eq!(
150 &image2,
151 "AAAAAAAA\n\
152 AAAAAAAA\n\
153 AA!B!!B!AAAA\n\
154 AA!B!!B!AAAA\n\
155 AAAA!B!!B!AA\n\
156 AAAA!B!!B!AA\n\
157 AAAAAAAA\n\
158 AAAAAAAA"
159 );
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::{MAX_BUFFER_BYTES, Renderer};
166 use core::cell::RefCell;
167
168 #[derive(Clone, Copy)]
169 struct ObservedElement<'a> {
170 value: char,
171 calls: &'a RefCell<Vec<(char, char, usize)>>,
172 }
173
174 impl Element for ObservedElement<'_> {
175 fn default_color(_color: Color) -> Self {
176 panic!("the callback regression uses explicit pixels")
177 }
178
179 fn strlen(self) -> usize {
180 self.calls.borrow_mut().push(('s', self.value, 0));
181 self.value.len_utf8()
182 }
183
184 fn append_to_string(self, output: &mut String) {
185 self.calls.borrow_mut().push(('a', self.value, output.len()));
186 output.push(self.value);
187 }
188 }
189
190 #[test]
191 fn row_serialization_preserves_custom_element_callbacks_and_newline_positions() {
192 let calls = RefCell::new(Vec::new());
193 let dark = ObservedElement { value: '🦀', calls: &calls };
194 let light = ObservedElement { value: 'é', calls: &calls };
195 let mut canvas = Canvas::new(3, 2, dark, light);
196 canvas.draw_dark_pixel(1, 0);
197 canvas.draw_dark_pixel(2, 1);
198 calls.borrow_mut().clear();
199
200 assert_eq!(canvas.into_image(), "é🦀é\néé🦀");
201 assert_eq!(
202 *calls.borrow(),
203 [('a', 'é', 0), ('a', '🦀', 2), ('a', 'é', 6), ('a', 'é', 9), ('a', 'é', 11), ('a', '🦀', 13)]
204 );
205 }
206
207 #[test]
208 fn character_budget_includes_buffer_and_output() {
209 let width = (MAX_BUFFER_BYTES / 5) as u32;
210 assert!(Canvas::<char>::validate_dimensions(width, 1, &'#', &' ').is_ok());
211 assert_eq!(Canvas::<char>::validate_dimensions(width + 1, 1, &'#', &' '), Err(RenderError::OutputTooLarge));
212 assert_eq!(Canvas::<char>::validate_dimensions(65_536, 65_536, &'#', &' '), Err(RenderError::OutputTooLarge));
213 }
214
215 #[test]
216 fn long_utf8_fragments_are_rejected_before_allocating() {
217 let fragment = "💖".repeat(1024);
218 let modules = [Color::Dark];
219 let result = Renderer::new(&modules, 1, 0)
220 .dark_color(fragment.as_str())
221 .light_color("")
222 .module_dimensions(1024, 1024)
223 .try_build();
224 assert_eq!(result, Err(RenderError::OutputTooLarge));
225 }
226
227 #[test]
228 fn repainting_shorter_pixels_keeps_the_actual_output_capacity() {
229 let mut canvas = Canvas::new(2, 2, "", "long");
230 for _ in 0..128 {
231 canvas.draw_dark_pixel(0, 0);
232 }
233 assert_eq!(canvas.capacity, 13);
234 assert_eq!(canvas.into_image(), "long\nlonglong");
235 }
236
237 #[test]
238 fn repainting_multibyte_characters_counts_the_replaced_pixel_once() {
239 let mut canvas = Canvas::new(2, 2, '😀', ' ');
240 for _ in 0..128 {
241 canvas.draw_dark_pixel(1, 1);
242 }
243 assert_eq!(canvas.capacity, 8);
244 assert_eq!(canvas.into_image(), " \n 😀");
245 }
246
247 #[test]
248 fn empty_canvases_produce_empty_text() {
249 for (width, height) in [(0, 0), (0, u32::MAX), (u32::MAX, 0)] {
250 assert_eq!(Canvas::new(width, height, 'â–ˆ', ' ').into_image(), "");
251 }
252 }
253}