From cb26cf225233f67392037032b36286bd6c25691d Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Mon, 1 Jul 2024 00:14:13 +0200 Subject: [PATCH] pic: initial support --- Cargo.toml | 7 +- README.md | 244 +++++++++++++++++++++++++++ examples/encode_pic.roff | 16 ++ examples/encode_pic.rs | 11 ++ src/lib.rs | 25 +++ src/render/mod.rs | 1 + src/render/pic.rs | 72 ++++++++ src/test_annex_i_micro_qr_as_pic.pic | 96 +++++++++++ src/test_annex_i_qr_as_pic.pic | 219 ++++++++++++++++++++++++ 9 files changed, 690 insertions(+), 1 deletion(-) create mode 100644 examples/encode_pic.roff create mode 100644 examples/encode_pic.rs create mode 100644 src/render/pic.rs create mode 100644 src/test_annex_i_micro_qr_as_pic.pic create mode 100644 src/test_annex_i_qr_as_pic.pic diff --git a/Cargo.toml b/Cargo.toml index b1d8072..d1049d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,10 @@ image = { version = "0.25", default-features = false, optional = true } image = "0.25" [features] -default = ["image", "svg"] +default = ["image", "svg", "pic"] bench = [] svg = [] +pic = [] [[bin]] name = "qrencode" @@ -41,3 +42,7 @@ name = "encode_string" [[example]] name = "encode_svg" required-features = ["svg"] + +[[example]] +name = "encode_pic" +required-features = ["pic"] diff --git a/README.md b/README.md index dc1275a..f2c639a 100644 --- a/README.md +++ b/README.md @@ -144,3 +144,247 @@ Generates this output: █████████████████████████████ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ``` + +## PIC generation + +```rust +use qrcode::render::pic; +use qrcode::QrCode; + +fn main() { + let code = QrCode::new(b"01234567").unwrap(); + let image = code + .render() + .min_dimensions(1, 1) + .dark_color(pic::Color("#800000")) + .light_color(pic::Color("#ffff80")) + .build(); + println!("{}", image); +} +``` + +Generates [PIC](https://en.wikipedia.org/wiki/PIC_(markup_language)) +output that renders as follows: + +```pic +maxpswid=29;maxpsht=29;movewid=0;moveht=1;boxwid=1;boxht=1 +define p { box wid $3 ht $4 fill 1 with .nw at $1,-$2 } +box wid maxpswid ht maxpsht with .nw at 0,0 +p(4,4,1,1) +p(5,4,1,1) +p(6,4,1,1) +p(7,4,1,1) +p(8,4,1,1) +p(9,4,1,1) +p(10,4,1,1) +p(13,4,1,1) +p(15,4,1,1) +p(16,4,1,1) +p(18,4,1,1) +p(19,4,1,1) +p(20,4,1,1) +p(21,4,1,1) +p(22,4,1,1) +p(23,4,1,1) +p(24,4,1,1) +p(4,5,1,1) +p(10,5,1,1) +p(13,5,1,1) +p(14,5,1,1) +p(15,5,1,1) +p(16,5,1,1) +p(18,5,1,1) +p(24,5,1,1) +p(4,6,1,1) +p(6,6,1,1) +p(7,6,1,1) +p(8,6,1,1) +p(10,6,1,1) +p(12,6,1,1) +p(18,6,1,1) +p(20,6,1,1) +p(21,6,1,1) +p(22,6,1,1) +p(24,6,1,1) +p(4,7,1,1) +p(6,7,1,1) +p(7,7,1,1) +p(8,7,1,1) +p(10,7,1,1) +p(12,7,1,1) +p(13,7,1,1) +p(18,7,1,1) +p(20,7,1,1) +p(21,7,1,1) +p(22,7,1,1) +p(24,7,1,1) +p(4,8,1,1) +p(6,8,1,1) +p(7,8,1,1) +p(8,8,1,1) +p(10,8,1,1) +p(12,8,1,1) +p(14,8,1,1) +p(15,8,1,1) +p(16,8,1,1) +p(18,8,1,1) +p(20,8,1,1) +p(21,8,1,1) +p(22,8,1,1) +p(24,8,1,1) +p(4,9,1,1) +p(10,9,1,1) +p(12,9,1,1) +p(16,9,1,1) +p(18,9,1,1) +p(24,9,1,1) +p(4,10,1,1) +p(5,10,1,1) +p(6,10,1,1) +p(7,10,1,1) +p(8,10,1,1) +p(9,10,1,1) +p(10,10,1,1) +p(12,10,1,1) +p(14,10,1,1) +p(16,10,1,1) +p(18,10,1,1) +p(19,10,1,1) +p(20,10,1,1) +p(21,10,1,1) +p(22,10,1,1) +p(23,10,1,1) +p(24,10,1,1) +p(12,11,1,1) +p(15,11,1,1) +p(16,11,1,1) +p(4,12,1,1) +p(6,12,1,1) +p(7,12,1,1) +p(8,12,1,1) +p(9,12,1,1) +p(10,12,1,1) +p(13,12,1,1) +p(16,12,1,1) +p(18,12,1,1) +p(19,12,1,1) +p(20,12,1,1) +p(21,12,1,1) +p(22,12,1,1) +p(7,13,1,1) +p(9,13,1,1) +p(11,13,1,1) +p(12,13,1,1) +p(14,13,1,1) +p(16,13,1,1) +p(19,13,1,1) +p(21,13,1,1) +p(22,13,1,1) +p(6,14,1,1) +p(10,14,1,1) +p(11,14,1,1) +p(13,14,1,1) +p(15,14,1,1) +p(17,14,1,1) +p(20,14,1,1) +p(21,14,1,1) +p(22,14,1,1) +p(23,14,1,1) +p(24,14,1,1) +p(8,15,1,1) +p(13,15,1,1) +p(19,15,1,1) +p(20,15,1,1) +p(21,15,1,1) +p(22,15,1,1) +p(7,16,1,1) +p(8,16,1,1) +p(9,16,1,1) +p(10,16,1,1) +p(11,16,1,1) +p(12,16,1,1) +p(15,16,1,1) +p(17,16,1,1) +p(20,16,1,1) +p(12,17,1,1) +p(14,17,1,1) +p(15,17,1,1) +p(16,17,1,1) +p(17,17,1,1) +p(18,17,1,1) +p(21,17,1,1) +p(22,17,1,1) +p(4,18,1,1) +p(5,18,1,1) +p(6,18,1,1) +p(7,18,1,1) +p(8,18,1,1) +p(9,18,1,1) +p(10,18,1,1) +p(13,18,1,1) +p(14,18,1,1) +p(16,18,1,1) +p(18,18,1,1) +p(19,18,1,1) +p(4,19,1,1) +p(10,19,1,1) +p(12,19,1,1) +p(14,19,1,1) +p(15,19,1,1) +p(16,19,1,1) +p(17,19,1,1) +p(18,19,1,1) +p(22,19,1,1) +p(24,19,1,1) +p(4,20,1,1) +p(6,20,1,1) +p(7,20,1,1) +p(8,20,1,1) +p(10,20,1,1) +p(12,20,1,1) +p(16,20,1,1) +p(19,20,1,1) +p(21,20,1,1) +p(22,20,1,1) +p(4,21,1,1) +p(6,21,1,1) +p(7,21,1,1) +p(8,21,1,1) +p(10,21,1,1) +p(12,21,1,1) +p(13,21,1,1) +p(16,21,1,1) +p(19,21,1,1) +p(4,22,1,1) +p(6,22,1,1) +p(7,22,1,1) +p(8,22,1,1) +p(10,22,1,1) +p(12,22,1,1) +p(14,22,1,1) +p(15,22,1,1) +p(17,22,1,1) +p(20,22,1,1) +p(22,22,1,1) +p(4,23,1,1) +p(10,23,1,1) +p(19,23,1,1) +p(20,23,1,1) +p(22,23,1,1) +p(23,23,1,1) +p(4,24,1,1) +p(5,24,1,1) +p(6,24,1,1) +p(7,24,1,1) +p(8,24,1,1) +p(9,24,1,1) +p(10,24,1,1) +p(12,24,1,1) +p(13,24,1,1) +p(14,24,1,1) +p(15,24,1,1) +p(17,24,1,1) +p(20,24,1,1) +p(22,24,1,1) + +``` diff --git a/examples/encode_pic.roff b/examples/encode_pic.roff new file mode 100644 index 0000000..8517970 --- /dev/null +++ b/examples/encode_pic.roff @@ -0,0 +1,16 @@ +.\" To convert this file to pdf use: +.\" groff -Tpdf -P-p14.5c,14.5c -p encode_pic.roff > encode_pic.pdf +. +.po -1i +.vs 0 +. +.\" For most flexibility include the QR-Code using copy +.\" and define your preferred picture scale, e.g.: +.PS +# To make the QR-Code smaller make scale a larger number +# To make the QR-Code bigger make scale a smaller number +scale=2.54*2 +# To generate encode_pic.pic run: cargo run --example encode_pic > encode_pic.pic +copy "encode_pic.pic" +.PE +.ex diff --git a/examples/encode_pic.rs b/examples/encode_pic.rs new file mode 100644 index 0000000..7c514e3 --- /dev/null +++ b/examples/encode_pic.rs @@ -0,0 +1,11 @@ +use qrcode::render::pic; +use qrcode::QrCode; + +fn main() { + let code = QrCode::new(b"01234567").unwrap(); + let image = code + .render::() + .min_dimensions(1, 1) + .build(); + println!("{}", image); +} diff --git a/src/lib.rs b/src/lib.rs index a56cb01..790aa8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -374,3 +374,28 @@ mod svg_tests { assert_eq!(&image, expected); } } + +#[cfg(all(test, feature = "pic"))] +mod pic_tests { + use crate::render::pic::Color as PicColor; + use crate::{EcLevel, QrCode, Version}; + + #[test] + fn test_annex_i_qr_as_pic() { + let code = QrCode::new(b"01234567").unwrap(); + let image = code.render::().build(); + let expected = include_str!("test_annex_i_qr_as_pic.pic"); + assert_eq!(&image, expected); + } + + #[test] + fn test_annex_i_micro_qr_as_pic() { + let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); + let image = code + .render::() + .min_dimensions(1, 1) + .build(); + let expected = include_str!("test_annex_i_micro_qr_as_pic.pic"); + assert_eq!(&image, expected); + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index ac92ed3..8c4e854 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -7,6 +7,7 @@ use std::cmp::max; pub mod image; pub mod string; pub mod svg; +pub mod pic; pub mod unicode; //------------------------------------------------------------------------------ diff --git a/src/render/pic.rs b/src/render/pic.rs new file mode 100644 index 0000000..77a0ab6 --- /dev/null +++ b/src/render/pic.rs @@ -0,0 +1,72 @@ +//! PIC rendering support. +//! +//! # Example +//! +//! ``` +//! use qrcode::QrCode; +//! use qrcode::render::pic; +//! +//! let code = QrCode::new(b"Hello").unwrap(); +//! let pic = code.render::().build(); +//! println!("{}", pic); + +#![cfg(feature = "pic")] + +use std::fmt::Write; +use std::marker::PhantomData; + +use crate::render::{Canvas as RenderCanvas, Pixel}; +use crate::types::Color as ModuleColor; + +/// A PIC color. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Color<'a>(pub &'a str); + +impl<'a> Pixel for Color<'a> { + type Canvas = Canvas<'a>; + type Image = String; + + fn default_color(color: ModuleColor) -> Self { + Color(color.select("#000000", "#ffffff")) + } +} + +#[doc(hidden)] +pub struct Canvas<'a> { + pic: String, + marker: PhantomData>, +} + +impl<'a> RenderCanvas for Canvas<'a> { + type Pixel = Color<'a>; + type Image = String; + + fn new(width: u32, height: u32, _dark_pixel: Color<'a>, _light_pixel: Color<'a>) -> Self { + Canvas { + pic: format!( + concat!( + "maxpswid={w};maxpsht={h};movewid=0;moveht=1;boxwid=1;boxht=1\n", + r#"define p {{ box wid $3 ht $4 fill 1 with .nw at $1,-$2 }}"#, + "\n", + r#"box wid maxpswid ht maxpsht with .nw at 0,0"#, + "\n", + ), + w = width, + h = height + ), + marker: PhantomData, + } + } + + fn draw_dark_pixel(&mut self, x: u32, y: u32) { + self.draw_dark_rect(x, y, 1, 1); + } + + fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) { + write!(self.pic, "p({left},{top},{width},{height})\n").unwrap(); + } + + fn into_image(mut self) -> String { + self.pic + } +} diff --git a/src/test_annex_i_micro_qr_as_pic.pic b/src/test_annex_i_micro_qr_as_pic.pic new file mode 100644 index 0000000..a2bed16 --- /dev/null +++ b/src/test_annex_i_micro_qr_as_pic.pic @@ -0,0 +1,96 @@ +maxpswid=17;maxpsht=17;movewid=0;moveht=1;boxwid=1;boxht=1 +define p { box wid $3 ht $4 fill 1 with .nw at $1,-$2 } +box wid maxpswid ht maxpsht with .nw at 0,0 +p(2,2,1,1) +p(3,2,1,1) +p(4,2,1,1) +p(5,2,1,1) +p(6,2,1,1) +p(7,2,1,1) +p(8,2,1,1) +p(10,2,1,1) +p(12,2,1,1) +p(14,2,1,1) +p(2,3,1,1) +p(8,3,1,1) +p(10,3,1,1) +p(11,3,1,1) +p(12,3,1,1) +p(14,3,1,1) +p(2,4,1,1) +p(4,4,1,1) +p(5,4,1,1) +p(6,4,1,1) +p(8,4,1,1) +p(11,4,1,1) +p(12,4,1,1) +p(14,4,1,1) +p(2,5,1,1) +p(4,5,1,1) +p(5,5,1,1) +p(6,5,1,1) +p(8,5,1,1) +p(11,5,1,1) +p(12,5,1,1) +p(13,5,1,1) +p(14,5,1,1) +p(2,6,1,1) +p(4,6,1,1) +p(5,6,1,1) +p(6,6,1,1) +p(8,6,1,1) +p(10,6,1,1) +p(11,6,1,1) +p(12,6,1,1) +p(2,7,1,1) +p(8,7,1,1) +p(10,7,1,1) +p(14,7,1,1) +p(2,8,1,1) +p(3,8,1,1) +p(4,8,1,1) +p(5,8,1,1) +p(6,8,1,1) +p(7,8,1,1) +p(8,8,1,1) +p(11,8,1,1) +p(12,8,1,1) +p(13,8,1,1) +p(14,8,1,1) +p(11,9,1,1) +p(12,9,1,1) +p(2,10,1,1) +p(3,10,1,1) +p(5,10,1,1) +p(10,10,1,1) +p(14,10,1,1) +p(3,11,1,1) +p(4,11,1,1) +p(6,11,1,1) +p(8,11,1,1) +p(10,11,1,1) +p(12,11,1,1) +p(14,11,1,1) +p(2,12,1,1) +p(3,12,1,1) +p(4,12,1,1) +p(7,12,1,1) +p(8,12,1,1) +p(9,12,1,1) +p(10,12,1,1) +p(11,12,1,1) +p(12,12,1,1) +p(13,12,1,1) +p(5,13,1,1) +p(7,13,1,1) +p(12,13,1,1) +p(13,13,1,1) +p(2,14,1,1) +p(3,14,1,1) +p(4,14,1,1) +p(6,14,1,1) +p(9,14,1,1) +p(10,14,1,1) +p(12,14,1,1) +p(13,14,1,1) +p(14,14,1,1) diff --git a/src/test_annex_i_qr_as_pic.pic b/src/test_annex_i_qr_as_pic.pic new file mode 100644 index 0000000..7ab7031 --- /dev/null +++ b/src/test_annex_i_qr_as_pic.pic @@ -0,0 +1,219 @@ +maxpswid=232;maxpsht=232;movewid=0;moveht=1;boxwid=1;boxht=1 +define p { box wid $3 ht $4 fill 1 with .nw at $1,-$2 } +box wid maxpswid ht maxpsht with .nw at 0,0 +p(32,32,8,8) +p(40,32,8,8) +p(48,32,8,8) +p(56,32,8,8) +p(64,32,8,8) +p(72,32,8,8) +p(80,32,8,8) +p(104,32,8,8) +p(120,32,8,8) +p(128,32,8,8) +p(144,32,8,8) +p(152,32,8,8) +p(160,32,8,8) +p(168,32,8,8) +p(176,32,8,8) +p(184,32,8,8) +p(192,32,8,8) +p(32,40,8,8) +p(80,40,8,8) +p(104,40,8,8) +p(112,40,8,8) +p(120,40,8,8) +p(128,40,8,8) +p(144,40,8,8) +p(192,40,8,8) +p(32,48,8,8) +p(48,48,8,8) +p(56,48,8,8) +p(64,48,8,8) +p(80,48,8,8) +p(96,48,8,8) +p(144,48,8,8) +p(160,48,8,8) +p(168,48,8,8) +p(176,48,8,8) +p(192,48,8,8) +p(32,56,8,8) +p(48,56,8,8) +p(56,56,8,8) +p(64,56,8,8) +p(80,56,8,8) +p(96,56,8,8) +p(104,56,8,8) +p(144,56,8,8) +p(160,56,8,8) +p(168,56,8,8) +p(176,56,8,8) +p(192,56,8,8) +p(32,64,8,8) +p(48,64,8,8) +p(56,64,8,8) +p(64,64,8,8) +p(80,64,8,8) +p(96,64,8,8) +p(112,64,8,8) +p(120,64,8,8) +p(128,64,8,8) +p(144,64,8,8) +p(160,64,8,8) +p(168,64,8,8) +p(176,64,8,8) +p(192,64,8,8) +p(32,72,8,8) +p(80,72,8,8) +p(96,72,8,8) +p(128,72,8,8) +p(144,72,8,8) +p(192,72,8,8) +p(32,80,8,8) +p(40,80,8,8) +p(48,80,8,8) +p(56,80,8,8) +p(64,80,8,8) +p(72,80,8,8) +p(80,80,8,8) +p(96,80,8,8) +p(112,80,8,8) +p(128,80,8,8) +p(144,80,8,8) +p(152,80,8,8) +p(160,80,8,8) +p(168,80,8,8) +p(176,80,8,8) +p(184,80,8,8) +p(192,80,8,8) +p(96,88,8,8) +p(120,88,8,8) +p(128,88,8,8) +p(32,96,8,8) +p(48,96,8,8) +p(56,96,8,8) +p(64,96,8,8) +p(72,96,8,8) +p(80,96,8,8) +p(104,96,8,8) +p(128,96,8,8) +p(144,96,8,8) +p(152,96,8,8) +p(160,96,8,8) +p(168,96,8,8) +p(176,96,8,8) +p(56,104,8,8) +p(72,104,8,8) +p(88,104,8,8) +p(96,104,8,8) +p(112,104,8,8) +p(128,104,8,8) +p(152,104,8,8) +p(168,104,8,8) +p(176,104,8,8) +p(48,112,8,8) +p(80,112,8,8) +p(88,112,8,8) +p(104,112,8,8) +p(120,112,8,8) +p(136,112,8,8) +p(160,112,8,8) +p(168,112,8,8) +p(176,112,8,8) +p(184,112,8,8) +p(192,112,8,8) +p(64,120,8,8) +p(104,120,8,8) +p(152,120,8,8) +p(160,120,8,8) +p(168,120,8,8) +p(176,120,8,8) +p(56,128,8,8) +p(64,128,8,8) +p(72,128,8,8) +p(80,128,8,8) +p(88,128,8,8) +p(96,128,8,8) +p(120,128,8,8) +p(136,128,8,8) +p(160,128,8,8) +p(96,136,8,8) +p(112,136,8,8) +p(120,136,8,8) +p(128,136,8,8) +p(136,136,8,8) +p(144,136,8,8) +p(168,136,8,8) +p(176,136,8,8) +p(32,144,8,8) +p(40,144,8,8) +p(48,144,8,8) +p(56,144,8,8) +p(64,144,8,8) +p(72,144,8,8) +p(80,144,8,8) +p(104,144,8,8) +p(112,144,8,8) +p(128,144,8,8) +p(144,144,8,8) +p(152,144,8,8) +p(32,152,8,8) +p(80,152,8,8) +p(96,152,8,8) +p(112,152,8,8) +p(120,152,8,8) +p(128,152,8,8) +p(136,152,8,8) +p(144,152,8,8) +p(176,152,8,8) +p(192,152,8,8) +p(32,160,8,8) +p(48,160,8,8) +p(56,160,8,8) +p(64,160,8,8) +p(80,160,8,8) +p(96,160,8,8) +p(128,160,8,8) +p(152,160,8,8) +p(168,160,8,8) +p(176,160,8,8) +p(32,168,8,8) +p(48,168,8,8) +p(56,168,8,8) +p(64,168,8,8) +p(80,168,8,8) +p(96,168,8,8) +p(104,168,8,8) +p(128,168,8,8) +p(152,168,8,8) +p(32,176,8,8) +p(48,176,8,8) +p(56,176,8,8) +p(64,176,8,8) +p(80,176,8,8) +p(96,176,8,8) +p(112,176,8,8) +p(120,176,8,8) +p(136,176,8,8) +p(160,176,8,8) +p(176,176,8,8) +p(32,184,8,8) +p(80,184,8,8) +p(152,184,8,8) +p(160,184,8,8) +p(176,184,8,8) +p(184,184,8,8) +p(32,192,8,8) +p(40,192,8,8) +p(48,192,8,8) +p(56,192,8,8) +p(64,192,8,8) +p(72,192,8,8) +p(80,192,8,8) +p(96,192,8,8) +p(104,192,8,8) +p(112,192,8,8) +p(120,192,8,8) +p(136,192,8,8) +p(160,192,8,8) +p(176,192,8,8)