From 8543d2d61fbafde6222e9b95f9604b41570db477 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Wed, 20 Jul 2022 16:26:08 +0800 Subject: Switch to `qrcode` lib --- Cargo.lock | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 +- src/consts.rs | 4 ++++ src/listing.rs | 41 +++++----------------------------- src/main.rs | 52 +++++++++++++++++++++---------------------- src/renderer.rs | 40 +++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 69 deletions(-) create mode 100644 src/consts.rs diff --git a/Cargo.lock b/Cargo.lock index 174d5bc..62c1ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,6 +474,12 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +[[package]] +name = "bytemuck" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" + [[package]] name = "byteorder" version = "1.4.3" @@ -522,6 +528,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "checked_int_cast" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" + [[package]] name = "chrono" version = "0.4.22" @@ -626,6 +638,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "comrak" version = "0.14.0" @@ -1072,7 +1090,7 @@ dependencies = [ "indexmap", "lasso", "num-bigint", - "num-rational", + "num-rational 0.4.1", "num-traits", "once_cell", "phf 0.9.0", @@ -1267,6 +1285,20 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational 0.3.2", + "num-traits", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1546,7 +1578,7 @@ dependencies = [ "port_check", "predicates", "pretty_assertions", - "qrcodegen", + "qrcode", "regex", "reqwest", "rstest", @@ -1629,6 +1661,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2024,10 +2078,14 @@ dependencies = [ ] [[package]] -name = "qrcodegen" -version = "1.8.0" +name = "qrcode" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" +checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f" +dependencies = [ + "checked_int_cast", + "image", +] [[package]] name = "quote" diff --git a/Cargo.toml b/Cargo.toml index de73987..9094883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ mime = "0.3" nanoid = "0.4" percent-encoding = "2" port_check = "0.1" -qrcodegen = "1" +qrcode = "0.12.0" rustls = { version = "0.20", optional = true } rustls-pemfile = { version = "1.0", optional = true } serde = { version = "1", features = ["derive"] } diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..07f47b0 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,4 @@ +use qrcode::EcLevel; + +/// The error correction level to use for all QR code generation. +pub const QR_EC_LEVEL: EcLevel = EcLevel::M; diff --git a/src/listing.rs b/src/listing.rs index 82b4cdb..f90c40c 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -9,14 +9,14 @@ use actix_web::{HttpMessage, HttpRequest, HttpResponse}; use bytesize::ByteSize; use comrak::{markdown_to_html, ComrakOptions}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; -use qrcodegen::{QrCode, QrCodeEcc}; +use qrcode::QrCode; use serde::Deserialize; use strum_macros::{Display, EnumString}; use crate::archive::ArchiveMethod; use crate::auth::CurrentUser; use crate::errors::{self, ContextualError}; -use crate::renderer; +use crate::{consts, renderer}; use self::percent_encode_sets::PATH_SEGMENT; @@ -220,10 +220,10 @@ pub fn directory_listing( // If the `qrcode` parameter is included in the url, then should respond to the QR code if let Some(url) = query_params.qrcode { - let res = match QrCode::encode_text(&url, QrCodeEcc::Medium) { + let res = match QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) { Ok(qr) => HttpResponse::Ok() - .append_header(("Content-Type", "image/svg+xml")) - .body(qr_to_svg_string(&qr, 2)), + .content_type("text/html; charset=utf-8") + .body(renderer::qr_code_page(&qr).into_string()), Err(err) => { log::error!("URL is invalid (too long?): {:?}", err); HttpResponse::UriTooLong().finish() @@ -407,34 +407,3 @@ pub fn extract_query_parameters(req: &HttpRequest) -> QueryParameters { } } } - -// Returns a string of SVG code for an image depicting -// the given QR Code, with the given number of border modules. -// The string always uses Unix newlines (\n), regardless of the platform. -fn qr_to_svg_string(qr: &QrCode, border: i32) -> String { - assert!(border >= 0, "Border must be non-negative"); - let mut result = String::new(); - result += "\n"; - result += "\n"; - let dimension = qr - .size() - .checked_add(border.checked_mul(2).unwrap()) - .unwrap(); - result += &format!( - "\n", dimension); - result += "\t\n"; - result += "\t\n"; - result += "\n"; - result -} diff --git a/src/main.rs b/src/main.rs index b46262d..558dabb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,13 +13,14 @@ use anyhow::Result; use clap::{crate_version, IntoApp, Parser}; use clap_complete::generate; use log::{error, warn}; -use qrcodegen::{QrCode, QrCodeEcc}; +use qrcode::QrCode; use yansi::{Color, Paint}; mod archive; mod args; mod auth; mod config; +mod consts; mod errors; mod file_upload; mod listing; @@ -239,7 +240,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { .iter() .filter(|url| !url.contains("//127.0.0.1:") && !url.contains("//[::1]:")) { - match QrCode::encode_text(url, QrCodeEcc::Low) { + match QrCode::with_error_correction_level(url, consts::QR_EC_LEVEL) { Ok(qr) => { println!("QR code for {}:", Color::Green.paint(url).bold()); print_qr(&qr); @@ -352,30 +353,27 @@ async fn css() -> impl Responder { .body(css) } -// Prints to the console two inverted QrCodes side by side. +// Prints to the console a normal and an inverted QrCode side by side. fn print_qr(qr: &QrCode) { - let border = 4; - let size = qr.size() + 2 * border; - - for y in (0..size).step_by(2) { - for x in 0..2 * size { - let inverted = x >= size; - let (x, y) = (x % size - border, y - border); - - //each char represents two vertical modules - let (mod1, mod2) = match inverted { - false => (qr.get_module(x, y), qr.get_module(x, y + 1)), - true => (!qr.get_module(x, y), !qr.get_module(x, y + 1)), - }; - let c = match (mod1, mod2) { - (false, false) => ' ', - (true, false) => '▀', - (false, true) => '▄', - (true, true) => '█', - }; - print!("{0}", c); - } - println!(); - } - println!(); + use qrcode::render::unicode::Dense1x2; + + let normal = qr + .render() + .quiet_zone(true) + .dark_color(Dense1x2::Dark) + .light_color(Dense1x2::Light) + .build(); + let inverted = qr + .render() + .quiet_zone(true) + .dark_color(Dense1x2::Light) + .light_color(Dense1x2::Dark) + .build(); + let codes = normal + .lines() + .zip(inverted.lines()) + .map(|(l, r)| format!("{} {}", l, r)) + .collect::>() + .join("\n"); + println!("{}", codes); } diff --git a/src/renderer.rs b/src/renderer.rs index 7ec48b0..48b74b6 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Utc}; use chrono_humanize::Humanize; use clap::{crate_name, crate_version}; use maud::{html, Markup, PreEscaped, DOCTYPE}; +use qrcode::QrCode; use std::time::SystemTime; use strum::IntoEnumIterator; @@ -224,6 +225,45 @@ pub fn raw(entries: Vec, is_root: bool) -> Markup { } } +/// Renders the QR code page +pub fn qr_code_page(qr: &QrCode) -> Markup { + use qrcode::render::svg; + + html! { + (DOCTYPE) + html { + body { + // make QR code expand and fill page + style { + (PreEscaped("\ + html {\ + width: 100vw;\ + height: 100vh;\ + }\ + body {\ + width: 100%;\ + height: 100%;\ + margin: 0;\ + display: grid;\ + align-items: center;\ + justify-items: center;\ + }\ + svg {\ + width: 80%;\ + height: 80%;\ + }\ + ")) + } + (PreEscaped(qr.render() + .quiet_zone(false) + .dark_color(svg::Color("#000000")) + .light_color(svg::Color("#ffffff")) + .build())) + } + } + } +} + // Partial: version footer fn version_footer() -> Markup { html! { -- cgit v1.2.3