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 write_xml_text(writer, 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 let mut writer = ErrorKeepingWriter {
301 inner: writer, error: None
302 };
303 let result =
304 PngEncoder::new_with_quality(&mut writer, CompressionType::Best, FilterType::NoFilter)
305 .write_image(&image, width, height, ColorType::L8.into());
306
307 if let Some(error) = writer.error.take() {
309 return Err(error.into());
310 }
311
312 result?;
313 writer.inner.flush()?;
314 Ok(())
315 }
316
317 #[cfg(feature = "image")]
318 pub fn to_png_vec(self) -> Result<Vec<u8>, RenderError> {
320 let mut bytes = Vec::with_capacity(4096);
321
322 self.write_png(&mut bytes)?;
323
324 Ok(bytes)
325 }
326
327 #[cfg(all(feature = "tokio", feature = "image"))]
328 pub async fn write_png_async<W: TokioAsyncWrite + Unpin>(
330 self,
331 mut writer: W,
332 ) -> Result<(), RenderError> {
333 let png = self.to_png_vec()?;
334
335 writer.write_all(&png).await?;
336 writer.flush().await?;
337
338 Ok(())
339 }
340
341 #[cfg(feature = "image")]
342 pub fn save_png(self, path: impl AsRef<Path>) -> Result<(), RenderError> {
344 let mut file = AtomicWriteFile::open(path)?;
345
346 self.write_png(&mut file)?;
347
348 file.commit()?;
349
350 Ok(())
351 }
352
353 #[cfg(all(feature = "tokio", feature = "image"))]
354 pub async fn save_png_async(self, path: impl AsRef<Path>) -> Result<(), RenderError> {
358 let png = self.to_png_vec()?;
359
360 save_atomic_blocking(path.as_ref().to_path_buf(), png).await
361 }
362
363 #[cfg(feature = "image")]
364 pub fn to_image_buffer(self) -> Result<ImageBuffer<Luma<u8>, Vec<u8>>, RenderError> {
366 let width = u32::try_from(self.width).map_err(|_| RenderError::ImageSizeTooLarge)?;
367 let height = u32::try_from(self.height).map_err(|_| RenderError::ImageSizeTooLarge)?;
368 let image = self.to_luma8()?;
369
370 ImageBuffer::from_vec(width, height, image).ok_or(RenderError::ImageSizeTooLarge)
371 }
372
373 fn layout(self) -> Result<Layout, RenderError> {
374 let quiet_zone_modules =
375 self.quiet_zone.checked_mul(2).ok_or(RenderError::ImageSizeTooLarge)?;
376 let modules_width = self
377 .symbol
378 .width()
379 .checked_add(quiet_zone_modules)
380 .ok_or(RenderError::ImageSizeTooLarge)?;
381 let modules_height = self
382 .symbol
383 .height()
384 .checked_add(quiet_zone_modules)
385 .ok_or(RenderError::ImageSizeTooLarge)?;
386
387 let scale = (self.width / modules_width).min(self.height / modules_height);
389
390 if scale == 0 {
391 return Err(RenderError::ImageSizeTooSmall);
392 }
393
394 let symbol_width =
395 self.symbol.width().checked_mul(scale).ok_or(RenderError::ImageSizeTooLarge)?;
396 let symbol_height =
397 self.symbol.height().checked_mul(scale).ok_or(RenderError::ImageSizeTooLarge)?;
398
399 Ok(Layout {
401 scale,
402 margin_x: (self.width - symbol_width) / 2,
403 margin_y: (self.height - symbol_height) / 2,
404 })
405 }
406}
407
408impl fmt::Display for Renderer<'_> {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 let visible_dark = !f.alternate();
419 let quiet_zone = self.quiet_zone;
420 let quiet_zone_modules = quiet_zone.checked_mul(2);
421 let with_quiet_zone = |modules: usize| {
422 quiet_zone_modules
423 .and_then(|margin| modules.checked_add(margin))
424 .expect("the quiet zone is too large")
425 };
426 let columns = with_quiet_zone(self.symbol.width());
427 let rows = with_quiet_zone(self.symbol.height());
428
429 for upper in (0..rows).step_by(2) {
430 for x in 0..columns {
431 let visible = |y: usize| {
432 let dark = x
434 .checked_sub(quiet_zone)
435 .zip(y.checked_sub(quiet_zone))
436 .and_then(|(x, y)| self.symbol.module(x, y))
437 == Some(true);
438
439 dark == visible_dark
440 };
441
442 let lower = upper + 1 < rows && visible(upper + 1);
444
445 f.write_str(match (visible(upper), lower) {
446 (true, true) => "█",
447 (true, false) => "▀",
448 (false, true) => "▄",
449 (false, false) => " ",
450 })?;
451 }
452
453 f.write_str("\n")?;
454 }
455
456 Ok(())
457 }
458}
459
460fn write_xml_text<W: fmt::Write>(writer: &mut W, text: &str) -> fmt::Result {
463 let mut start = 0;
464
465 for (index, c) in text.char_indices() {
466 let replacement = match c {
467 '&' => "&",
468 '<' => "<",
469 '>' => ">",
471 '\r' => "
",
473 '\t' | '\n' => continue,
474 '\u{0}'..='\u{1F}' | '\u{FFFE}' | '\u{FFFF}' => "\u{FFFD}",
475 _ => continue,
476 };
477
478 writer.write_str(&text[start..index])?;
479 writer.write_str(replacement)?;
480 start = index + c.len_utf8();
481 }
482
483 writer.write_str(&text[start..])
484}
485
486#[cfg(feature = "std")]
487struct IoFmtWriter<W> {
488 inner: W,
489 error: Option<io::Error>,
490}
491
492#[cfg(feature = "std")]
493impl<W: IoWrite> fmt::Write for IoFmtWriter<W> {
494 fn write_str(&mut self, text: &str) -> fmt::Result {
495 self.inner.write_all(text.as_bytes()).map_err(|error| {
496 self.error = Some(error);
497 fmt::Error
498 })
499 }
500}
501
502#[cfg(feature = "image")]
504struct ErrorKeepingWriter<W> {
505 inner: W,
506 error: Option<io::Error>,
507}
508
509#[cfg(feature = "image")]
510impl<W: IoWrite> ErrorKeepingWriter<W> {
511 fn keep<T>(&mut self, result: io::Result<T>) -> io::Result<T> {
512 result.map_err(|error| {
513 let kind = error.kind();
514
515 self.error.get_or_insert(error);
516 kind.into()
517 })
518 }
519}
520
521#[cfg(feature = "image")]
522impl<W: IoWrite> IoWrite for ErrorKeepingWriter<W> {
523 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
524 match self.inner.write(bytes) {
525 Err(error) if error.kind() == io::ErrorKind::Interrupted => Err(error),
527 result => self.keep(result),
528 }
529 }
530
531 fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
532 let result = self.inner.write_all(bytes);
533
534 self.keep(result)
535 }
536
537 fn flush(&mut self) -> io::Result<()> {
538 let result = self.inner.flush();
539
540 self.keep(result)
541 }
542}
543
544#[cfg(feature = "tokio")]
546async fn save_atomic_blocking(path: std::path::PathBuf, bytes: Vec<u8>) -> Result<(), RenderError> {
547 let write = tokio::task::spawn_blocking(move || {
548 let mut file = AtomicWriteFile::open(path)?;
549
550 file.write_all(&bytes)?;
551
552 file.commit()
553 })
554 .await;
555
556 match write {
557 Ok(result) => result.map_err(RenderError::from),
558 Err(join_error) => Err(RenderError::Io(io::Error::other(join_error))),
559 }
560}
561
562struct Layout {
563 scale: usize,
564 margin_x: usize,
565 margin_y: usize,
566}