dev_kit/command/qrcode/
generator.rs

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