dev_kit/command/qrcode/
generator.rs

1use 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}