diff options
Diffstat (limited to '')
-rw-r--r-- | Cargo.lock | 68 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/consts.rs | 4 | ||||
-rw-r--r-- | src/listing.rs | 41 | ||||
-rw-r--r-- | src/main.rs | 52 | ||||
-rw-r--r-- | src/renderer.rs | 40 |
6 files changed, 138 insertions, 69 deletions
@@ -475,6 +475,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -523,6 +529,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -627,6 +639,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1072,7 +1090,7 @@ dependencies = [ "indexmap", "lasso", "num-bigint", - "num-rational", + "num-rational 0.4.1", "num-traits", "once_cell", "phf 0.9.0", @@ -1268,6 +1286,20 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1546,7 +1578,7 @@ dependencies = [ "port_check", "predicates", "pretty_assertions", - "qrcodegen", + "qrcode", "regex", "reqwest", "rstest", @@ -1630,6 +1662,28 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -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" @@ -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 += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - result += "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"; - let dimension = qr - .size() - .checked_add(border.checked_mul(2).unwrap()) - .unwrap(); - result += &format!( - "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension); - result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n"; - result += "\t<path d=\""; - for y in 0..qr.size() { - for x in 0..qr.size() { - if qr.get_module(x, y) { - if x != 0 || y != 0 { - result += " "; - } - result += &format!("M{},{}h1v1h-1z", x + border, y + border); - } - } - } - result += "\" fill=\"#000000\"/>\n"; - result += "</svg>\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::<Vec<_>>() + .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<Entry>, 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! { |