// mbt-fontsub 命令行入口。
//
// 核心逻辑全部在库包 `Mr-Houjie/mbt-fontsub`;这里只做参数解析、
// 文件读写、结果输出与退出码。
///|
const VERSION : String = "0.1.0"
///|
fn usage() -> String {
let out = StringBuilder()
out.write_string("mbt-fontsub \{VERSION} — TrueType 字体子集化\n\n")
out.write_string("用法:\n")
out.write_string(" mbt-fontsub info <字体> [--face N]\n")
out.write_string(
" mbt-fontsub subset <字体> --text <文本> [-o 输出] [--face N]\n",
)
out.write_string(
" mbt-fontsub verify <字体> --text <文本> [--face N]\n\n",
)
out.write_string("说明:\n")
out.write_string(
" info 打印 units/em、字形数、TTC 面数、表目录与 cmap 格式\n",
)
out.write_string(
" subset 按文本子集化并写出新字体,同时打印体积变化\n",
)
out.write_string(
" verify 在内存中完成子集化并自检,不写文件\n",
)
out.write_string(" --face TTC 集合中的字体面下标,默认 0\n\n")
out.write_string(
"退出码:0 成功;1 自检失败;2 用法或读写错误\n",
)
out.to_string()
}
///|
fn flag_value(args : Array[String], name : String) -> String? {
let mut i = 0
while i + 1 < args.length() {
if args[i] == name {
return Some(args[i + 1])
}
i = i + 1
}
None
}
///|
fn chars_of(text : String) -> Array[Int] {
let chars : Array[Int] = []
for ch in text {
chars.push(ch.to_int())
}
chars
}
///|
fn parse_face(args : Array[String]) -> Int {
match flag_value(args, "--face") {
Some(value) => @string.parse_int(value) catch { _ => 0 }
None => 0
}
}
///|
fn load_font(path : String, face : Int) -> Result[(@lib.Font, Int), String] {
match (Some(@fs.read_file_to_bytes(path)) catch { _ => None }) {
None => Err("无法读取字体:\{path}")
Some(data) =>
Ok((@lib.parse_font_face(data, face), @lib.ttc_face_count(data))) catch {
error => Err("解析失败:\{@lib.font_error_message(error)}")
}
}
}
///|
fn print_info(font : @lib.Font, faces : Int) -> Unit {
println("units/em : \{font.units()}")
println("字形数 : \{font.glyph_count()}")
println("TTC 面数 : \{faces}")
println("字符映射数 : \{font.cmap.length()}")
let mut formats = ""
for format in font.cmap_formats {
if formats.length() > 0 {
formats = formats + ", "
}
formats = formats + format.to_string()
}
println("cmap 格式 : \{formats}")
println("文件大小 : \{font.bytes.length()} 字节")
let tags = font.table_tags()
let mut line = ""
for tag in tags {
line = line + tag + " "
}
println("表目录 : \{line}")
}
///|
fn run_subset(path : String, text : String, output : String, face : Int) -> Int {
match load_font(path, face) {
Err(message) => {
println(message)
2
}
Ok(pair) => {
let font = pair.0
let chars = chars_of(text)
match (Some(@lib.subset_by_text(font, text)) catch { _ => None }) {
None => {
println("子集化失败")
1
}
Some(subset) => {
@fs.write_bytes_to_file(output, subset) catch {
_ => {
println("无法写入:\{output}")
return 2
}
}
let report = @lib.verify_subset(font, subset, chars)
println("已写出 : \{output}")
println(
"体积 : \{report.original_size} → \{report.subset_size} 字节" +
"(省 \{report.savings_percent}%)",
)
println(
"字符 / 字形 : \{report.chars_checked} / \{report.glyphs}(原 \{font.glyph_count()})",
)
if report.ok {
println("自检 : 通过")
0
} else {
println(
"自检 : 失败(\{report.failures.length()} 项)",
)
for failure in report.failures {
println(" - \{failure}")
}
1
}
}
}
}
}
}
///|
fn run_verify(path : String, text : String, face : Int) -> Int {
match load_font(path, face) {
Err(message) => {
println(message)
2
}
Ok(pair) => {
let font = pair.0
let chars = chars_of(text)
match (Some(@lib.subset_by_text(font, text)) catch { _ => None }) {
None => {
println("子集化失败")
1
}
Some(subset) => {
let report = @lib.verify_subset(font, subset, chars)
println(
"字符 / 字形 : \{report.chars_checked} / \{report.glyphs}(原 \{font.glyph_count()})",
)
println(
"体积 : \{report.original_size} → \{report.subset_size} 字节" +
"(省 \{report.savings_percent}%)",
)
if report.ok {
println("自检 : 通过")
0
} else {
println(
"自检 : 失败(\{report.failures.length()} 项)",
)
for failure in report.failures {
println(" - \{failure}")
}
1
}
}
}
}
}
}
///|
fn main {
let args = @env.args()
if args.length() < 2 {
println(usage())
@sys.exit(2)
}
let command = args[1]
if command == "-h" || command == "--help" || command == "help" {
println(usage())
@sys.exit(0)
}
if command == "-v" || command == "--version" {
println(VERSION)
@sys.exit(0)
}
if args.length() < 3 {
println(usage())
@sys.exit(2)
}
let path = args[2]
let face = parse_face(args)
if command == "info" {
match load_font(path, face) {
Err(message) => {
println(message)
@sys.exit(2)
}
Ok(pair) => {
print_info(pair.0, pair.1)
@sys.exit(0)
}
}
} else if command == "subset" {
let text = match flag_value(args, "--text") {
Some(value) => value
None => ""
}
let output = match flag_value(args, "-o") {
Some(value) => value
None => path + ".subset.ttf"
}
@sys.exit(run_subset(path, text, output, face))
} else if command == "verify" {
let text = match flag_value(args, "--text") {
Some(value) => value
None => ""
}
@sys.exit(run_verify(path, text, face))
} else {
println(usage())
@sys.exit(2)
}
}