cn_font_split/
run_subset.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::link_subset::name_template::name_template;
use crate::message::EventFactory;
use crate::runner::Context;
use cn_font_proto::api_interface::output_report::{
    BundleMessage, SubsetDetail,
};
use cn_font_proto::api_interface::EventMessage;
use cn_font_utils::u8_size_in_kb;
use harfbuzz_rs_now::subset::Subset;
use harfbuzz_rs_now::{Face, Owned};
use log::{info, warn};
use rayon::iter::{
    IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator,
};
use std::collections::BTreeSet;
use std::time::Instant;
use woff::version2::compress;

/// 构建单个分包为字体文件
pub fn build_single_subset(face: &Owned<Face>, subset: &Vec<u32>) -> Vec<u8> {
    let subset_runner = Subset::new();
    subset_runner.clear_drop_table();
    subset_runner.adjust_layout();
    subset_runner.add_chars(subset);
    let new_face = subset_runner.run_subset(&face).face_data();
    let ttf_binary = new_face.get_data();
    let woff2_binary = compress(ttf_binary, String::from(""), 1, true)
        .expect("Failed to compress subset");
    woff2_binary
}

struct ThreadResult {
    pub subset_result: RunSubsetResult,
    pub log: SubsetDetail,
    pub message: EventMessage,
}
/// 根据预处理结果,生成字体子集文件,通过 callback 返回文件保存数据
pub fn run_subset(ctx: &mut Context) {
    let origin_bytes: u32 = (&ctx.input.input).len() as u32;
    let all_chars = BTreeSet::from_iter(
        ctx.face.collect_unicodes().iter().map(|x| x.clone()),
    );
    let origin_size: u32 = all_chars.len().try_into().unwrap();
    let file_name_template = ctx.input.rename_output_font.clone();
    info!("font subset result log");
    let thread_result: Vec<ThreadResult> = ctx
        .pre_subset_result
        .par_iter()
        .enumerate()
        .map(|(index, r)| {
            let start_time = Instant::now();

            let result = build_single_subset(&ctx.face, r);
            let result_bytes = u8_size_in_kb(&result);
            let digest = md5::compute(result.as_slice());
            // println!("{:?}", hash);
            let hash_string = format!("{:x}", digest);
            let duration = start_time.elapsed();
            info!(
                "{}\t{}ms/{}/{}kb\t{}",
                index,
                duration.as_millis(),
                r.len(),
                result_bytes,
                hash_string.to_string()
            );
            let file_name = if let Some(name) = &file_name_template {
                name_template(&name, &hash_string, "woff2", &index)
            } else {
                hash_string.to_string() + ".woff2"
            };
            ThreadResult {
                subset_result: RunSubsetResult {
                    hash: hash_string.to_string(),
                    unicodes: r.clone(),
                    file_name: file_name.clone(),
                },
                log: SubsetDetail {
                    id: (index as u32) + 1_u32,
                    file_name: file_name.clone(),
                    hash: hash_string.to_string(),
                    chars: r.clone(),
                    bytes: result.len() as u32,
                    duration: duration.as_millis() as u32,
                },
                message: EventMessage::output_data(&file_name, result),
            }
        })
        .collect::<Vec<ThreadResult>>();
    let mut bundled_bytes: u32 = 0;

    let mut bundle_chars = BTreeSet::new();
    bundle_chars.insert(0);
    for res in thread_result {
        (ctx.callback)(res.message);

        bundled_bytes += res.log.bytes;
        res.log.chars.iter().for_each(|x| {
            bundle_chars.insert(x.clone());
        });
        ctx.run_subset_result.push(res.subset_result);
        ctx.reporter.subset_detail.push(res.log);
    }

    // 汇报构建前后的 unicode 差异
    let diff: Vec<u32> =
        bundle_chars.difference(&all_chars).map(|x| x.clone()).collect();

    if diff.len() > 0 {
        warn!(
            "subsets result diff: {}",
            diff.iter()
                .filter(|x| **x != 0)
                .map(|x| x.to_string())
                .collect::<Vec<String>>()
                .join(" ")
        );
    }

    ctx.reporter.bundle_message = Some(BundleMessage {
        origin_size,
        bundled_size: bundle_chars.len() as u32,
        origin_bytes,
        bundled_bytes,
    })
}

#[derive(Debug, Clone)]
pub struct RunSubsetResult {
    pub hash: String,
    pub unicodes: Vec<u32>,
    pub file_name: String,
}