dev_kit/command/qrcode/
generator.rs1use crate::command::qrcode::{OutputType, QrContent, QrEcLevel, QrVersion};
2use derive_more::Deref;
3use image::Luma;
4use qrcode::render::{svg, unicode};
5use qrcode::types::QrError;
6use qrcode::{EcLevel, QrCode, Version};
7use std::fmt::{Debug, Display, Formatter};
8use std::ops::Deref;
9use std::path::PathBuf;
10use std::str::FromStr;
11
12pub fn generate<'a>(
13 content: &'a QrContent,
14 ec_level: &'a QrEcLevel,
15 version: &'a QrVersion,
16 output_type: OutputType,
17) -> crate::Result<QrCodeImage<'a>> {
18 let (qr_code, version) = create_qr_code(content, version, ec_level)?;
19 let image = match output_type {
20 OutputType::Text => {
21 let image = qr_code
22 .render::<unicode::Dense1x2>()
23 .dark_color(unicode::Dense1x2::Light)
24 .light_color(unicode::Dense1x2::Dark)
25 .build();
26 QrCodeImageVal::Text(image)
27 }
28 OutputType::Image => {
29 let image = {
30 let image = qr_code.render::<Luma<u8>>().build();
31 let path =
32 std::env::temp_dir().join(format!("qrcode-{}.png", uuid::Uuid::new_v4()));
33 let _ = image.save(&path)?;
34 path
35 };
36 QrCodeImageVal::Image(image)
37 }
38 OutputType::Svg => {
39 let image = {
40 let image = qr_code
41 .render()
42 .min_dimensions(200, 200)
43 .dark_color(svg::Color("#800000"))
44 .light_color(svg::Color("#ffff80"))
45 .build();
46 let path =
47 std::env::temp_dir().join(format!("qrcode-{}.svg", uuid::Uuid::new_v4()));
48 let _ = std::fs::write(&path, image.as_bytes())?;
49 path
50 };
51 QrCodeImageVal::Svg(image)
52 }
53 };
54 Ok(QrCodeImage {
55 content,
56 ec_level: *ec_level,
57 version,
58 image,
59 })
60}
61
62fn create_qr_code(
63 content_str: &str,
64 qr_version: &QrVersion,
65 qr_ec_level: &QrEcLevel,
66) -> Result<(QrCode, QrVersion), QrError> {
67 let mut version = match qr_version {
68 QrVersion::Auto => Version::Normal(3),
69 QrVersion::Version(val) => *val,
70 };
71 let ec_level = **qr_ec_level;
72 loop {
73 match QrCode::with_version(content_str, version, ec_level) {
74 Ok(val) => {
75 return Ok((val, QrVersion::Version(version)));
76 }
77 Err(QrError::DataTooLong) => match qr_version {
78 QrVersion::Auto => {
79 version = match version {
80 Version::Normal(val) => Version::Normal(val + 1),
81 Version::Micro(val) => Version::Micro(val + 1),
82 };
83 continue;
84 }
85 _ => return Err(QrError::DataTooLong),
86 },
87 Err(QrError::InvalidVersion) => {
88 return Err(QrError::DataTooLong);
89 }
90 Err(err) => {
91 return Err(err);
92 }
93 }
94 }
95}
96
97impl FromStr for QrEcLevel {
98 type Err = anyhow::Error;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 let s = s.to_lowercase();
102 Ok(match s.as_str() {
103 "0" | "7" | "7%" | "l" => Self(EcLevel::L),
104 "1" | "15" | "15%" | "m" => Self(EcLevel::M),
105 "2" | "25" | "25%" | "q" => Self(EcLevel::Q),
106 "3" | "30" | "30%" | "h" => Self(EcLevel::H),
107 _ => Self::default(),
108 })
109 }
110}
111
112impl Display for QrEcLevel {
113 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114 match self.deref() {
115 EcLevel::L => write!(f, "7%"),
116 EcLevel::M => write!(f, "15%"),
117 EcLevel::Q => write!(f, "25%"),
118 EcLevel::H => write!(f, "30%"),
119 }
120 }
121}
122
123impl Default for QrEcLevel {
124 fn default() -> Self {
125 Self(EcLevel::Q)
126 }
127}
128
129impl FromStr for QrVersion {
130 type Err = anyhow::Error;
131
132 fn from_str(s: &str) -> Result<Self, Self::Err> {
133 let s = s.to_lowercase();
134 match s.as_str() {
135 "auto" => Ok(Self::Auto),
136 val => Ok(val
137 .parse::<u8>()
138 .map(|it| Self::Version(Version::Normal(it as i16)))
139 .unwrap_or_default()),
140 }
141 }
142}
143
144impl Display for QrVersion {
145 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::Auto => write!(f, "Auto"),
148 Self::Version(val @ Version::Normal(int_val))
149 | Self::Version(val @ Version::Micro(int_val)) => {
150 write!(f, "{} ({}*{})", int_val, val.width(), val.width())
151 }
152 }
153 }
154}
155
156impl Default for QrVersion {
157 fn default() -> Self {
158 Self::Auto
159 }
160}
161
162#[derive(Debug, Clone, Deref)]
163pub struct QrCodeImage<'a> {
164 pub content: &'a QrContent,
165 pub ec_level: QrEcLevel,
166 pub version: QrVersion,
167 #[deref]
168 pub image: QrCodeImageVal,
169}
170#[derive(Debug, Clone)]
171pub enum QrCodeImageVal {
172 Text(String),
173 Image(PathBuf),
174 Svg(PathBuf),
175}
176
177impl Display for QrCodeImage<'_> {
178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 use std::fmt::Display;
180 match &self.image {
181 QrCodeImageVal::Text(image) => Display::fmt(image, f),
182 QrCodeImageVal::Image(image) => Display::fmt(&image.display(), f),
183 QrCodeImageVal::Svg(image) => Display::fmt(&image.display(), f),
184 }
185 }
186}
187
188impl QrCodeImage<'_> {
189 pub fn out_put_type(&self) -> OutputType {
190 OutputType::from(self.deref())
191 }
192}
193
194impl From<&QrCodeImageVal> for OutputType {
195 fn from(value: &QrCodeImageVal) -> Self {
196 match value {
197 QrCodeImageVal::Text(_) => OutputType::Text,
198 QrCodeImageVal::Image(_) => OutputType::Image,
199 QrCodeImageVal::Svg(_) => OutputType::Svg,
200 }
201 }
202}