Coverage Report

Created: 2026-08-11 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/skera/src/post.rs
Line
Count
Source
1
//! impl subset() for post
2
use crate::fnv::FnvHashMap;
3
use crate::{
4
    serialize::{SerializeErrorFlags, Serializer},
5
    Plan, Subset, SubsetError, SubsetFlags,
6
};
7
use write_fonts::{
8
    read::{
9
        tables::post::{Post, DEFAULT_GLYPH_NAMES},
10
        FontRef, MinByteRange, TopLevelTable,
11
    },
12
    types::{BigEndian, Version16Dot16},
13
    FontBuilder,
14
};
15
16
// reference: subset() for post in harfbuzz
17
// https://github.com/harfbuzz/harfbuzz/blob/a070f9ebbe88dc71b248af9731dd49ec93f4e6e6/src/hb-ot-post-table.hh#L96
18
impl Subset for Post<'_> {
19
0
    fn subset(
20
0
        &self,
21
0
        plan: &Plan,
22
0
        _font: &FontRef,
23
0
        s: &mut Serializer,
24
0
        _builder: &mut FontBuilder,
25
0
    ) -> Result<(), SubsetError> {
26
        // copy header
27
0
        s.embed_bytes(self.min_table_bytes())
28
0
            .map_err(|_| SubsetError::SubsetTableError(Post::TAG))?;
29
30
0
        let glyph_names = plan
31
0
            .subset_flags
32
0
            .contains(SubsetFlags::SUBSET_FLAGS_GLYPH_NAMES);
33
        //version 3 does not have any glyph names
34
0
        if !glyph_names {
35
0
            s.copy_assign(self.version_byte_range().start, Version16Dot16::VERSION_3_0);
36
0
        }
37
38
0
        if glyph_names && self.version() == Version16Dot16::VERSION_2_0 {
39
0
            subset_post_v2tail(self, plan, s)
40
0
                .map_err(|_| SubsetError::SubsetTableError(Post::TAG))?;
41
0
        }
42
0
        Ok(())
43
0
    }
44
}
45
46
0
fn subset_post_v2tail(
47
0
    post: &Post,
48
0
    plan: &Plan,
49
0
    s: &mut Serializer,
50
0
) -> Result<(), SerializeErrorFlags> {
51
    // handle empty V2tail
52
0
    let Some(glyph_name_indices) = post.glyph_name_index() else {
53
0
        s.embed(0_u16)?;
54
0
        return Ok(());
55
    };
56
57
    //copy numGlyphs
58
0
    let num_output_glyphs = plan.num_output_glyphs;
59
0
    s.embed(plan.num_output_glyphs as u16)?;
60
61
    // init all glyphNameIndex as 0, which refers to name .notdef
62
    // this handles retain-gid holes, so we don't need to loop all retained glyphs
63
0
    let idx_start = s.allocate_size(num_output_glyphs * 2, false)?;
64
65
0
    let string_data_byte_range = post.string_data_byte_range();
66
0
    let (string_data, index_to_offset) = if string_data_byte_range.is_empty() {
67
0
        (None, Vec::new())
68
    } else {
69
0
        let str_data = post
70
0
            .offset_data()
71
0
            .as_bytes()
72
0
            .get(string_data_byte_range.start..)
73
0
            .ok_or_else(|| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
74
0
        (
75
0
            Some(str_data),
76
0
            index_to_offset(glyph_name_indices, num_output_glyphs, str_data),
77
0
        )
78
    };
79
80
0
    let default_name_idx_map: FnvHashMap<&[u8], u16> = if string_data_byte_range.is_empty() {
81
0
        FnvHashMap::default()
82
    } else {
83
0
        DEFAULT_GLYPH_NAMES
84
0
            .iter()
85
0
            .enumerate()
86
0
            .map(|(i, s)| (s.as_bytes(), i as u16))
87
0
            .collect()
88
    };
89
90
0
    let mut new_name_idx = 258_u16;
91
0
    let mut old_to_new_idx_map = FnvHashMap::default();
92
0
    let mut name_bytes_to_new_idx_map = FnvHashMap::default();
93
94
0
    for (new_gid, old_gid) in plan
95
0
        .new_to_old_gid_list
96
0
        .iter()
97
0
        .map(|(new, old)| (new.to_u32() as usize, old.to_u32() as usize))
98
    {
99
0
        let old_name_idx = glyph_name_indices
100
0
            .get(old_gid)
101
0
            .ok_or_else(|| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_OTHER))?
102
0
            .get();
103
104
0
        let idx_pos = idx_start + new_gid * 2;
105
0
        if old_name_idx < 258 {
106
0
            s.copy_assign(idx_pos, old_name_idx);
107
0
        } else if let Some(idx) = old_to_new_idx_map.get(&old_name_idx) {
108
0
            s.copy_assign(idx_pos, *idx);
109
0
        } else {
110
0
            let Some(string_data) = string_data else {
111
0
                return Err(s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR));
112
            };
113
114
0
            let name_bytes = find_glyph_name(old_name_idx as usize, string_data, &index_to_offset)
115
0
                .ok_or_else(|| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
116
117
0
            if let Some(idx) = name_bytes_to_new_idx_map.get(name_bytes) {
118
0
                s.copy_assign(idx_pos, *idx);
119
0
                old_to_new_idx_map.insert(old_name_idx, *idx);
120
0
            } else {
121
0
                let name_str = name_bytes
122
0
                    .get(1..)
123
0
                    .ok_or_else(|| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
124
                // check if duplicate with DEFAULT names first
125
0
                if let Some(default_idx) = default_name_idx_map.get(name_str) {
126
0
                    s.copy_assign(idx_pos, *default_idx);
127
0
                    name_bytes_to_new_idx_map.insert(name_bytes, *default_idx);
128
0
                    old_to_new_idx_map.insert(old_name_idx, *default_idx);
129
0
                } else {
130
0
                    s.copy_assign(idx_pos, new_name_idx);
131
0
                    name_bytes_to_new_idx_map.insert(name_bytes, new_name_idx);
132
0
                    old_to_new_idx_map.insert(old_name_idx, new_name_idx);
133
0
                    new_name_idx += 1;
134
135
                    // copy name bytes
136
0
                    s.embed_bytes(name_bytes)?;
137
                }
138
            }
139
        }
140
    }
141
0
    Ok(())
142
0
}
143
144
0
fn find_glyph_name<'a>(
145
0
    idx: usize,
146
0
    string_data: &'a [u8],
147
0
    index_to_offset: &[usize],
148
0
) -> Option<&'a [u8]> {
149
0
    let offset = index_to_offset.get(idx - 258)?;
150
0
    let len = string_data.get(*offset)?;
151
0
    if *len == 0 {
152
0
        return None;
153
0
    }
154
0
    let start = *offset;
155
0
    let end = start + *len as usize + 1;
156
0
    string_data.get(start..end)
157
0
}
158
159
// get() in PString is slow, we need to precompute offset into string_data for each index
160
// why it's slow, ref: <https://github.com/googlefonts/fontations/blob/7c9d875992c42d0bda6d0c7f807c25222e863490/read-fonts/src/array.rs#L140>
161
0
fn index_to_offset(
162
0
    glyph_name_indices: &[BigEndian<u16>],
163
0
    num_output_glyphs: usize,
164
0
    string_data: &[u8],
165
0
) -> Vec<usize> {
166
0
    let mut index_to_offset = Vec::with_capacity(glyph_name_indices.len().min(num_output_glyphs));
167
0
    let total_len = string_data.len();
168
0
    let mut pos = 0;
169
170
0
    while pos < total_len && index_to_offset.len() < 65535 {
171
0
        let cur_len = string_data[pos] as usize;
172
0
        if pos + cur_len >= total_len {
173
0
            break;
174
0
        }
175
176
0
        index_to_offset.push(pos);
177
0
        pos += 1 + cur_len;
178
    }
179
180
0
    index_to_offset
181
0
}