diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 61717a32d..0f28235f2 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2791,6 +2791,22 @@ dc_array_t* dc_get_locations (dc_context_t* context, uint32_t cha void dc_delete_all_locations (dc_context_t* context); +// misc + +/** + * Create a QR code from any input data. + * + * The QR code is returned as a square SVG image. + * + * @memberof dc_context_t + * @param payload The content for the QR code. + * @return SVG image with the QR code. + * On errors, an empty string is returned. + * The returned string must be released using dc_str_unref() after usage. + */ +char* dc_create_qr_svg (const char* payload); + + /** * Get last error string. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 6559ab663..aade1d4f2 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -30,7 +30,7 @@ use deltachat::ephemeral::Timer as EphemeralTimer; use deltachat::imex::BackupProvider; use deltachat::key::preconfigure_keypair; use deltachat::message::MsgId; -use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg}; +use deltachat::qr_code_generator::{create_qr_svg, generate_backup_qr, get_securejoin_qr_svg}; use deltachat::stock_str::StockMessage; use deltachat::webxdc::StatusUpdateSerial; use deltachat::*; @@ -2594,6 +2594,18 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) { }); } +#[no_mangle] +pub unsafe extern "C" fn dc_create_qr_svg(payload: *const libc::c_char) -> *mut libc::c_char { + if payload.is_null() { + eprintln!("ignoring careless call to dc_create_qr_svg()"); + return "".strdup(); + } + + create_qr_svg(&to_string_lossy(payload)) + .unwrap_or_else(|_| "".to_string()) + .strdup() +} + #[no_mangle] pub unsafe extern "C" fn dc_get_last_error(context: *mut dc_context_t) -> *mut libc::c_char { if context.is_null() { diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index 1c6fc9ecd..dd1883984 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -22,6 +22,7 @@ use deltachat::mimeparser::SystemMessage; use deltachat::peer_channels::{send_webxdc_realtime_advertisement, send_webxdc_realtime_data}; use deltachat::peerstate::*; use deltachat::qr::*; +use deltachat::qr_code_generator::create_qr_svg; use deltachat::reaction::send_reaction; use deltachat::receive_imf::*; use deltachat::sql; @@ -425,6 +426,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu checkqr \n\ joinqr \n\ setqr \n\ + createqrsvg \n\ providerinfo \n\ fileinfo \n\ estimatedeletion \n\ @@ -1249,6 +1251,13 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu Err(err) => println!("Cannot set config from QR code: {err:?}"), } } + "createqrsvg" => { + ensure!(!arg1.is_empty(), "Argument missing."); + let svg = create_qr_svg(arg1)?; + let file = dirs::home_dir().unwrap_or_default().join("qr.svg"); + fs::write(&file, svg).await?; + println!("{file:#?} written."); + } "providerinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let proxy_enabled = context diff --git a/deltachat-repl/src/main.rs b/deltachat-repl/src/main.rs index 213c28317..2b16cc68f 100644 --- a/deltachat-repl/src/main.rs +++ b/deltachat-repl/src/main.rs @@ -240,12 +240,13 @@ const CONTACT_COMMANDS: [&str; 9] = [ "unblock", "listblocked", ]; -const MISC_COMMANDS: [&str; 11] = [ +const MISC_COMMANDS: [&str; 12] = [ "getqr", "getqrsvg", "getbadqr", "checkqr", "joinqr", + "createqrsvg", "fileinfo", "clear", "exit", diff --git a/src/qr_code_generator.rs b/src/qr_code_generator.rs index fc747aba2..b221061c9 100644 --- a/src/qr_code_generator.rs +++ b/src/qr_code_generator.rs @@ -14,6 +14,65 @@ use crate::qr::{self, Qr}; use crate::securejoin; use crate::stock_str::{self, backup_transfer_qr}; +/// Create a QR code from any input data. +pub fn create_qr_svg(qrcode_content: &str) -> Result { + let all_size = 512.0; + let qr_code_size = 416.0; + + let qr = QrCode::encode_text(qrcode_content, 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 {all_size} {all_size}"))?; + d.attr("xmlns:xlink", "http://www.w3.org/1999/xlink")?; // required for enabling xlink:href on browsers + Ok(()) + })? + .build(|w| { + // background + w.single("rect", |d| { + d.attr("x", 0)?; + d.attr("y", 0)?; + d.attr("width", all_size)?; + d.attr("height", all_size)?; + d.attr("style", "fill:#ffffff")?; + Ok(()) + })?; + // QR code + w.elem("g", |d| { + d.attr( + "transform", + format!( + "translate({},{})", + (all_size - qr_code_size) / 2.0, + ((all_size - qr_code_size) / 2.0) + ), + ) + })? + .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{x},{y}h1v1h-1z"); + } + } + } + + d.attr("style", "fill:#000000")?; + d.attr("d", path_data)?; + d.attr("transform", format!("scale({scale})")) + }) + }) + })?; + + Ok(svg) +} + /// Returns SVG of the QR code to join the group or verify contact. /// /// If `chat_id` is `None`, returns verification QR code.