Coverage Report

Created: 2026-07-25 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/skera/src/cpal.rs
Line
Count
Source
1
//! impl subset() for CPAL table
2
3
use crate::fnv::FnvHashMap;
4
use crate::{
5
    offset::{SerializeCopy, SerializeSubset},
6
    serialize::{SerializeErrorFlags, Serializer},
7
    NameIdClosure, Plan, Subset, SubsetError, SubsetTable,
8
};
9
use write_fonts::{
10
    read::{
11
        collections::IntSet,
12
        tables::cpal::{ColorRecord, Cpal},
13
        types::{BigEndian, NameId},
14
        FontRef, Offset, TopLevelTable,
15
    },
16
    types::{FixedSize, Offset32},
17
    FontBuilder,
18
};
19
20
// reference: subset() for CPAL in Harfbuzz:
21
// <https://github.com/harfbuzz/harfbuzz/blob/3c02fcd0e8ebd9330634058839941a672f777ac3/src/OT/Color/CPAL/CPAL.hh#L277>
22
impl Subset for Cpal<'_> {
23
0
    fn subset(
24
0
        &self,
25
0
        plan: &Plan,
26
0
        _font: &FontRef,
27
0
        s: &mut Serializer,
28
0
        _builder: &mut FontBuilder,
29
0
    ) -> Result<(), SubsetError> {
30
0
        subset_v0(self, plan, s).map_err(|_| SubsetError::SubsetTableError(Cpal::TAG))?;
31
0
        if self.version() == 1 {
32
0
            subset_v1(self, plan, s).map_err(|_| SubsetError::SubsetTableError(Cpal::TAG))?;
33
0
        }
34
0
        Ok(())
35
0
    }
36
}
37
38
0
fn subset_v0(cpal: &Cpal, plan: &Plan, s: &mut Serializer) -> Result<(), SerializeErrorFlags> {
39
0
    let colr_index_map = &plan.colr_palettes;
40
0
    if colr_index_map.is_empty() || cpal.num_palettes() == 0 {
41
0
        return Err(SerializeErrorFlags::SERIALIZE_ERROR_EMPTY);
42
0
    }
43
44
0
    let retained_entries: IntSet<u16> = colr_index_map
45
0
        .keys()
46
0
        .filter(|&n| *n != 0xFFFF)
47
0
        .copied()
48
0
        .collect();
49
0
    if retained_entries.is_empty() {
50
0
        return Err(SerializeErrorFlags::SERIALIZE_ERROR_EMPTY);
51
0
    }
52
53
0
    s.embed(cpal.version())?;
54
55
0
    let num_colors = retained_entries.len() as u16;
56
0
    s.embed(num_colors)?;
57
58
0
    s.embed(cpal.num_palettes())?;
59
    //numColorRecords, initialized to 0
60
0
    let num_color_records_pos = s.embed(0_u16)?;
61
62
    //colorRecordsArrayOffset
63
0
    let color_records_arr_offset_pos = s.embed(0_u32)?;
64
65
0
    let Some(Ok(color_records)) = cpal.color_records_array() else {
66
0
        return Err(s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR));
67
    };
68
69
0
    let color_record_indices = cpal.color_record_indices();
70
0
    let first_record_idx_map = Offset32::serialize_subset(
71
0
        &color_records,
72
0
        s,
73
0
        plan,
74
0
        (color_record_indices, &retained_entries),
75
0
        color_records_arr_offset_pos,
76
0
    )?;
77
78
0
    let num_color_records = (first_record_idx_map.len() as u16) * num_colors;
79
0
    s.copy_assign(num_color_records_pos, num_color_records);
80
81
    //colorRecordIndices
82
0
    for idx in color_record_indices {
83
0
        let new_idx = first_record_idx_map.get(&idx.get()).unwrap();
84
0
        s.embed(*new_idx)?;
85
    }
86
0
    Ok(())
87
0
}
88
89
0
fn subset_v1(cpal: &Cpal, plan: &Plan, s: &mut Serializer) -> Result<(), SerializeErrorFlags> {
90
0
    let palette_types_offset_pos = s.embed(0_u32)?;
91
0
    let palette_labels_offset_pos = s.embed(0_u32)?;
92
0
    let palette_entry_labels_offset_pos = s.embed(0_u32)?;
93
94
0
    let num_palettes = cpal.num_palettes();
95
    // v1 must have this field
96
0
    let Some(palette_types_offset) = cpal.palette_types_array_offset() else {
97
0
        return Err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR);
98
    };
99
100
0
    let palette_types_offset = palette_types_offset.offset().to_usize();
101
0
    if palette_types_offset != 0 {
102
        //size of PaletteType is 4
103
0
        let bytes_len = (num_palettes as usize) * 4;
104
0
        let src_bytes = cpal
105
0
            .offset_data()
106
0
            .as_bytes()
107
0
            .get(palette_types_offset..palette_types_offset + bytes_len)
108
0
            .ok_or(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR)?;
109
110
0
        Offset32::serialize_copy_from_bytes(src_bytes, s, palette_types_offset_pos)?;
111
0
    }
112
113
    // v1 must have this field
114
0
    let Some(palette_labels_offset) = cpal.palette_labels_array_offset() else {
115
0
        return Err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR);
116
    };
117
0
    let palette_labels_offset = palette_labels_offset.offset().to_usize();
118
0
    if palette_labels_offset != 0 {
119
0
        let bytes_len = (num_palettes as usize) * NameId::RAW_BYTE_LEN;
120
0
        let src_bytes = cpal
121
0
            .offset_data()
122
0
            .as_bytes()
123
0
            .get(palette_labels_offset..palette_labels_offset + bytes_len)
124
0
            .ok_or(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR)?;
125
126
0
        Offset32::serialize_copy_from_bytes(src_bytes, s, palette_labels_offset_pos)?;
127
0
    }
128
129
0
    if let Some(palette_entry_labels) = cpal
130
0
        .palette_entry_labels_array()
131
0
        .transpose()
132
0
        .map_err(|_| SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR)?
133
    {
134
0
        Offset32::serialize_subset(
135
0
            &palette_entry_labels,
136
0
            s,
137
0
            plan,
138
0
            (),
139
0
            palette_entry_labels_offset_pos,
140
0
        )?;
141
0
    }
142
143
0
    Ok(())
144
0
}
145
146
impl<'a> SubsetTable<'a> for &'a [ColorRecord] {
147
    // color_record_indices, num_palette_entries
148
    type ArgsForSubset = (&'a [BigEndian<u16>], &'a IntSet<u16>);
149
    type Output = FnvHashMap<u16, u16>;
150
0
    fn subset(
151
0
        &self,
152
0
        _plan: &Plan,
153
0
        s: &mut Serializer,
154
0
        args: Self::ArgsForSubset,
155
0
    ) -> Result<Self::Output, SerializeErrorFlags> {
156
0
        let (color_record_indices, retained_entries) = args;
157
0
        let num_palette_entries = retained_entries.len() as u16;
158
159
0
        let mut new_idx = 0_u16;
160
0
        let mut first_record_idx_map = FnvHashMap::default();
161
0
        for first_record_idx in color_record_indices {
162
0
            let first_idx = first_record_idx.get();
163
0
            if first_record_idx_map.contains_key(&first_idx) {
164
0
                continue;
165
0
            }
166
167
0
            for entry_idx in retained_entries.iter() {
168
0
                let record_idx = first_idx + entry_idx;
169
0
                let Some(record) = self.get(record_idx as usize) else {
170
0
                    return Err(s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_OTHER));
171
                };
172
0
                s.embed(record.blue())?;
173
0
                s.embed(record.green())?;
174
0
                s.embed(record.red())?;
175
0
                s.embed(record.alpha())?;
176
            }
177
0
            first_record_idx_map.insert(first_idx, new_idx);
178
0
            new_idx += num_palette_entries;
179
        }
180
0
        Ok(first_record_idx_map)
181
0
    }
182
}
183
184
impl<'a> SubsetTable<'a> for &'a [BigEndian<NameId>] {
185
    type ArgsForSubset = ();
186
    type Output = ();
187
0
    fn subset(
188
0
        &self,
189
0
        plan: &Plan,
190
0
        s: &mut Serializer,
191
0
        _args: Self::ArgsForSubset,
192
0
    ) -> Result<Self::Output, SerializeErrorFlags> {
193
0
        for (entry, name_id) in self.iter().enumerate() {
194
0
            if !plan.colr_palettes.contains_key(&(entry as u16)) {
195
0
                continue;
196
0
            }
197
0
            s.embed(name_id.get())?;
198
        }
199
0
        Ok(())
200
0
    }
201
}
202
203
impl NameIdClosure for Cpal<'_> {
204
0
    fn collect_name_ids(&self, plan: &mut Plan) {
205
0
        if self.version() == 0 {
206
0
            return;
207
0
        }
208
209
0
        if let Some(Ok(palette_labels)) = self.palette_labels_array() {
210
0
            plan.name_ids
211
0
                .extend_unsorted(palette_labels.iter().map(|x| x.get()));
212
0
        }
213
214
0
        if let Some(Ok(palette_entry_labels)) = self.palette_entry_labels_array() {
215
0
            plan.name_ids.extend_unsorted(
216
0
                palette_entry_labels
217
0
                    .iter()
218
0
                    .enumerate()
219
0
                    .filter(|x| plan.colr_palettes.contains_key(&(x.0 as u16)))
220
0
                    .map(|x| x.1.get()),
221
            );
222
0
        }
223
0
    }
224
}
225
226
#[cfg(test)]
227
mod test {
228
    use super::*;
229
    use write_fonts::read::TableProvider;
230
    #[test]
231
    fn test_subset_cpal_retain_all_glyphs() {
232
        let ttf: &[u8] = include_bytes!("../test-data/fonts/TwemojiMozilla.subset.ttf");
233
        let font = FontRef::new(ttf).unwrap();
234
        let cpal = font.cpal().unwrap();
235
236
        let mut builder = FontBuilder::new();
237
238
        let mut plan = Plan::default();
239
        plan.colr_palettes.insert(2, 0);
240
        plan.colr_palettes.insert(11, 1);
241
242
        let mut s = Serializer::new(1024);
243
        assert_eq!(s.start_serialize(), Ok(()));
244
        let ret = cpal.subset(&plan, &font, &mut s, &mut builder);
245
        assert!(ret.is_ok());
246
        assert!(!s.in_error());
247
        s.end_serialize();
248
249
        let subsetted_data = s.copy_bytes();
250
        let expected_data: [u8; 22] = [
251
            0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00,
252
            0x44, 0x2e, 0xdd, 0xff, 0xff, 0xff, 0xff, 0xff,
253
        ];
254
        assert_eq!(subsetted_data, expected_data);
255
    }
256
}