feat: CLI 编码模式 + 批量生成 + text_builder
- 新增 core/src/text_builder.rs — Rust 版文本构造(WiFi/vCard/Email/Phone/SMS) - CLI 新增 --mode 模式参数(wifi/vcard/email/phone/sms/url/batch) - CLI 新增 --ssid/--password/--name/--phone/--to 等模式专属参数 - CLI 新增 --batch <file> 批量生成(JSON 数组 / CSV) - 批量支持自动检测 JSON/CSV 格式并自动编号输出 - 新增 6 个 text_builder 单元测试(80 tests total)
This commit is contained in:
@@ -4,4 +4,5 @@ pub mod encoder;
|
||||
pub mod matrix;
|
||||
pub mod qr;
|
||||
pub mod render;
|
||||
pub mod text_builder;
|
||||
pub mod version;
|
||||
|
||||
+11
-9
@@ -26,8 +26,7 @@ fn fill_module(
|
||||
|
||||
/// 在 QR 码 PNG 缓冲区中央叠加 logo
|
||||
fn overlay_logo(img: &mut RgbaImage, logo_bytes: &[u8], logo_size_pct: f32) -> Result<(), String> {
|
||||
let logo =
|
||||
image::load_from_memory(logo_bytes).map_err(|e| format!("Logo 加载失败: {e}"))?;
|
||||
let logo = image::load_from_memory(logo_bytes).map_err(|e| format!("Logo 加载失败: {e}"))?;
|
||||
let logo = logo.to_rgba8();
|
||||
|
||||
let img_w = img.width();
|
||||
@@ -39,12 +38,7 @@ fn overlay_logo(img: &mut RgbaImage, logo_bytes: &[u8], logo_size_pct: f32) -> R
|
||||
return Ok(()); // 太小,跳过
|
||||
}
|
||||
|
||||
let resized = imageops::resize(
|
||||
&logo,
|
||||
logo_size,
|
||||
logo_size,
|
||||
imageops::FilterType::Lanczos3,
|
||||
);
|
||||
let resized = imageops::resize(&logo, logo_size, logo_size, imageops::FilterType::Lanczos3);
|
||||
|
||||
let x = (img_w - logo_size) / 2;
|
||||
let y = (img_h - logo_size) / 2;
|
||||
@@ -79,7 +73,15 @@ pub fn render_png(
|
||||
false
|
||||
};
|
||||
|
||||
fill_module(&mut img, x, y, module_size as u32, is_dark, &qr.fg_color, &qr.bg_color);
|
||||
fill_module(
|
||||
&mut img,
|
||||
x,
|
||||
y,
|
||||
module_size as u32,
|
||||
is_dark,
|
||||
&qr.fg_color,
|
||||
&qr.bg_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//! QR 编码文本构造工具
|
||||
//!
|
||||
//! 集中管理各模式的文本格式(与 gui 前端 `utils/qrText.ts` 功能一致)
|
||||
|
||||
/// 构造 WiFi 连接字符串
|
||||
pub fn build_wifi_text(ssid: &str, password: &str, encryption: &str, hidden: bool) -> String {
|
||||
let h = if hidden { "H:true;" } else { "" };
|
||||
format!("WIFI:T:{encryption};S:{ssid};P:{password};{h};")
|
||||
}
|
||||
|
||||
/// 构造 vCard 字符串
|
||||
pub fn build_vcard_text(
|
||||
name: &str,
|
||||
phone: &str,
|
||||
email: &str,
|
||||
company: &str,
|
||||
address: &str,
|
||||
) -> String {
|
||||
format!("BEGIN:VCARD\nVERSION:3.0\nFN:{name}\nTEL:{phone}\nEMAIL:{email}\nORG:{company}\nADR:{address}\nEND:VCARD")
|
||||
}
|
||||
|
||||
/// 构造 mailto 链接
|
||||
pub fn build_email_text(to: &str, subject: &str, body: &str) -> String {
|
||||
let subject_enc = urlencoding(subject);
|
||||
let body_enc = urlencoding(body);
|
||||
format!("mailto:{to}?subject={subject_enc}&body={body_enc}")
|
||||
}
|
||||
|
||||
/// 构造电话链接
|
||||
pub fn build_phone_text(number: &str) -> String {
|
||||
format!("tel:{number}")
|
||||
}
|
||||
|
||||
/// 构造短信链接
|
||||
pub fn build_sms_text(number: &str, message: &str) -> String {
|
||||
format!("smsto:{number}:{message}")
|
||||
}
|
||||
|
||||
/// 简易 URL 编码(仅编码特殊字符)
|
||||
fn urlencoding(s: &str) -> String {
|
||||
s.chars()
|
||||
.map(|c| match c {
|
||||
' ' => "%20".into(),
|
||||
'&' => "%26".into(),
|
||||
'=' => "%3D".into(),
|
||||
'#' => "%23".into(),
|
||||
'%' => "%25".into(),
|
||||
'+' => "%2B".into(),
|
||||
'\n' => "%0A".into(),
|
||||
'\r' => "%0D".into(),
|
||||
_ => c.to_string(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_build_wifi_text() {
|
||||
let text = build_wifi_text("MyWiFi", "pass123", "WPA", false);
|
||||
assert!(text.contains("WIFI:T:WPA;S:MyWiFi;P:pass123;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_wifi_hidden() {
|
||||
let text = build_wifi_text("HiddenNet", "secret", "WPA2", true);
|
||||
assert!(text.contains("H:true;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_vcard_text() {
|
||||
let text = build_vcard_text("张三", "13800138000", "a@b.com", "公司", "北京");
|
||||
assert!(text.contains("BEGIN:VCARD"));
|
||||
assert!(text.contains("FN:张三"));
|
||||
assert!(text.contains("END:VCARD"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_email_text() {
|
||||
let text = build_email_text("a@b.com", "Hello World", "Test body");
|
||||
assert!(text.starts_with("mailto:a@b.com"));
|
||||
assert!(text.contains("Hello%20World"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_phone_text() {
|
||||
assert_eq!(build_phone_text("13800138000"), "tel:13800138000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sms_text() {
|
||||
let text = build_sms_text("13800138000", "Hi");
|
||||
assert_eq!(text, "smsto:13800138000:Hi");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user