mirror of
https://github.com/chatmail/core.git
synced 2026-05-03 21:36:29 +03:00
generate qr codes
This commit is contained in:
18
src/qr.rs
18
src/qr.rs
@@ -32,6 +32,7 @@ const VCARD_SCHEME: &str = "BEGIN:VCARD";
|
||||
const SMTP_SCHEME: &str = "SMTP:";
|
||||
const HTTP_SCHEME: &str = "http://";
|
||||
const HTTPS_SCHEME: &str = "https://";
|
||||
pub const DCBACKUP_SCHEME: &str = "DCBACKUP:";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Qr {
|
||||
@@ -61,6 +62,9 @@ pub enum Qr {
|
||||
Account {
|
||||
domain: String,
|
||||
},
|
||||
Backup {
|
||||
ticket: iroh_share::Ticket,
|
||||
},
|
||||
WebrtcInstance {
|
||||
domain: String,
|
||||
instance_pattern: String,
|
||||
@@ -127,6 +131,8 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
|
||||
decode_account(qr)?
|
||||
} else if starts_with_ignore_case(qr, DCLOGIN_SCHEME) {
|
||||
dclogin_scheme::decode_login(qr)?
|
||||
} else if starts_with_ignore_case(qr, DCBACKUP_SCHEME) {
|
||||
decode_backup(qr)?
|
||||
} else if starts_with_ignore_case(qr, DCWEBRTC_SCHEME) {
|
||||
decode_webrtc_instance(context, qr)?
|
||||
} else if qr.starts_with(MAILTO_SCHEME) {
|
||||
@@ -332,6 +338,18 @@ fn decode_account(qr: &str) -> Result<Qr> {
|
||||
}
|
||||
}
|
||||
|
||||
/// scheme: `DCBACKUP:<multibase + bincode encode ticket>`
|
||||
fn decode_backup(qr: &str) -> Result<Qr> {
|
||||
let payload = qr
|
||||
.get(DCBACKUP_SCHEME.len()..)
|
||||
.context("invalid DCBACKUP payload")?;
|
||||
|
||||
let (_, ticket_bytes) = multibase::decode(payload)?;
|
||||
let ticket = iroh_share::Ticket::from_bytes(&ticket_bytes)?;
|
||||
|
||||
Ok(Qr::Backup { ticket })
|
||||
}
|
||||
|
||||
/// scheme: `DCWEBRTC:https://meet.jit.si/$ROOM`
|
||||
fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
|
||||
let payload = qr
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
config::Config,
|
||||
contact::{Contact, ContactId},
|
||||
context::Context,
|
||||
qr::DCBACKUP_SCHEME,
|
||||
securejoin, stock_str,
|
||||
};
|
||||
|
||||
@@ -262,6 +263,93 @@ fn inner_generate_secure_join_qr_code(
|
||||
Ok(svg)
|
||||
}
|
||||
|
||||
pub fn generate_backup_qr_code(ticket: &iroh_share::Ticket) -> Result<String> {
|
||||
let ticket_bytes = ticket.as_bytes();
|
||||
let ticket_str = multibase::encode(multibase::Base::Base64, &ticket_bytes);
|
||||
let ticket_str = format!("{}{}", DCBACKUP_SCHEME, ticket_str);
|
||||
// config
|
||||
let width = 515.0;
|
||||
let height = 630.0;
|
||||
let qr_code_size = 400.0;
|
||||
let qr_translate_up = 40.0;
|
||||
let card_roundness = 40.0;
|
||||
let card_border_size = 2.0;
|
||||
|
||||
let qr = QrCode::encode_text(&ticket_str, QrCodeEcc::Medium)?;
|
||||
let mut svg = String::with_capacity(28000);
|
||||
let mut w = tagger::new(&mut svg);
|
||||
|
||||
w.elem("svg", |d| {
|
||||
d.attr("xmlns", "http://www.w3.org/2000/svg")?;
|
||||
d.attr("viewBox", format_args!("0 0 {} {}", width, height))?;
|
||||
Ok(())
|
||||
})?
|
||||
.build(|w| {
|
||||
// White Background apears like a card
|
||||
w.single("rect", |d| {
|
||||
d.attr("x", card_border_size)?;
|
||||
d.attr("y", card_border_size)?;
|
||||
d.attr("rx", card_roundness)?;
|
||||
d.attr("stroke", "#c6c6c6")?;
|
||||
d.attr("stroke-width", card_border_size)?;
|
||||
d.attr("width", width - (card_border_size * 2.0))?;
|
||||
d.attr("height", height - (card_border_size * 2.0))?;
|
||||
d.attr("style", "fill:#f2f2f2")?;
|
||||
Ok(())
|
||||
})?;
|
||||
// Qrcode
|
||||
w.elem("g", |d| {
|
||||
d.attr(
|
||||
"transform",
|
||||
format!(
|
||||
"translate({},{})",
|
||||
(width - qr_code_size) / 2.0,
|
||||
((height - qr_code_size) / 2.0) - qr_translate_up
|
||||
),
|
||||
)
|
||||
// If the qr code should be in the wrong place,
|
||||
// we could also translate and scale the points in the path already,
|
||||
// but that would make the resulting svg way bigger in size and might bring up rounding issues,
|
||||
// so better avoid doing it manually if possible
|
||||
})?
|
||||
.build(|w| {
|
||||
w.single("path", |d| {
|
||||
let mut path_data = String::with_capacity(0);
|
||||
let scale = qr_code_size / qr.size() as f32;
|
||||
|
||||
for y in 0..qr.size() {
|
||||
for x in 0..qr.size() {
|
||||
if qr.get_module(x, y) {
|
||||
path_data += &format!("M{},{}h1v1h-1z", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.attr("style", "fill:#000000")?;
|
||||
d.attr("d", path_data)?;
|
||||
d.attr("transform", format!("scale({})", scale))
|
||||
})
|
||||
})?;
|
||||
|
||||
// Footer logo
|
||||
const FOOTER_HEIGHT: f32 = 35.0;
|
||||
const FOOTER_WIDTH: f32 = 198.0;
|
||||
w.elem("g", |d| {
|
||||
d.attr(
|
||||
"transform",
|
||||
format!(
|
||||
"translate({},{})",
|
||||
(width - FOOTER_WIDTH) / 2.0,
|
||||
height - FOOTER_HEIGHT
|
||||
),
|
||||
)
|
||||
})?
|
||||
.build(|w| w.put_raw(include_str!("../assets/qrcode_logo_footer.svg")))
|
||||
})?;
|
||||
|
||||
Ok(svg)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user