///|
/// Common payload templates. Each variant renders to the string that should be
/// encoded into the QR symbol.
///
/// 常用载荷模板。每个变体渲染为待编码到 QR 符号中的字符串。
pub(all) enum Content {
Text(String)
Url(String)
Wifi(ssid~ : String, password~ : String, auth~ : String)
Tel(String)
Mailto(address~ : String, subject~ : String, body~ : String)
Sms(number~ : String, message~ : String)
Geo(lat~ : String, lng~ : String)
VCard(name~ : String, phone~ : String, email~ : String, org~ : String)
}
///|
pub fn Content::wifi(
ssid : String,
password : String,
auth~ : String = "WPA",
) -> Content {
Wifi(ssid~, password~, auth~)
}
///|
pub fn Content::mailto(
address : String,
subject~ : String = "",
body~ : String = "",
) -> Content {
Mailto(address~, subject~, body~)
}
///|
pub fn Content::sms(number : String, message~ : String = "") -> Content {
Sms(number~, message~)
}
///|
pub fn Content::tel(number : String) -> Content {
Tel(number)
}
///|
pub fn Content::geo(lat : String, lng : String) -> Content {
Geo(lat~, lng~)
}
///|
pub fn Content::vcard(
name : String,
phone~ : String = "",
email~ : String = "",
org~ : String = "",
) -> Content {
VCard(name~, phone~, email~, org~)
}
///|
fn escape_wifi(value : String) -> String {
let sb = StringBuilder()
for c in value {
if c == '\\' || c == ';' || c == ',' || c == ':' || c == '"' {
sb.write_char('\\')
}
sb.write_char(c)
}
sb.to_string()
}
///|
fn escape_vcard(value : String) -> String {
let sb = StringBuilder()
for c in value {
match c {
'\\' => sb.write_string("\\\\")
';' => sb.write_string("\\;")
',' => sb.write_string("\\,")
'\n' => sb.write_string("\\n")
_ => sb.write_char(c)
}
}
sb.to_string()
}
///|
fn hex_chars() -> Array[Char] {
[
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
]
}
///|
fn percent_encode(value : String) -> String {
let digits = hex_chars()
let sb = StringBuilder()
for b in @utf8.encode(value) {
let v = b.to_int()
let digit = v >= 48 && v <= 57
let upper = v >= 65 && v <= 90
let lower = v >= 97 && v <= 122
let unreserved = digit ||
upper ||
lower ||
v == 45 ||
v == 46 ||
v == 95 ||
v == 126
if unreserved {
sb.write_char(b.to_char())
} else {
sb.write_char('%')
sb.write_char(digits[(v >> 4) & 15])
sb.write_char(digits[v & 15])
}
}
sb.to_string()
}
///|
/// Serializes the template into the payload text.
///
/// 将模板序列化为载荷文本。
pub fn Content::to_text(self : Content) -> String {
match self {
Text(s) => s
Url(s) => s
Wifi(ssid~, password~, auth~) =>
"WIFI:T:\{auth};S:\{escape_wifi(ssid)};P:\{escape_wifi(password)};;"
Tel(number) => "tel:\{number}"
Mailto(address~, subject~, body~) => {
let mut result = "mailto:\{address}"
let mut sep = "?"
if !subject.is_empty() {
result = result + sep + "subject=" + percent_encode(subject)
sep = "&"
}
if !body.is_empty() {
result = result + sep + "body=" + percent_encode(body)
}
result
}
Sms(number~, message~) => "SMSTO:\{number}:\{message}"
Geo(lat~, lng~) => "geo:\{lat},\{lng}"
VCard(name~, phone~, email~, org~) => {
let sb = StringBuilder()
sb <+ "BEGIN:VCARD\nVERSION:3.0\n"
sb <+ "N:\{escape_vcard(name)}\n"
sb <+ "FN:\{escape_vcard(name)}\n"
if !org.is_empty() {
sb <+ "ORG:\{escape_vcard(org)}\n"
}
if !phone.is_empty() {
sb <+ "TEL:\{escape_vcard(phone)}\n"
}
if !email.is_empty() {
sb <+ "EMAIL:\{escape_vcard(email)}\n"
}
sb <+ "END:VCARD"
sb.to_string()
}
}
}
///|
/// Convenience wrapper: render a template and encode it.
///
/// 便捷包装:渲染模板并编码。
pub fn encode_content(
content : Content,
ec_level~ : @core.EcLevel = @core.EcLevel::M,
) -> @core.QrCode raise @core.QrError {
@core.encode(content.to_text(), ec_level~)
}