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