86d788e57c
Phase 1: 格式扩展 - png.rs → image.rs,OutputFormat 枚举 (PNG/BMP/JPEG/WebP) - CLI -f/--format,Web fmt 参数扩展,image crate +bmp feature Phase 2: 解码增强 - 新增 decoder/perspective.rs — 旋转矫正(MVP) - auto_correct: finder 检测→计算旋转角→仿射变换→再解码 - decode_image 自动重试矫正流水线 Phase 3: vCard 扩展 - 新增 5 字段:TITLE/URL/BDAY/NOTE/PHOTO - Rust text_builder + TS qrText + VCardMode UI 同步 - CLI 新增 --title --vcard-url --birthday --note --photo - 中/英 i18n 翻译 测试: 81 Rust + 19 前端全部通过
158 lines
4.2 KiB
Rust
158 lines
4.2 KiB
Rust
//! 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 3.0 字符串(含扩展字段)
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn build_vcard_text(
|
|
name: &str,
|
|
phone: &str,
|
|
email: &str,
|
|
company: &str,
|
|
address: &str,
|
|
title: &str,
|
|
url: &str,
|
|
birthday: &str,
|
|
note: &str,
|
|
photo: &str,
|
|
) -> String {
|
|
let mut s =
|
|
format!("BEGIN:VCARD\nVERSION:3.0\nFN:{name}\nTEL:{phone}\nEMAIL:{email}\nORG:{company}");
|
|
if !title.is_empty() {
|
|
s.push_str(&format!("\nTITLE:{title}"));
|
|
}
|
|
if !address.is_empty() {
|
|
s.push_str(&format!("\nADR:{address}"));
|
|
}
|
|
if !url.is_empty() {
|
|
s.push_str(&format!("\nURL:{url}"));
|
|
}
|
|
if !birthday.is_empty() {
|
|
s.push_str(&format!("\nBDAY:{birthday}"));
|
|
}
|
|
if !note.is_empty() {
|
|
s.push_str(&format!("\nNOTE:{note}"));
|
|
}
|
|
if !photo.is_empty() {
|
|
s.push_str(&format!("\nPHOTO:{photo}"));
|
|
}
|
|
s.push_str("\nEND:VCARD");
|
|
s
|
|
}
|
|
|
|
/// 构造 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"));
|
|
assert!(!text.contains("TITLE:")); // 空字段不输出
|
|
}
|
|
|
|
#[test]
|
|
fn test_build_vcard_full() {
|
|
let text = build_vcard_text(
|
|
"张三",
|
|
"13800138000",
|
|
"a@b.com",
|
|
"公司",
|
|
"北京",
|
|
"工程师",
|
|
"https://z.com",
|
|
"1990-01-01",
|
|
"备注",
|
|
"https://z.com/p.jpg",
|
|
);
|
|
assert!(text.contains("TITLE:工程师"));
|
|
assert!(text.contains("URL:https://z.com"));
|
|
assert!(text.contains("BDAY:1990-01-01"));
|
|
assert!(text.contains("NOTE:备注"));
|
|
assert!(text.contains("PHOTO:https://z.com/p.jpg"));
|
|
}
|
|
|
|
#[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");
|
|
}
|
|
}
|