1use alloc::{string::String, vec, vec::Vec};
2use core::fmt;
3#[cfg(feature = "std")]
4use std::{
5 io::{self, Write as IoWrite},
6 path::Path,
7};
8
9#[cfg(feature = "std")]
10use atomic_write_file::AtomicWriteFile;
11#[cfg(feature = "image")]
12use image::{
13 ColorType, ImageBuffer, ImageEncoder, Luma,
14 codecs::png::{CompressionType, FilterType, PngEncoder},
15};
16#[cfg(feature = "tokio")]
17use tokio::io::{AsyncWrite as TokioAsyncWrite, AsyncWriteExt};
18
19use crate::{RenderError, Symbol, SymbolVersion};
20
21#[derive(Clone, Copy, Debug)]
23pub struct Renderer<'a> {
24 symbol: &'a Symbol,
25 width: usize,
26 height: usize,
27 quiet_zone: usize,
28 svg_xml_declaration: bool,
29}
30
31impl<'a> Renderer<'a> {
32 #[inline]
34 pub const fn new(symbol: &'a Symbol, size: usize) -> Self {
35 Self::new_with_dimensions(symbol, size, size)
36 }
37
38 #[inline]
40 pub const fn new_with_dimensions(symbol: &'a Symbol, width: usize, height: usize) -> Self {
41 let quiet_zone = match symbol.version() {
42 #[cfg(feature = "qr")]
43 SymbolVersion::Qr(_) => 4,
44 #[cfg(feature = "micro-qr")]
45 SymbolVersion::Micro(_) => 2,
46 #[cfg(feature = "rmqr")]
47 SymbolVersion::Rmqr(_) => 2,
48 };
49
50 Self {
51 symbol,
52 width,
53 height,
54 quiet_zone,
55 svg_xml_declaration: true,
56 }
57 }
58
59 #[must_use]
61 #[inline]
62 pub const fn quiet_zone(mut self, modules: usize) -> Self {
63 self.quiet_zone = modules;
64
65 self
66 }
67
68 #[must_use]
71 #[inline]
72 pub const fn svg_xml_declaration(mut self, enabled: bool) -> Self {
73 self.svg_xml_declaration = enabled;
74
75 self
76 }
77
78 pub fn to_luma8(self) -> Result<Vec<u8>, RenderError> {
80 let layout = self.layout()?;
81 let length = self.width.checked_mul(self.height).ok_or(RenderError::ImageSizeTooLarge)?;
82 let mut image = vec![255; length];
83 let symbol_width = self.symbol.width();
84 let modules = self.symbol.modules();
85
86 for y in 0..self.symbol.height() {
87 let first_row = layout.margin_y + y * layout.scale;
88 let row_start = first_row * self.width;
89
90 let module_row = &modules[y * symbol_width..][..symbol_width];
92
93 let mut x = 0;
95
96 while x < symbol_width {
97 if !module_row[x] {
98 x += 1;
99 continue;
100 }
101
102 let start = x;
103
104 while x < symbol_width && module_row[x] {
105 x += 1;
106 }
107
108 let output_x = layout.margin_x + start * layout.scale;
109
110 image[row_start + output_x..row_start + output_x + (x - start) * layout.scale]
111 .fill(0);
112 }
113
114 for row in 1..layout.scale {
116 let destination = (first_row + row) * self.width;
117
118 image.copy_within(row_start..row_start + self.width, destination);
119 }
120 }
121
122 Ok(image)
123 }
124
125 #[cfg(feature = "std")]
130 pub fn write_svg<W: IoWrite>(
131 self,
132 writer: W,
133 description: Option<impl AsRef<str>>,
134 ) -> Result<(), RenderError> {
135 let description = description.as_ref().map(AsRef::as_ref);
136 let layout = self.layout()?;
137 let mut writer = IoFmtWriter {
138 inner: writer, error: None
139 };
140
141 if self.write_svg_content(&mut writer, description, layout).is_err() {
142 return Err(writer.error.expect("the I/O adapter stores formatting errors").into());
143 }
144
145 writer.inner.flush()?;
146 Ok(())
147 }
148
149 pub fn to_svg_string(
154 self,
155 description: Option<impl AsRef<str>>,
156 ) -> Result<String, RenderError> {
157 let description = description.as_ref().map(AsRef::as_ref);
158 let layout = self.layout()?;
159 let mut svg = String::with_capacity(8192);
160
161 self.write_svg_content(&mut svg, description, layout)
162 .expect("writing an SVG to a String cannot fail");
163
164 Ok(svg)
165 }
166
167 fn write_svg_content<W: fmt::Write>(
168 self,
169 writer: &mut W,
170 description: Option<&str>,
171 layout: Layout,
172 ) -> fmt::Result {
173 if self.svg_xml_declaration {
174 writer.write_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")?;
175 }
176
177 writeln!(
178 writer,
179 "<svg width=\"{}\" height=\"{}\" shape-rendering=\"crispEdges\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">",
180 self.width, self.height
181 )?;
182
183 if let Some(description) = description {
184 if !description.is_empty() {
185 writer.write_str("\t<desc>")?;
186 writer.write_str(&html_escape::encode_safe(description))?;
187 writer.write_str("</desc>\n")?;
188 }
189 } else {
190 writeln!(
191 writer,
192 "\t<desc>{} {} by magiclen.org</desc>",
193 env!("CARGO_PKG_NAME"),
194 env!("CARGO_PKG_VERSION")
195 )?;
196 }
197
198 write!(
199 writer,
200 "\t<rect width=\"{}\" height=\"{}\" fill=\"#FFF\"/>\n\t<path d=\"",
201 self.width, self.height
202 )?;
203
204 let symbol_width = self.symbol.width();
205 let modules = self.symbol.modules();
206
207 for y in 0..self.symbol.height() {
208 let module_row = &modules[y * symbol_width..][..symbol_width];
210
211 let mut x = 0;
213
214 while x < symbol_width {
215 if !module_row[x] {
216 x += 1;
217 continue;
218 }
219
220 let start = x;
221
222 while x < symbol_width && module_row[x] {
223 x += 1;
224 }
225
226 let output_x = layout.margin_x + start * layout.scale;
227 let output_y = layout.margin_y + y * layout.scale;
228 let width = (x - start) * layout.scale;
229
230 write!(
231 writer,
232 "M{output_x} {output_y}h{width}v{}H{output_x}V{output_y}",
233 layout.scale
234 )?;
235 }
236 }
237 writer.write_str("\"/>\n</svg>")
238 }
239
240 #[cfg(feature = "tokio")]
241 pub async fn write_svg_async<W: TokioAsyncWrite + Unpin>(
246 self,
247 mut writer: W,
248 description: Option<impl AsRef<str>>,
249 ) -> Result<(), RenderError> {
250 let svg = self.to_svg_string(description)?;
251
252 writer.write_all(svg.as_bytes()).await?;
253 writer.flush().await?;
254
255 Ok(())
256 }
257
258 #[cfg(feature = "std")]
263 pub fn save_svg(
264 self,
265 path: impl AsRef<Path>,
266 description: Option<impl AsRef<str>>,
267 ) -> Result<(), RenderError> {
268 let mut file = AtomicWriteFile::open(path)?;
269
270 self.write_svg(io::BufWriter::new(&mut file), description)?;
272
273 file.commit()?;
274
275 Ok(())
276 }
277
278 #[cfg(feature = "tokio")]
279 pub async fn save_svg_async(
285 self,
286 path: impl AsRef<Path>,
287 description: Option<impl AsRef<str>>,
288 ) -> Result<(), RenderError> {
289 let svg = self.to_svg_string(description)?;
290
291 save_atomic_blocking(path.as_ref().to_path_buf(), svg.into_bytes()).await
292 }
293
294 #[cfg(feature = "image")]
295 pub fn write_png<W: IoWrite>(self, writer: W) -> Result<(), RenderError> {
297 let width = u32::try_from(self.width).map_err(|_| RenderError::ImageSizeTooLarge)?;
298 let height = u32::try_from(self.height).map_err(|_| RenderError::ImageSizeTooLarge)?;
299 let image = self.to_luma8()?;
300
301 PngEncoder::new_with_quality(writer, CompressionType::Best, FilterType::NoFilter)
302 .write_image(&image, width, height, ColorType::L8.into())?;
303
304 Ok(())
305 }
306
307 #[cfg(feature = "image")]
308 pub fn to_png_vec(self) -> Result<Vec<u8>, RenderError> {
310 let mut bytes = Vec::with_capacity(4096);
311
312 self.write_png(&mut bytes)?;
313
314 Ok(bytes)
315 }
316
317 #[cfg(all(feature = "tokio", feature = "image"))]
318 pub async fn write_png_async<W: TokioAsyncWrite + Unpin>(
320 self,
321 mut writer: W,
322 ) -> Result<(), RenderError> {
323 let png = self.to_png_vec()?;
324
325 writer.write_all(&png).await?;
326 writer.flush().await?;
327
328 Ok(())
329 }
330
331 #[cfg(feature = "image")]
332 pub fn save_png(self, path: impl AsRef<Path>) -> Result<(), RenderError> {
334 let mut file = AtomicWriteFile::open(path)?;
335
336 self.write_png(&mut file)?;
337
338 file.commit()?;
339
340 Ok(())
341 }
342
343 #[cfg(all(feature = "tokio", feature = "image"))]
344 pub async fn save_png_async(self, path: impl AsRef<Path>) -> Result<(), RenderError> {
348 let png = self.to_png_vec()?;
349
350 save_atomic_blocking(path.as_ref().to_path_buf(), png).await
351 }
352
353 #[cfg(feature = "image")]
354 pub fn to_image_buffer(self) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, RenderError> {
356 let width = u32::try_from(self.width).map_err(|_| RenderError::ImageSizeTooLarge)?;
357 let height = u32::try_from(self.height).map_err(|_| RenderError::ImageSizeTooLarge)?;
358 let image = self.to_luma8()?;
359
360 ImageBuffer::from_vec(width, height, image).ok_or(RenderError::ImageSizeTooLarge)
361 }
362
363 fn layout(self) -> Result<Layout, RenderError> {
364 let quiet_zone_modules =
365 self.quiet_zone.checked_mul(2).ok_or(RenderError::ImageSizeTooLarge)?;
366 let modules_width = self
367 .symbol
368 .width()
369 .checked_add(quiet_zone_modules)
370 .ok_or(RenderError::ImageSizeTooLarge)?;
371 let modules_height = self
372 .symbol
373 .height()
374 .checked_add(quiet_zone_modules)
375 .ok_or(RenderError::ImageSizeTooLarge)?;
376
377 let scale = (self.width / modules_width).min(self.height / modules_height);
379
380 if scale == 0 {
381 return Err(RenderError::ImageSizeTooSmall);
382 }
383
384 let symbol_width =
385 self.symbol.width().checked_mul(scale).ok_or(RenderError::ImageSizeTooLarge)?;
386 let symbol_height =
387 self.symbol.height().checked_mul(scale).ok_or(RenderError::ImageSizeTooLarge)?;
388
389 Ok(Layout {
391 scale,
392 margin_x: (self.width - symbol_width) / 2,
393 margin_y: (self.height - symbol_height) / 2,
394 })
395 }
396}
397
398impl fmt::Display for Renderer<'_> {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 let visible_dark = !f.alternate();
408 let quiet_zone = self.quiet_zone;
409 let columns = self.symbol.width() + quiet_zone * 2;
410 let rows = self.symbol.height() + quiet_zone * 2;
411
412 for upper in (0..rows).step_by(2) {
413 for x in 0..columns {
414 let visible = |y: usize| {
415 let dark = x
417 .checked_sub(quiet_zone)
418 .zip(y.checked_sub(quiet_zone))
419 .and_then(|(x, y)| self.symbol.module(x, y))
420 == Some(true);
421
422 dark == visible_dark
423 };
424
425 let lower = upper + 1 < rows && visible(upper + 1);
427
428 f.write_str(match (visible(upper), lower) {
429 (true, true) => "█",
430 (true, false) => "▀",
431 (false, true) => "▄",
432 (false, false) => " ",
433 })?;
434 }
435
436 f.write_str("\n")?;
437 }
438
439 Ok(())
440 }
441}
442
443#[cfg(feature = "std")]
444struct IoFmtWriter<W> {
445 inner: W,
446 error: Option<io::Error>,
447}
448
449#[cfg(feature = "std")]
450impl<W: IoWrite> fmt::Write for IoFmtWriter<W> {
451 fn write_str(&mut self, text: &str) -> fmt::Result {
452 self.inner.write_all(text.as_bytes()).map_err(|error| {
453 self.error = Some(error);
454 fmt::Error
455 })
456 }
457}
458
459#[cfg(feature = "tokio")]
461async fn save_atomic_blocking(path: std::path::PathBuf, bytes: Vec<u8>) -> Result<(), RenderError> {
462 let write = tokio::task::spawn_blocking(move || {
463 let mut file = AtomicWriteFile::open(path)?;
464
465 file.write_all(&bytes)?;
466
467 file.commit()
468 })
469 .await;
470
471 match write {
472 Ok(result) => result.map_err(RenderError::from),
473 Err(join_error) => Err(RenderError::Io(io::Error::other(join_error))),
474 }
475}
476
477struct Layout {
478 scale: usize,
479 margin_x: usize,
480 margin_y: usize,
481}