use base64::{engine::general_purpose::STANDARD, Engine as _};
use image::Luma;
use minijinja::{
value::{Kwargs, ViaDeserialize},
Error, State,
};
use qrcode::{render::svg, QrCode};
use serde::Deserialize;
use std::io::Cursor;
#[derive(Debug, Deserialize, PartialEq)]
enum QRFormat {
#[serde(rename = "png")]
Png,
#[serde(rename = "svg")]
Svg,
#[serde(rename = "svg:raw")]
SvgRaw,
}
pub fn generate(_state: &State, content: String, options: Kwargs) -> Result<String, Error> {
let format = options
.get::<Option<ViaDeserialize<QRFormat>>>("format")?
.unwrap_or(ViaDeserialize(QRFormat::Png))
.0;
let width: Option<usize> = options.get("width")?;
let height: Option<usize> = options.get("height")?;
let code = QrCode::new(content.as_bytes()).unwrap();
match format {
QRFormat::Png => {
let image = if let (Some(w), Some(h)) = (width, height) {
code.render::<Luma<u8>>()
.max_dimensions(w as u32, h as u32)
.build()
} else {
code.render::<Luma<u8>>().build()
};
let mut w: Vec<u8> = Vec::new();
image
.write_to(&mut Cursor::new(&mut w), image::ImageFormat::Png)
.unwrap();
let image_data = STANDARD.encode(w);
Ok(format!("data:image/png;base64,{}", image_data))
}
QRFormat::Svg | QRFormat::SvgRaw => {
let image = if let (Some(w), Some(h)) = (width, height) {
code.render()
.max_dimensions(w as u32, h as u32)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build()
} else {
code.render()
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build()
};
if format == QRFormat::Svg {
let image_data = STANDARD.encode(image);
Ok(format!("data:image/svg+xml;base64,{}", image_data))
} else {
Ok(String::from_utf8(image.into()).unwrap())
}
}
}
}