Coverage Report

Created: 2026-07-25 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fontations/incremental-font-transfer/src/glyph_keyed.rs
Line
Count
Source
1
//! Implementation of Glyph Keyed patch application.
2
//!
3
//! Glyph Keyed patches are a type of incremental font patch which stores opaque data blobs
4
//! keyed by glyph id. Patch application places the data blobs into the appropriate place
5
//! in the base font based on the associated glyph id.
6
//!
7
//! Glyph Keyed patches are specified here:
8
//! <https://w3c.github.io/IFT/Overview.html#glyph-keyed>
9
use crate::patchmap::IftTableTag;
10
use crate::table_keyed::copy_unprocessed_tables;
11
use crate::{font_patch::PatchingError, patch_group::PatchInfo};
12
13
use font_types::{Scalar, Uint24};
14
use read_fonts::ps::cff::{index::Index, v1::Index as Index1, v2::Index as Index2};
15
use read_fonts::{
16
    collections::IntSet,
17
    tables::{
18
        cff::Cff,
19
        cff2::Cff2,
20
        glyf::Glyf,
21
        gvar::{Gvar, GvarFlags},
22
        ift::{GlyphKeyedPatch, GlyphPatches},
23
        ift::{IFTX_TAG, IFT_TAG},
24
        loca::Loca,
25
    },
26
    types::Tag,
27
    FontData, FontRead, FontRef, ReadError, TableProvider, TopLevelTable,
28
};
29
30
use shared_brotli_patch_decoder::SharedBrotliDecoder;
31
use skera::serialize::{OffsetWhence, SerializeErrorFlags, Serializer};
32
use skrifa::GlyphId;
33
use std::collections::{BTreeSet, HashMap};
34
use std::ops::{Range, RangeInclusive};
35
36
use write_fonts::FontBuilder;
37
38
10
pub(crate) fn apply_glyph_keyed_patches<D: SharedBrotliDecoder>(
39
10
    patches: &[(&PatchInfo, GlyphKeyedPatch<'_>)],
40
10
    font: &FontRef,
41
10
    brotli_decoder: &D,
42
10
) -> Result<Vec<u8>, PatchingError> {
43
10
    let mut decompression_buffer: Vec<Vec<u8>> = Vec::with_capacity(patches.len());
44
45
10
    for (_, patch) in patches {
46
10
        if patch.format() != Tag::new(b"ifgk") {
47
10
            return Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'"));
48
0
        }
49
50
0
        decompression_buffer.push(
51
0
            brotli_decoder
52
0
                .decode(
53
0
                    patch.brotli_stream(),
54
0
                    None,
55
0
                    patch.max_uncompressed_length() as usize,
56
                )
57
0
                .map_err(PatchingError::from)?,
58
        );
59
    }
60
61
0
    let mut glyph_patches: Vec<GlyphPatches<'_>> = vec![];
62
0
    for (raw_data, patch) in decompression_buffer.iter().zip(patches) {
63
0
        glyph_patches.push(
64
0
            GlyphPatches::read(FontData::new(raw_data), patch.1.flags())
65
0
                .map_err(PatchingError::PatchParsingFailed)?,
66
        );
67
    }
68
69
0
    let num_glyphs = font
70
0
        .maxp()
71
0
        .map_err(PatchingError::FontParsingFailed)?
72
0
        .num_glyphs();
73
74
0
    let max_glyph_id = GlyphId::new(num_glyphs.checked_sub(1).ok_or(
75
0
        PatchingError::FontParsingFailed(ReadError::MalformedData("Font has no glyphs.")),
76
0
    )? as u32);
77
78
    // IFT and IFTX tables will be modified and then copied below.
79
0
    let mut processed_tables = BTreeSet::from([IFT_TAG, IFTX_TAG]);
80
0
    let mut font_builder = FontBuilder::new();
81
82
0
    for table_tag in table_tag_list(&glyph_patches)? {
83
0
        if table_tag == Glyf::TAG {
84
0
            let (Some(glyf), Ok(loca)) = (font.table_data(Glyf::TAG), font.loca(None)) else {
85
0
                return Err(PatchingError::InvalidPatch(
86
0
                    "Trying to patch glyf/loca but base font doesn't have them.",
87
0
                ));
88
            };
89
0
            patch_offset_array(
90
                Glyf::TAG,
91
0
                &glyph_patches,
92
0
                GlyfAndLoca {
93
0
                    loca,
94
0
                    glyf: glyf.as_bytes(),
95
0
                },
96
0
                max_glyph_id,
97
0
                &mut font_builder,
98
0
            )?;
99
            // glyf patch application also generates a loca table.
100
0
            processed_tables.insert(table_tag);
101
0
            processed_tables.insert(Loca::TAG);
102
0
        } else if table_tag == Gvar::TAG {
103
0
            let Ok(gvar) = font.gvar() else {
104
0
                return Err(PatchingError::InvalidPatch(
105
0
                    "Trying to patch gvar but base font doesn't have them.",
106
0
                ));
107
            };
108
0
            patch_offset_array(
109
                Gvar::TAG,
110
0
                &glyph_patches,
111
0
                gvar,
112
0
                max_glyph_id,
113
0
                &mut font_builder,
114
0
            )?;
115
0
            processed_tables.insert(Gvar::TAG);
116
0
        } else if table_tag == Cff::TAG {
117
0
            let Some(charstrings_offset) = font
118
0
                .ift()
119
0
                .ok()
120
0
                .as_ref()
121
0
                .and_then(|t| t.cff_charstrings_offset())
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::NoopBrotliDecoder>::{closure#0}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::BuiltInBrotliDecoder>::{closure#0}
122
            else {
123
0
                return Err(PatchingError::InvalidPatch(
124
0
                    "Required CFF charstrings offset is missing from IFT table.",
125
0
                ));
126
            };
127
0
            patch_offset_array(
128
0
                table_tag,
129
0
                &glyph_patches,
130
0
                CFFAndCharStrings::from_cff_font(font, charstrings_offset, max_glyph_id)
131
0
                    .map_err(PatchingError::from)?,
132
0
                max_glyph_id,
133
0
                &mut font_builder,
134
0
            )?;
135
0
            processed_tables.insert(table_tag);
136
0
        } else if table_tag == Cff2::TAG {
137
0
            let Some(charstrings_offset) = font
138
0
                .ift()
139
0
                .ok()
140
0
                .as_ref()
141
0
                .and_then(|t| t.cff2_charstrings_offset())
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::NoopBrotliDecoder>::{closure#1}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::BuiltInBrotliDecoder>::{closure#1}
142
            else {
143
0
                return Err(PatchingError::InvalidPatch(
144
0
                    "Required CFF2 charstrings offset is missing from IFT table.",
145
0
                ));
146
            };
147
0
            patch_offset_array(
148
0
                table_tag,
149
0
                &glyph_patches,
150
0
                CFFAndCharStrings::from_cff2_font(font, charstrings_offset, max_glyph_id)
151
0
                    .map_err(PatchingError::from)?,
152
0
                max_glyph_id,
153
0
                &mut font_builder,
154
0
            )?;
155
0
            processed_tables.insert(table_tag);
156
        } else {
157
            // All other table tags are ignored.
158
0
            continue;
159
        }
160
    }
161
162
    // Mark patches applied in IFT and IFTX as needed, copy the modified tables into the font builder.
163
0
    let mut new_itf_data = font
164
0
        .table_data(IFT_TAG)
165
0
        .map(|data| data.as_bytes().to_vec());
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::NoopBrotliDecoder>::{closure#2}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::BuiltInBrotliDecoder>::{closure#2}
166
0
    let mut new_itfx_data = font
167
0
        .table_data(IFTX_TAG)
168
0
        .map(|data| data.as_bytes().to_vec());
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::NoopBrotliDecoder>::{closure#3}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::BuiltInBrotliDecoder>::{closure#3}
169
0
    for (info, _) in patches {
170
0
        let data = match info.tag() {
171
0
            IftTableTag::Ift(_) => new_itf_data.as_mut().ok_or(PatchingError::InternalError)?,
172
0
            IftTableTag::Iftx(_) => new_itfx_data.as_mut().ok_or(PatchingError::InternalError)?,
173
        };
174
175
0
        for bit_index in info.application_flag_bit_indices() {
176
0
            let byte_index = (bit_index as usize) / 8;
177
0
            let bit_index = (bit_index as usize % 8) as u8;
178
0
            let byte = data
179
0
                .get_mut(byte_index)
180
0
                .ok_or(PatchingError::InternalError)?;
181
0
            *byte |= 1 << bit_index;
182
        }
183
    }
184
185
0
    if let Some(data) = new_itf_data {
186
0
        font_builder.add_raw(IFT_TAG, data);
187
0
    }
188
0
    if let Some(data) = new_itfx_data {
189
0
        font_builder.add_raw(IFTX_TAG, data);
190
0
    }
191
192
0
    copy_unprocessed_tables(font, processed_tables, &mut font_builder);
193
194
0
    Ok(font_builder.build())
195
10
}
incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::NoopBrotliDecoder>
Line
Count
Source
38
10
pub(crate) fn apply_glyph_keyed_patches<D: SharedBrotliDecoder>(
39
10
    patches: &[(&PatchInfo, GlyphKeyedPatch<'_>)],
40
10
    font: &FontRef,
41
10
    brotli_decoder: &D,
42
10
) -> Result<Vec<u8>, PatchingError> {
43
10
    let mut decompression_buffer: Vec<Vec<u8>> = Vec::with_capacity(patches.len());
44
45
10
    for (_, patch) in patches {
46
10
        if patch.format() != Tag::new(b"ifgk") {
47
10
            return Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'"));
48
0
        }
49
50
0
        decompression_buffer.push(
51
0
            brotli_decoder
52
0
                .decode(
53
0
                    patch.brotli_stream(),
54
0
                    None,
55
0
                    patch.max_uncompressed_length() as usize,
56
                )
57
0
                .map_err(PatchingError::from)?,
58
        );
59
    }
60
61
0
    let mut glyph_patches: Vec<GlyphPatches<'_>> = vec![];
62
0
    for (raw_data, patch) in decompression_buffer.iter().zip(patches) {
63
0
        glyph_patches.push(
64
0
            GlyphPatches::read(FontData::new(raw_data), patch.1.flags())
65
0
                .map_err(PatchingError::PatchParsingFailed)?,
66
        );
67
    }
68
69
0
    let num_glyphs = font
70
0
        .maxp()
71
0
        .map_err(PatchingError::FontParsingFailed)?
72
0
        .num_glyphs();
73
74
0
    let max_glyph_id = GlyphId::new(num_glyphs.checked_sub(1).ok_or(
75
0
        PatchingError::FontParsingFailed(ReadError::MalformedData("Font has no glyphs.")),
76
0
    )? as u32);
77
78
    // IFT and IFTX tables will be modified and then copied below.
79
0
    let mut processed_tables = BTreeSet::from([IFT_TAG, IFTX_TAG]);
80
0
    let mut font_builder = FontBuilder::new();
81
82
0
    for table_tag in table_tag_list(&glyph_patches)? {
83
0
        if table_tag == Glyf::TAG {
84
0
            let (Some(glyf), Ok(loca)) = (font.table_data(Glyf::TAG), font.loca(None)) else {
85
0
                return Err(PatchingError::InvalidPatch(
86
0
                    "Trying to patch glyf/loca but base font doesn't have them.",
87
0
                ));
88
            };
89
0
            patch_offset_array(
90
                Glyf::TAG,
91
0
                &glyph_patches,
92
0
                GlyfAndLoca {
93
0
                    loca,
94
0
                    glyf: glyf.as_bytes(),
95
0
                },
96
0
                max_glyph_id,
97
0
                &mut font_builder,
98
0
            )?;
99
            // glyf patch application also generates a loca table.
100
0
            processed_tables.insert(table_tag);
101
0
            processed_tables.insert(Loca::TAG);
102
0
        } else if table_tag == Gvar::TAG {
103
0
            let Ok(gvar) = font.gvar() else {
104
0
                return Err(PatchingError::InvalidPatch(
105
0
                    "Trying to patch gvar but base font doesn't have them.",
106
0
                ));
107
            };
108
0
            patch_offset_array(
109
                Gvar::TAG,
110
0
                &glyph_patches,
111
0
                gvar,
112
0
                max_glyph_id,
113
0
                &mut font_builder,
114
0
            )?;
115
0
            processed_tables.insert(Gvar::TAG);
116
0
        } else if table_tag == Cff::TAG {
117
0
            let Some(charstrings_offset) = font
118
0
                .ift()
119
0
                .ok()
120
0
                .as_ref()
121
0
                .and_then(|t| t.cff_charstrings_offset())
122
            else {
123
0
                return Err(PatchingError::InvalidPatch(
124
0
                    "Required CFF charstrings offset is missing from IFT table.",
125
0
                ));
126
            };
127
0
            patch_offset_array(
128
0
                table_tag,
129
0
                &glyph_patches,
130
0
                CFFAndCharStrings::from_cff_font(font, charstrings_offset, max_glyph_id)
131
0
                    .map_err(PatchingError::from)?,
132
0
                max_glyph_id,
133
0
                &mut font_builder,
134
0
            )?;
135
0
            processed_tables.insert(table_tag);
136
0
        } else if table_tag == Cff2::TAG {
137
0
            let Some(charstrings_offset) = font
138
0
                .ift()
139
0
                .ok()
140
0
                .as_ref()
141
0
                .and_then(|t| t.cff2_charstrings_offset())
142
            else {
143
0
                return Err(PatchingError::InvalidPatch(
144
0
                    "Required CFF2 charstrings offset is missing from IFT table.",
145
0
                ));
146
            };
147
0
            patch_offset_array(
148
0
                table_tag,
149
0
                &glyph_patches,
150
0
                CFFAndCharStrings::from_cff2_font(font, charstrings_offset, max_glyph_id)
151
0
                    .map_err(PatchingError::from)?,
152
0
                max_glyph_id,
153
0
                &mut font_builder,
154
0
            )?;
155
0
            processed_tables.insert(table_tag);
156
        } else {
157
            // All other table tags are ignored.
158
0
            continue;
159
        }
160
    }
161
162
    // Mark patches applied in IFT and IFTX as needed, copy the modified tables into the font builder.
163
0
    let mut new_itf_data = font
164
0
        .table_data(IFT_TAG)
165
0
        .map(|data| data.as_bytes().to_vec());
166
0
    let mut new_itfx_data = font
167
0
        .table_data(IFTX_TAG)
168
0
        .map(|data| data.as_bytes().to_vec());
169
0
    for (info, _) in patches {
170
0
        let data = match info.tag() {
171
0
            IftTableTag::Ift(_) => new_itf_data.as_mut().ok_or(PatchingError::InternalError)?,
172
0
            IftTableTag::Iftx(_) => new_itfx_data.as_mut().ok_or(PatchingError::InternalError)?,
173
        };
174
175
0
        for bit_index in info.application_flag_bit_indices() {
176
0
            let byte_index = (bit_index as usize) / 8;
177
0
            let bit_index = (bit_index as usize % 8) as u8;
178
0
            let byte = data
179
0
                .get_mut(byte_index)
180
0
                .ok_or(PatchingError::InternalError)?;
181
0
            *byte |= 1 << bit_index;
182
        }
183
    }
184
185
0
    if let Some(data) = new_itf_data {
186
0
        font_builder.add_raw(IFT_TAG, data);
187
0
    }
188
0
    if let Some(data) = new_itfx_data {
189
0
        font_builder.add_raw(IFTX_TAG, data);
190
0
    }
191
192
0
    copy_unprocessed_tables(font, processed_tables, &mut font_builder);
193
194
0
    Ok(font_builder.build())
195
10
}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::apply_glyph_keyed_patches::<shared_brotli_patch_decoder::BuiltInBrotliDecoder>
196
197
0
fn table_tag_list(glyph_patches: &[GlyphPatches]) -> Result<BTreeSet<Tag>, PatchingError> {
198
0
    for patches in glyph_patches {
199
0
        if patches
200
0
            .tables()
201
0
            .iter()
202
0
            .zip(patches.tables().iter().skip(1))
203
0
            .any(|(prev_tag, next_tag)| next_tag <= prev_tag)
204
        {
205
0
            return Err(PatchingError::InvalidPatch(
206
0
                "Duplicate or unsorted table tag.",
207
0
            ));
208
0
        }
209
    }
210
211
0
    Ok(glyph_patches
212
0
        .iter()
213
0
        .flat_map(|patch| patch.tables())
214
0
        .map(|tag| tag.get())
215
0
        .collect::<BTreeSet<Tag>>())
216
0
}
217
218
0
fn dedup_gid_replacement_data<'a>(
219
0
    glyph_patches: impl Iterator<Item = &'a GlyphPatches<'a>>,
220
0
    table_tag: Tag,
221
0
) -> Result<(IntSet<GlyphId>, Vec<&'a [u8]>), ReadError> {
222
    // Since the specification allows us to freely choose patch application order (for groups of glyph keyed patches,
223
    // see: https://w3c.github.io/IFT/Overview.html#extend-font-subset) if two patches affect the same gid we can choose
224
    // one arbitrarily to remain applied. In this case we choose the first applied patch for each gid be the one that takes
225
    // priority.
226
0
    let mut gids: IntSet<GlyphId> = IntSet::default();
227
0
    let mut data_for_gid: HashMap<GlyphId, &'a [u8]> = HashMap::default();
228
0
    for glyph_patch in glyph_patches {
229
0
        let Some((table_index, _)) = glyph_patch
230
0
            .tables()
231
0
            .iter()
232
0
            .enumerate()
233
0
            .find(|(_, tag)| tag.get() == table_tag)
234
        else {
235
0
            continue;
236
        };
237
238
0
        glyph_patch
239
0
            .glyph_data_for_table(table_index)
240
0
            .try_for_each(|result| {
241
0
                let (gid, data) = result?;
242
0
                data_for_gid.entry(gid).or_insert(data);
243
0
                gids.insert(gid);
244
0
                Ok(())
245
0
            })?;
246
    }
247
248
0
    let mut deduped: Vec<&'a [u8]> = Vec::with_capacity(data_for_gid.len());
249
0
    gids.iter().for_each(|gid| {
250
        // in the above loop for each gid in gids there is always an  entry in data_for_gid
251
0
        deduped.push(data_for_gid.get(&gid).unwrap());
252
0
    });
253
254
0
    Ok((gids, deduped))
255
0
}
256
257
0
fn retained_glyphs_in_font(
258
0
    replace_gids: &IntSet<GlyphId>,
259
0
    max_glyph_id: GlyphId,
260
0
) -> impl Iterator<Item = RangeInclusive<GlyphId>> + '_ {
261
0
    replace_gids
262
0
        .iter_excluded_ranges()
263
0
        .filter_map(move |range| {
264
            // Filter out values beyond max_glyph_id.
265
0
            if *range.start() > max_glyph_id {
266
0
                return None;
267
0
            }
268
0
            if *range.end() > max_glyph_id {
269
0
                return Some(*range.start()..=max_glyph_id);
270
0
            }
271
0
            Some(range)
272
0
        })
273
0
}
274
275
0
fn retained_glyphs_total_size<T: GlyphDataOffsetArray>(
276
0
    gids: &IntSet<GlyphId>,
277
0
    offsets: &T,
278
0
    max_glyph_id: GlyphId,
279
0
) -> Result<usize, PatchingError> {
280
0
    let mut total_size = 0usize;
281
0
    for keep_range in retained_glyphs_in_font(gids, max_glyph_id) {
282
0
        let start = *keep_range.start();
283
0
        let end: GlyphId = keep_range
284
0
            .end()
285
0
            .to_u32()
286
0
            .checked_add(1)
287
0
            .ok_or(PatchingError::InternalError)?
288
0
            .into();
289
290
0
        let start_offset = offsets.offset_for(start)?;
291
0
        let end_offset = offsets.offset_for(end)?;
292
293
0
        total_size += end_offset
294
0
            .checked_sub(start_offset) // TODO: this can be removed if we pre-verify ascending order
295
0
            .ok_or(PatchingError::FontParsingFailed(ReadError::MalformedData(
296
0
                "offset entries are not in ascending order",
297
0
            )))? as usize;
298
    }
299
300
0
    Ok(total_size)
301
0
}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::retained_glyphs_total_size::<incremental_font_transfer::glyph_keyed::GlyfAndLoca>
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::retained_glyphs_total_size::<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::retained_glyphs_total_size::<read_fonts::tables::gvar::Gvar>
302
303
struct OffsetArrayAndData {
304
    data: Vec<u8>,
305
    offset_array: Vec<u8>,
306
}
307
308
struct OffsetArrayBuilder<'a, T> {
309
    gids: &'a IntSet<GlyphId>,
310
    max_glyph_id: GlyphId,
311
    replacement_data: &'a [&'a [u8]],
312
    offset_array: &'a T,
313
    new_data_len: usize,
314
    new_offsets_len: usize,
315
}
316
317
impl<T: GlyphDataOffsetArray> OffsetArrayBuilder<'_, T> {
318
0
    fn build<OffsetInfo: OffsetTypeInfo, OffsetType: Scalar + TryFrom<usize>>(
319
0
        self,
320
0
    ) -> Result<OffsetArrayAndData, PatchingError> {
321
0
        if !self.offset_array.all_offsets_are_ascending() {
322
0
            return Err(PatchingError::FontParsingFailed(ReadError::MalformedData(
323
0
                "offset array contains unordered offsets.",
324
0
            )));
325
0
        }
326
327
0
        let mut new_data = Serializer::new(self.new_data_len);
328
0
        new_data
329
0
            .start_serialize()
330
0
            .map_err(PatchingError::SerializationError)?;
331
0
        let mut new_offsets = Serializer::new(self.new_offsets_len);
332
0
        new_offsets
333
0
            .start_serialize()
334
0
            .map_err(PatchingError::SerializationError)?;
335
336
0
        let divisor = OffsetInfo::DIVISOR;
337
0
        let bias = OffsetInfo::BIAS as usize;
338
339
0
        let mut replace_it = self.gids.iter_ranges().peekable();
340
0
        let mut keep_it = retained_glyphs_in_font(self.gids, self.max_glyph_id).peekable();
341
0
        let mut replacement_data_it = self.replacement_data.iter();
342
0
        let mut write_index = 0;
343
344
        loop {
345
0
            let (range, replace) = match (replace_it.peek(), keep_it.peek()) {
346
0
                (Some(replace), Some(keep)) => {
347
0
                    if replace.start() <= keep.start() {
348
0
                        (replace_it.next().unwrap(), true)
349
                    } else {
350
0
                        (keep_it.next().unwrap(), false)
351
                    }
352
                }
353
0
                (Some(_), None) => (replace_it.next().unwrap(), true),
354
0
                (None, Some(_)) => (keep_it.next().unwrap(), false),
355
0
                (None, None) => break,
356
            };
357
358
0
            let (start, end) = (range.start().to_u32(), range.end().to_u32());
359
360
0
            if replace {
361
0
                for _ in start..=end {
362
0
                    let data = *replacement_data_it
363
0
                        .next()
364
0
                        .ok_or(PatchingError::InternalError)?;
365
366
0
                    new_data
367
0
                        .embed_bytes(data)
368
0
                        .map_err(PatchingError::SerializationError)?;
369
370
0
                    let new_off: OffsetType = ((write_index / divisor) + bias)
371
0
                        .try_into()
372
0
                        .map_err(|_| PatchingError::InternalError)?;
373
374
0
                    new_offsets
375
0
                        .embed(new_off)
376
0
                        .map_err(PatchingError::SerializationError)?;
377
378
0
                    write_index += data.len();
379
                    // Add padding if the offset gets divided
380
0
                    if divisor > 1 {
381
0
                        let padding = data.len() % divisor;
382
0
                        write_index += padding;
383
0
                        new_data
384
0
                            .pad(padding)
385
0
                            .map_err(PatchingError::SerializationError)?;
386
0
                    }
387
                }
388
            } else {
389
0
                let start_off = self.offset_array.offset_for(start.into())? as usize;
390
0
                let end_off = self.offset_array.offset_for(
391
0
                    end.checked_add(1)
392
0
                        .ok_or(PatchingError::InternalError)?
393
0
                        .into(),
394
0
                )? as usize;
395
396
0
                let len = end_off
397
0
                    .checked_sub(start_off)
398
0
                    .ok_or(PatchingError::InternalError)?;
399
0
                new_data
400
0
                    .embed_bytes(self.offset_array.get(start_off..end_off)?)
401
0
                    .map_err(PatchingError::SerializationError)?;
402
403
0
                for gid in start..=end {
404
0
                    let cur_off = self.offset_array.offset_for(gid.into())? as usize;
405
0
                    let new_off = cur_off - start_off + write_index;
406
407
0
                    let new_off: OffsetType = ((new_off / divisor) + bias)
408
0
                        .try_into()
409
0
                        .map_err(|_| PatchingError::InternalError)?;
410
0
                    new_offsets
411
0
                        .embed(new_off)
412
0
                        .map_err(PatchingError::SerializationError)?;
413
                }
414
415
0
                write_index += len;
416
            }
417
        }
418
419
        // Write the last offset
420
0
        let new_off: OffsetType = ((write_index / divisor) + bias)
421
0
            .try_into()
422
0
            .map_err(|_| PatchingError::InternalError)?;
423
0
        new_offsets
424
0
            .embed(new_off)
425
0
            .map_err(PatchingError::SerializationError)?;
426
427
0
        new_data.end_serialize();
428
0
        new_offsets.end_serialize();
429
430
0
        Ok(OffsetArrayAndData {
431
0
            data: new_data.copy_bytes(),
432
0
            offset_array: new_offsets.copy_bytes(),
433
0
        })
434
0
    }
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::CffOneInfo, u8>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::CffTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::CffFourInfo, u32>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::CffThreeInfo, font_types::uint24::Uint24>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::GlyfAndLoca>>::build::<incremental_font_transfer::glyph_keyed::LongInfo, u32>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::CffOneInfo, u8>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::CffTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::CffFourInfo, u32>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::CffThreeInfo, font_types::uint24::Uint24>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>>::build::<incremental_font_transfer::glyph_keyed::LongInfo, u32>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::CffOneInfo, u8>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::CffTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::CffFourInfo, u32>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::CffThreeInfo, font_types::uint24::Uint24>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo, u16>
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::OffsetArrayBuilder<read_fonts::tables::gvar::Gvar>>::build::<incremental_font_transfer::glyph_keyed::LongInfo, u32>
435
}
436
437
0
fn patch_offset_array<'a, T: GlyphDataOffsetArray>(
438
0
    table_tag: Tag,
439
0
    glyph_patches: &'a [GlyphPatches<'a>],
440
0
    offset_array: T,
441
0
    max_glyph_id: GlyphId,
442
0
    font_builder: &mut FontBuilder,
443
0
) -> Result<(), PatchingError> {
444
    // Step 0: merge the individual patches into a list of replacement data for gid.
445
    // TODO(garretrieger): special case where gids is empty, just returned umodified copy of glyf + loca?
446
0
    let offset_type = offset_array.offset_type();
447
0
    let (gids, replacement_data) = dedup_gid_replacement_data(glyph_patches.iter(), table_tag)
448
0
        .map_err(PatchingError::PatchParsingFailed)?;
449
450
    // Step 1: determine the new total size of the data portion.
451
0
    let mut total_data_size = retained_glyphs_total_size(&gids, &offset_array, max_glyph_id)?;
452
0
    for data in replacement_data.iter() {
453
0
        let len = data.len();
454
0
        // note: include padding when needed (if offsets are divided for storage)
455
0
        total_data_size += len + (len % offset_type.offset_divisor());
456
0
    }
457
458
    // TODO(garretrieger): pre-check loca has all ascending offsets.
459
460
    // Check to see if the offset size needs to be upgraded
461
0
    let new_offset_type = if total_data_size > offset_type.max_representable_size() {
462
0
        offset_array
463
0
            .available_offset_types()
464
0
            .find(|candidate_type| candidate_type.max_representable_size() >= total_data_size)
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<incremental_font_transfer::glyph_keyed::GlyfAndLoca>::{closure#0}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>::{closure#0}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<read_fonts::tables::gvar::Gvar>::{closure#0}
465
0
            .ok_or(PatchingError::SerializationError(
466
0
                SerializeErrorFlags::SERIALIZE_ERROR_OFFSET_OVERFLOW,
467
0
            ))?
468
    } else {
469
0
        offset_type
470
    };
471
472
0
    if gids.last().unwrap_or(GlyphId::new(0)) > max_glyph_id {
473
0
        return Err(PatchingError::InvalidPatch(
474
0
            "Patch would add a glyph beyond this fonts maximum.",
475
0
        ));
476
0
    }
477
478
    // Step 2: patch together the new data array (by copying in ranges of data in the correct order).
479
    // Note: we synthesize using whatever new_offset_type was selected above.
480
    // Note: max_glyph_id + 2 here because we want num glyphs + 1.
481
0
    let offsets_size = (max_glyph_id.to_u32() as usize + 2) * new_offset_type.offset_width();
482
483
0
    let offset_array_builder = OffsetArrayBuilder {
484
0
        gids: &gids,
485
0
        max_glyph_id,
486
0
        replacement_data: &replacement_data,
487
0
        offset_array: &offset_array,
488
0
        new_data_len: total_data_size as usize,
489
0
        new_offsets_len: offsets_size,
490
0
    };
491
492
0
    let new_offsets = match new_offset_type {
493
0
        OffsetType::CffOne(_) => offset_array_builder.build::<CffOneInfo, u8>()?,
494
0
        OffsetType::CffTwo(_) => offset_array_builder.build::<CffTwoInfo, u16>()?,
495
0
        OffsetType::CffThree(_) => offset_array_builder.build::<CffThreeInfo, Uint24>()?,
496
0
        OffsetType::CffFour(_) => offset_array_builder.build::<CffFourInfo, u32>()?,
497
0
        OffsetType::ShortDivByTwo(_) => offset_array_builder.build::<ShortDivByTwoInfo, u16>()?,
498
0
        OffsetType::Long(_) => offset_array_builder.build::<LongInfo, u32>()?,
499
    };
500
501
    // Step 3: add new tables to the output builder
502
0
    offset_array.add_to_font(font_builder, new_offsets, new_offset_type)?;
503
504
0
    Ok(())
505
0
}
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<incremental_font_transfer::glyph_keyed::GlyfAndLoca>
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<incremental_font_transfer::glyph_keyed::CFFAndCharStrings>
Unexecuted instantiation: incremental_font_transfer::glyph_keyed::patch_offset_array::<read_fonts::tables::gvar::Gvar>
506
507
/// Classifies the different style of offsets that can be used in a data offset array.
508
#[derive(Clone, Copy, PartialEq, Eq)]
509
enum OffsetType {
510
    // CFF needs it's own offset types since CFF offsets have a 1 byte bias.
511
    CffOne(CffOneInfo),
512
    CffTwo(CffTwoInfo),
513
    CffThree(CffThreeInfo),
514
    CffFour(CffFourInfo),
515
516
    // For gvar, loca, glyf
517
    ShortDivByTwo(ShortDivByTwoInfo),
518
    Long(LongInfo),
519
}
520
521
trait OffsetTypeInfo {
522
    const WIDTH: usize;
523
    const DIVISOR: usize;
524
    const BIAS: u32;
525
526
0
    fn width(&self) -> usize {
527
0
        Self::WIDTH
528
0
    }
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffOneInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffFourInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffThreeInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::LongInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::width
529
530
0
    fn divisor(&self) -> usize {
531
0
        Self::DIVISOR
532
0
    }
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffOneInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffFourInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffThreeInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::LongInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::divisor
533
534
0
    fn bias(&self) -> u32 {
535
0
        Self::BIAS
536
0
    }
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffOneInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffFourInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::CffThreeInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::ShortDivByTwoInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
Unexecuted instantiation: <incremental_font_transfer::glyph_keyed::LongInfo as incremental_font_transfer::glyph_keyed::OffsetTypeInfo>::bias
537
}
538
539
#[derive(Clone, Copy, PartialEq, Eq, Default)]
540
struct CffOneInfo;
541
542
impl OffsetTypeInfo for CffOneInfo {
543
    const WIDTH: usize = 1;
544
    const DIVISOR: usize = 1;
545
    const BIAS: u32 = 1;
546
}
547
548
#[derive(Clone, Copy, PartialEq, Eq, Default)]
549
struct CffTwoInfo;
550
551
impl OffsetTypeInfo for CffTwoInfo {
552
    const WIDTH: usize = 2;
553
    const DIVISOR: usize = 1;
554
    const BIAS: u32 = 1;
555
}
556
557
#[derive(Clone, Copy, PartialEq, Eq, Default)]
558
struct CffThreeInfo;
559
560
impl OffsetTypeInfo for CffThreeInfo {
561
    const WIDTH: usize = 3;
562
    const DIVISOR: usize = 1;
563
    const BIAS: u32 = 1;
564
}
565
566
#[derive(Clone, Copy, PartialEq, Eq, Default)]
567
struct CffFourInfo;
568
569
impl OffsetTypeInfo for CffFourInfo {
570
    const WIDTH: usize = 4;
571
    const DIVISOR: usize = 1;
572
    const BIAS: u32 = 1;
573
}
574
575
#[derive(Clone, Copy, PartialEq, Eq, Default)]
576
struct ShortDivByTwoInfo;
577
impl OffsetTypeInfo for ShortDivByTwoInfo {
578
    const WIDTH: usize = 2;
579
    const DIVISOR: usize = 2;
580
    const BIAS: u32 = 0;
581
}
582
583
#[derive(Clone, Copy, PartialEq, Eq, Default)]
584
struct LongInfo;
585
impl OffsetTypeInfo for LongInfo {
586
    const WIDTH: usize = 4;
587
    const DIVISOR: usize = 1;
588
    const BIAS: u32 = 0;
589
}
590
591
impl OffsetType {
592
0
    fn max_representable_size(self) -> usize {
593
0
        match self {
594
0
            Self::ShortDivByTwo(_) => ((1 << 16) - 1) * 2,
595
0
            _ => ((1 << (self.offset_width() as u64 * 8)) - 1 - self.offset_bias() as u64) as usize,
596
        }
597
0
    }
598
599
0
    fn offset_width(&self) -> usize {
600
0
        match self {
601
0
            Self::CffOne(info) => info.width(),
602
0
            Self::CffTwo(info) => info.width(),
603
0
            Self::CffThree(info) => info.width(),
604
0
            Self::CffFour(info) => info.width(),
605
0
            Self::ShortDivByTwo(info) => info.width(),
606
0
            Self::Long(info) => info.width(),
607
        }
608
0
    }
609
610
0
    fn offset_divisor(&self) -> usize {
611
0
        match self {
612
0
            Self::CffOne(info) => info.divisor(),
613
0
            Self::CffTwo(info) => info.divisor(),
614
0
            Self::CffThree(info) => info.divisor(),
615
0
            Self::CffFour(info) => info.divisor(),
616
0
            Self::ShortDivByTwo(info) => info.divisor(),
617
0
            Self::Long(info) => info.divisor(),
618
        }
619
0
    }
620
621
0
    fn offset_bias(&self) -> u32 {
622
0
        match self {
623
0
            Self::CffOne(info) => info.bias(),
624
0
            Self::CffTwo(info) => info.bias(),
625
0
            Self::CffThree(info) => info.bias(),
626
0
            Self::CffFour(info) => info.bias(),
627
0
            Self::ShortDivByTwo(info) => info.bias(),
628
0
            Self::Long(info) => info.bias(),
629
        }
630
0
    }
631
}
632
633
struct GlyfAndLoca<'a> {
634
    loca: Loca<'a>,
635
    glyf: &'a [u8],
636
}
637
638
struct CFFAndCharStrings<'a> {
639
    cff_data: &'a [u8],
640
    charstrings: Index<'a>,
641
    charstrings_object_data: &'a [u8],
642
    charstrings_offset: usize,
643
    offset_type: OffsetType,
644
}
645
646
impl CFFAndCharStrings<'_> {
647
0
    fn from_cff_font<'a>(
648
0
        font: &FontRef<'a>,
649
0
        charstrings_offset: u32,
650
0
        max_glyph_id: GlyphId,
651
0
    ) -> Result<CFFAndCharStrings<'a>, ReadError> {
652
0
        let cff = font.cff()?;
653
0
        let charstrings_data =
654
0
            Self::charstrings_data(cff.offset_data(), charstrings_offset as usize)?;
655
0
        let charstrings = Index1::read(charstrings_data)?;
656
0
        let offset_type = Self::offset_type(charstrings.off_size())?;
657
658
0
        let offset_base = charstrings.data_byte_range().start;
659
0
        let charstrings_object_data = charstrings_data
660
0
            .split_off(offset_base)
661
0
            .ok_or(ReadError::OutOfBounds)?
662
0
            .as_bytes();
663
664
0
        Self::check_glyph_count(charstrings.count() as u32, max_glyph_id)?;
665
666
0
        Ok(CFFAndCharStrings {
667
0
            cff_data: cff.offset_data().as_bytes(),
668
0
            charstrings: Index::Format1(charstrings),
669
0
            charstrings_object_data,
670
0
            charstrings_offset: charstrings_offset as usize,
671
0
            offset_type,
672
0
        })
673
0
    }
674
675
0
    fn from_cff2_font<'a>(
676
0
        font: &FontRef<'a>,
677
0
        charstrings_offset: u32,
678
0
        max_glyph_id: GlyphId,
679
0
    ) -> Result<CFFAndCharStrings<'a>, ReadError> {
680
0
        let cff2 = font.cff2()?;
681
0
        let charstrings_data =
682
0
            Self::charstrings_data(cff2.offset_data(), charstrings_offset as usize)?;
683
0
        let charstrings = Index2::read(charstrings_data)?;
684
0
        let offset_type = Self::offset_type(charstrings.off_size())?;
685
686
0
        let offset_base = charstrings.data_byte_range().start;
687
0
        let charstrings_object_data = charstrings_data
688
0
            .split_off(offset_base)
689
0
            .ok_or(ReadError::OutOfBounds)?
690
0
            .as_bytes();
691
692
0
        Self::check_glyph_count(charstrings.count(), max_glyph_id)?;
693
694
0
        Ok(CFFAndCharStrings {
695
0
            cff_data: cff2.offset_data().as_bytes(),
696
0
            charstrings: Index::Format2(charstrings),
697
0
            charstrings_object_data,
698
0
            charstrings_offset: charstrings_offset as usize,
699
0
            offset_type,
700
0
        })
701
0
    }
702
703
0
    fn check_glyph_count(count: u32, max_glyph_id: GlyphId) -> Result<(), ReadError> {
704
0
        if count != max_glyph_id.to_u32() + 1 {
705
0
            return Err(ReadError::MalformedData(
706
0
                "CFF/CFF2 charstrings glyph count does not match maxp's.",
707
0
            ));
708
0
        }
709
0
        Ok(())
710
0
    }
711
712
0
    fn charstrings_data(
713
0
        table_data: FontData,
714
0
        charstrings_offset: usize,
715
0
    ) -> Result<FontData, ReadError> {
716
0
        table_data
717
0
            .split_off(charstrings_offset)
718
0
            .ok_or(ReadError::OutOfBounds)
719
0
    }
720
721
0
    fn offset_type(size: u8) -> Result<OffsetType, ReadError> {
722
0
        Ok(match size {
723
0
            1 => OffsetType::CffOne(Default::default()),
724
0
            2 => OffsetType::CffTwo(Default::default()),
725
0
            3 => OffsetType::CffThree(Default::default()),
726
0
            4 => OffsetType::CffFour(Default::default()),
727
            _ => {
728
0
                return Err(ReadError::MalformedData(
729
0
                    "Invalid charstrings offset size (is not 1, 2, 3, or 4).",
730
0
                ))
731
            }
732
        })
733
0
    }
734
}
735
736
/// Abstraction of a table which has blocks of data located by an array of ascending offsets (eg. glyf + loca)
737
trait GlyphDataOffsetArray {
738
    fn offset_type(&self) -> OffsetType;
739
740
    /// Returns which offset types this array could be changed into.
741
    ///
742
    /// If no changes are possible will only include offset_type().
743
    /// Types are listed in ascending order of size.
744
    fn available_offset_types(&self) -> impl Iterator<Item = OffsetType>;
745
746
    /// Returns the offset associated with a specific gid.
747
    ///
748
    /// This is the offset at which data for that glyph starts.
749
    fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError>;
750
751
    /// Checks that all offsets are in ascending order.
752
    fn all_offsets_are_ascending(&self) -> bool;
753
754
    fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError>;
755
756
    fn add_to_font(
757
        &self,
758
        font_builder: &mut FontBuilder,
759
        offsets: OffsetArrayAndData,
760
        offset_type: OffsetType,
761
    ) -> Result<(), PatchingError>;
762
}
763
764
impl GlyphDataOffsetArray for GlyfAndLoca<'_> {
765
0
    fn offset_type(&self) -> OffsetType {
766
0
        match self.loca {
767
0
            Loca::Short(_) => OffsetType::ShortDivByTwo(Default::default()),
768
0
            Loca::Long(_) => OffsetType::Long(Default::default()),
769
        }
770
0
    }
771
772
0
    fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
773
0
        std::iter::once(self.offset_type())
774
0
    }
775
776
0
    fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
777
0
        self.loca
778
            // Note: get_raw(...) applies the * 2 for short loca when needed.
779
0
            .get_raw(gid.to_u32() as usize)
780
0
            .ok_or(PatchingError::InvalidPatch("Start loca entry is missing."))
781
0
    }
782
783
0
    fn all_offsets_are_ascending(&self) -> bool {
784
0
        self.loca.all_offsets_are_ascending()
785
0
    }
786
787
0
    fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
788
0
        self.glyf
789
0
            .get(range)
790
0
            .ok_or(PatchingError::from(ReadError::OutOfBounds))
791
0
    }
792
793
0
    fn add_to_font(
794
0
        &self,
795
0
        font_builder: &mut FontBuilder,
796
0
        offsets: OffsetArrayAndData,
797
0
        new_offset_type: OffsetType,
798
0
    ) -> Result<(), PatchingError> {
799
0
        if new_offset_type != self.offset_type() {
800
            // glyf/loca does not support changing offset types.
801
0
            return Err(PatchingError::SerializationError(
802
0
                SerializeErrorFlags::SERIALIZE_ERROR_OFFSET_OVERFLOW,
803
0
            ));
804
0
        }
805
806
0
        font_builder.add_raw(Glyf::TAG, offsets.data);
807
0
        font_builder.add_raw(Loca::TAG, offsets.offset_array);
808
0
        Ok(())
809
0
    }
810
}
811
812
impl GlyphDataOffsetArray for Gvar<'_> {
813
0
    fn offset_type(&self) -> OffsetType {
814
0
        if self.flags().contains(GvarFlags::LONG_OFFSETS) {
815
0
            OffsetType::Long(Default::default())
816
        } else {
817
0
            OffsetType::ShortDivByTwo(Default::default())
818
        }
819
0
    }
820
821
0
    fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
822
0
        [
823
0
            OffsetType::ShortDivByTwo(ShortDivByTwoInfo),
824
0
            OffsetType::Long(LongInfo),
825
0
        ]
826
0
        .iter()
827
0
        .copied()
828
0
    }
829
830
0
    fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
831
0
        Ok(self
832
0
            .glyph_variation_data_offsets()
833
0
            .get(gid.to_u32() as usize)
834
0
            .map_err(PatchingError::FontParsingFailed)?
835
0
            .get())
836
0
    }
837
838
0
    fn all_offsets_are_ascending(&self) -> bool {
839
0
        let mut prev: Option<u32> = None;
840
0
        for v in self.glyph_variation_data_offsets().iter() {
841
0
            let Ok(v) = v else {
842
0
                return false;
843
            };
844
0
            let v = v.get();
845
0
            if let Some(prev_value) = prev {
846
0
                if prev_value > v {
847
0
                    return false;
848
0
                }
849
0
            }
850
0
            prev = Some(v);
851
        }
852
0
        true
853
0
    }
854
855
0
    fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
856
0
        self.glyph_variation_data_for_range(range)
857
0
            .map_err(PatchingError::FontParsingFailed)
858
0
            .map(|fd| fd.as_bytes())
859
0
    }
860
861
0
    fn add_to_font(
862
0
        &self,
863
0
        font_builder: &mut FontBuilder,
864
0
        offsets: OffsetArrayAndData,
865
0
        new_offset_type: OffsetType,
866
0
    ) -> Result<(), PatchingError> {
867
        const GVAR_FLAGS_OFFSET: usize = 15;
868
869
        // typical gvar layout (see: https://learn.microsoft.com/en-us/typography/opentype/spec/gvar):
870
        //   Part 1 - Header
871
        //   Part 2 - glyphVariationDataOffsets[glyphCount + 1]
872
        //   Part 3 - Shared Tuples
873
        //   Part 4 - Array of per glyph variation data
874
        //
875
        // When constructing a new gvar from the newly synthesized data we'll be replacing
876
        // Part 2 and 4 with 'offsets' and 'data' respectively. In order to be consistent with the open type
877
        // spec which prescribes the above ordering we always output the parts in the spec ordering
878
        // regardless of how they were ordered in the original table. Additionally this will correctly resolve cases
879
        // where the original table had overlapping shared tuple and glyph variation data.
880
        // However, as a result we may need to change offsets in part 1 if ordering gets modified. The skera serializer
881
        // is used to recalculate offsets as needed.
882
0
        let orig_bytes = self.as_bytes();
883
0
        let orig_size = orig_bytes.len();
884
885
0
        let original_offsets_range = self.glyph_variation_data_offsets_byte_range();
886
887
0
        if new_offset_type == self.offset_type()
888
0
            && offsets.offset_array.len()
889
0
                != original_offsets_range.end - original_offsets_range.start
890
        {
891
            // computed offsets array length should not have changed from the original offsets array length
892
            // if offset type is not changing.
893
0
            return Err(PatchingError::InternalError);
894
0
        }
895
896
0
        let part1_header_pre_flag = orig_bytes
897
0
            .get(0..GVAR_FLAGS_OFFSET)
898
0
            .ok_or(PatchingError::InternalError)?;
899
900
0
        let part1_header_post_flag = orig_bytes
901
0
            .get(GVAR_FLAGS_OFFSET + 1..original_offsets_range.start)
902
0
            .ok_or(PatchingError::InternalError)?;
903
904
0
        let mut flags: u8 = orig_bytes
905
0
            .get(GVAR_FLAGS_OFFSET)
906
0
            .copied()
907
0
            .ok_or(PatchingError::InternalError)?;
908
0
        if new_offset_type.offset_width() == 4 {
909
0
            flags |= 0b00000001;
910
0
        } else {
911
0
            flags &= 0b11111110;
912
0
        }
913
914
0
        let max_new_size = orig_size + offsets.data.len();
915
916
        // part 1 and 2 - write gvar header and offsets
917
0
        let mut serializer = Serializer::new(max_new_size);
918
0
        serializer
919
0
            .start_serialize()
920
0
            .and(serializer.embed_bytes(part1_header_pre_flag))
921
0
            .and(serializer.embed(flags))
922
0
            .and(serializer.embed_bytes(part1_header_post_flag))
923
0
            .and(serializer.embed_bytes(&offsets.offset_array))
924
0
            .map_err(PatchingError::from)?;
925
926
        // part 4 - write new glyph variation data
927
0
        serializer
928
0
            .push()
929
0
            .and(serializer.embed_bytes(&offsets.data))
930
0
            .map_err(PatchingError::from)?;
931
932
0
        let glyph_data_obj = serializer
933
0
            .pop_pack(false)
934
0
            .ok_or_else(|| PatchingError::SerializationError(serializer.error()))?;
935
936
        // part 3 - write shared tuples.
937
0
        let shared_tuples = self.shared_tuples().map_err(PatchingError::from)?;
938
939
0
        let shared_tuples_bytes = shared_tuples
940
0
            .offset_data()
941
0
            .as_bytes()
942
0
            .get(shared_tuples.tuples_byte_range())
943
0
            .ok_or_else(|| PatchingError::SerializationError(serializer.error()))?;
944
945
0
        let shared_tuples_obj = if !shared_tuples_bytes.is_empty() {
946
0
            serializer
947
0
                .push()
948
0
                .and(serializer.embed_bytes(shared_tuples_bytes))
949
0
                .map_err(PatchingError::from)?;
950
951
            // The spec says that shared tuple data should come before glyph variation data so use a virtual link to
952
            // ensure the correct ordering.
953
0
            serializer.add_virtual_link(glyph_data_obj)?;
954
955
0
            serializer
956
0
                .pop_pack(false)
957
0
                .ok_or_else(|| PatchingError::SerializationError(serializer.error()))?
958
        } else {
959
            // If there's no shared tuples just point the shared tuples offset to the start of glyph_data_obj
960
            // (since it can't be null).
961
0
            glyph_data_obj
962
        };
963
964
        // Set up offsets to shared tuples and glyph data.
965
0
        serializer
966
0
            .add_link(
967
0
                self.shared_tuples_offset_byte_range(),
968
0
                shared_tuples_obj,
969
0
                OffsetWhence::Head,
970
                0,
971
                false,
972
            )
973
0
            .and(serializer.add_link(
974
0
                self.glyph_variation_data_array_offset_byte_range(),
975
0
                glyph_data_obj,
976
0
                OffsetWhence::Head,
977
                0,
978
                false,
979
            ))
980
0
            .map_err(PatchingError::from)?;
981
982
        // Generate the final output
983
0
        serializer.end_serialize();
984
0
        let new_gvar = serializer.copy_bytes();
985
0
        font_builder.add_raw(Gvar::TAG, new_gvar);
986
0
        Ok(())
987
0
    }
988
}
989
990
impl GlyphDataOffsetArray for CFFAndCharStrings<'_> {
991
0
    fn offset_type(&self) -> OffsetType {
992
0
        self.offset_type
993
0
    }
994
995
0
    fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
996
0
        [
997
0
            OffsetType::CffOne(CffOneInfo),
998
0
            OffsetType::CffTwo(CffTwoInfo),
999
0
            OffsetType::CffThree(CffThreeInfo),
1000
0
            OffsetType::CffFour(CffFourInfo),
1001
0
        ]
1002
0
        .iter()
1003
0
        .copied()
1004
0
    }
1005
1006
0
    fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
1007
0
        self.charstrings
1008
0
            .get_offset(gid.to_u32() as usize)
1009
0
            .map_err(|_| PatchingError::FontParsingFailed(ReadError::OutOfBounds))
1010
0
            .map(|offset| offset as u32)
1011
0
    }
1012
1013
0
    fn all_offsets_are_ascending(&self) -> bool {
1014
0
        let it1 = (0..self.charstrings.count()).map(|index| self.offset_for(GlyphId::new(index)));
1015
0
        let it2 = it1.clone().skip(1);
1016
1017
0
        !it1.zip(it2).any(|(start, end)| {
1018
0
            let (Ok(start), Ok(end)) = (start, end) else {
1019
0
                return true;
1020
            };
1021
0
            start > end
1022
0
        })
1023
0
    }
1024
1025
0
    fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
1026
0
        self.charstrings_object_data
1027
0
            .get(range)
1028
0
            .ok_or(PatchingError::FontParsingFailed(ReadError::OutOfBounds))
1029
0
    }
1030
1031
0
    fn add_to_font(
1032
0
        &self,
1033
0
        font_builder: &mut FontBuilder,
1034
0
        offsets: OffsetArrayAndData,
1035
0
        offset_type: OffsetType,
1036
0
    ) -> Result<(), PatchingError> {
1037
        // The IFT specification requires that for IFT fonts CFF tables must have the charstrings data
1038
        // at the end and not overlapping anything (see: https://w3c.github.io/IFT/Overview.html#cff).
1039
        //
1040
        // This allows us to significantly simplify the CFF table reconstruction in this method:
1041
        // 1. Copy everything preceding charstrings unmodified into the new table.
1042
        // 2. Synthesize a new charstrings to the requested offset size.
1043
0
        let (count_width, table_tag) = match &self.charstrings {
1044
0
            Index::Format1(_) => (2, Cff::TAG),
1045
0
            Index::Format2(_) => (4, Cff2::TAG),
1046
0
            Index::Empty => return Err(PatchingError::InternalError),
1047
        };
1048
1049
0
        let offset_data: &[u8] = &offsets.offset_array;
1050
0
        let outline_data: &[u8] = &offsets.data;
1051
0
        let max_new_size = self.charstrings_offset + // this is the size of everything other than charstrings
1052
0
            outline_data.len() +
1053
0
            offset_data.len() +
1054
0
            1 + count_width; // header size for INDEX
1055
1056
0
        let mut serializer = Serializer::new(max_new_size);
1057
1058
        // Part 1 - Non charstrings data.
1059
0
        let r = serializer.start_serialize().and(
1060
0
            serializer.embed_bytes(
1061
0
                self.cff_data
1062
0
                    .get(0..self.charstrings_offset)
1063
0
                    .ok_or(PatchingError::FontParsingFailed(ReadError::OutOfBounds))?,
1064
            ),
1065
        );
1066
1067
        // Part 2 - Charstrings data.
1068
0
        let r = match &self.charstrings {
1069
            // Count size differs between format 1 and 2 so pull out the inner type in
1070
            // order to embed the count with the correct width.
1071
0
            Index::Format1(charstrings) => r.and(serializer.embed(charstrings.count())),
1072
0
            Index::Format2(charstrings) => r.and(serializer.embed(charstrings.count())),
1073
0
            Index::Empty => return Err(PatchingError::InternalError),
1074
        };
1075
1076
0
        r.and(serializer.embed(offset_type.offset_width() as u8))
1077
0
            .and(serializer.embed_bytes(offset_data))
1078
0
            .and(serializer.embed_bytes(outline_data))
1079
0
            .map_err(PatchingError::SerializationError)?;
1080
1081
        // Generate the final output
1082
0
        serializer.end_serialize();
1083
0
        let new_cff = serializer.copy_bytes();
1084
1085
0
        font_builder.add_raw(table_tag, new_cff);
1086
1087
0
        Ok(())
1088
0
    }
1089
}
1090
1091
#[cfg(test)]
1092
pub(crate) mod tests {
1093
    use std::{
1094
        collections::{BTreeSet, HashMap},
1095
        io::Write,
1096
        iter,
1097
    };
1098
1099
    use brotlic::CompressorWriter;
1100
    use read_fonts::{
1101
        collections::IntSet,
1102
        tables::{
1103
            cff2::Cff2,
1104
            glyf::Glyf,
1105
            gvar::Gvar,
1106
            ift::{CompatibilityId, GlyphKeyedPatch, IFTX_TAG, IFT_TAG},
1107
            loca::Loca,
1108
        },
1109
        FontData, FontRead, ReadError, TableProvider, TopLevelTable,
1110
    };
1111
    use shared_brotli_patch_decoder::BuiltInBrotliDecoder;
1112
1113
    use font_test_data::{
1114
        bebuffer::BeBuffer,
1115
        ift::{
1116
            cff_u16_glyph_patches, format2_with_one_charstrings_offset,
1117
            glyf_and_gvar_u16_glyph_patches, glyf_u16_glyph_patches, glyf_u16_glyph_patches_2,
1118
            glyph_keyed_patch_header, long_gvar_with_shared_tuples, noop_glyf_glyph_patches,
1119
            out_of_order_gvar_with_shared_tuples, short_gvar_near_maximum_offset_size,
1120
            short_gvar_with_no_shared_tuples, short_gvar_with_shared_tuples, CFF2_FONT,
1121
            CFF2_FONT_CHARSTRINGS_OFFSET, CFF_FONT, CFF_FONT_CHARSTRINGS_OFFSET,
1122
        },
1123
    };
1124
    use skrifa::{FontRef, GlyphId, Tag};
1125
    use write_fonts::FontBuilder;
1126
1127
    use crate::{
1128
        font_patch::PatchingError,
1129
        glyph_keyed::{apply_glyph_keyed_patches, CffFourInfo, ShortDivByTwoInfo},
1130
        patchmap::{PatchId, PatchUrl},
1131
        testdata::{test_font_for_patching, test_font_for_patching_with_loca_mod},
1132
    };
1133
1134
    use super::{CFFAndCharStrings, IftTableTag, LongInfo, OffsetType, PatchInfo};
1135
1136
    pub(crate) fn assemble_glyph_keyed_patch(mut header: BeBuffer, payload: BeBuffer) -> BeBuffer {
1137
        let payload_data: &[u8] = &payload;
1138
        let mut compressor = CompressorWriter::new(Vec::new());
1139
        compressor.write_all(payload_data).unwrap();
1140
        let compressed = compressor.into_inner().unwrap();
1141
1142
        header.write_at("max_uncompressed_length", payload_data.len() as u32);
1143
        header.extend(compressed)
1144
    }
1145
1146
    fn check_tables_equal(a: &FontRef, b: &FontRef, excluding: BTreeSet<Tag>) {
1147
        let it_a = a
1148
            .table_directory()
1149
            .table_records()
1150
            .iter()
1151
            .map(|r| r.tag())
1152
            .filter(|tag| !excluding.contains(tag));
1153
        let it_b = b
1154
            .table_directory()
1155
            .table_records()
1156
            .iter()
1157
            .map(|r| r.tag())
1158
            .filter(|tag| !excluding.contains(tag));
1159
1160
        for (tag_a, tag_b) in it_a.zip(it_b) {
1161
            assert_eq!(tag_a, tag_b);
1162
            let data_a = a.table_data(tag_a).unwrap();
1163
            let data_b = b.table_data(tag_b).unwrap();
1164
            if tag_a == Tag::new(b"head") {
1165
                // ignore the head.checksum_adjustment, which will necessarily differ
1166
                assert_eq!(data_a.as_bytes()[..8], data_b.as_bytes()[..8]);
1167
                assert_eq!(data_a.as_bytes()[12..], data_b.as_bytes()[12..]);
1168
            } else {
1169
                assert_eq!(data_a.as_bytes(), data_b.as_bytes(), "{}", tag_a);
1170
            }
1171
        }
1172
    }
1173
1174
    fn patch_info(tag: Tag, bit_index: usize) -> PatchInfo {
1175
        let source = match &tag.to_be_bytes() {
1176
            b"IFT " => IftTableTag::Ift(CompatibilityId::from_u32s([0, 0, 0, 0])),
1177
            b"IFTX" => IftTableTag::Iftx(CompatibilityId::from_u32s([0, 0, 0, 0])),
1178
            _ => panic!("Unexpected tag value."),
1179
        };
1180
1181
        let mut info = PatchInfo {
1182
            url: PatchUrl::expand_template(&[], &PatchId::Numeric(0)).unwrap(),
1183
            source_table: source,
1184
            application_flag_bit_indices: IntSet::<u32>::empty(),
1185
        };
1186
        info.application_flag_bit_indices.insert(bit_index as u32);
1187
        info
1188
    }
1189
1190
    #[test]
1191
    fn noop_glyph_keyed() {
1192
        let patch =
1193
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), noop_glyf_glyph_patches());
1194
        let patch: &[u8] = &patch;
1195
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1196
1197
        let font = test_font_for_patching();
1198
        let font = FontRef::new(&font).unwrap();
1199
1200
        let patch_info = patch_info(IFT_TAG, 4);
1201
1202
        let patched =
1203
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1204
                .unwrap();
1205
        let patched = FontRef::new(&patched).unwrap();
1206
1207
        // Application bit will be set in the patched font.
1208
        let expected_font = test_font_for_patching_with_loca_mod(
1209
            true,
1210
            |_| {},
1211
            HashMap::from([(IFT_TAG, vec![0b0001_0000, 0, 0, 0].as_slice())]),
1212
        );
1213
        let expected_font = FontRef::new(&expected_font).unwrap();
1214
        check_tables_equal(&expected_font, &patched, BTreeSet::default());
1215
    }
1216
1217
    #[test]
1218
    fn basic_glyph_keyed() {
1219
        let patch =
1220
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
1221
        let patch: &[u8] = &patch;
1222
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1223
1224
        let font = test_font_for_patching();
1225
        let font = FontRef::new(&font).unwrap();
1226
1227
        let patch_info = patch_info(IFT_TAG, 28);
1228
        let patched =
1229
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1230
                .unwrap();
1231
        let patched = FontRef::new(&patched).unwrap();
1232
1233
        let new_ift: &[u8] = patched.table_data(IFT_TAG).unwrap().as_bytes();
1234
        assert_eq!(&[0, 0, 0, 0b0001_0000], new_ift);
1235
1236
        let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
1237
        assert_eq!(
1238
            &[
1239
                1, 2, 3, 4, 5, 0, // gid 0
1240
                6, 7, 8, 0, // gid 1
1241
                b'a', b'b', b'c', 0, // gid2
1242
                b'd', b'e', b'f', b'g', // gid 7
1243
                b'h', b'i', b'j', b'k', b'l', 0, // gid 8 + 9
1244
                b'm', b'n', // gid 13
1245
            ],
1246
            new_glyf
1247
        );
1248
1249
        let new_loca = patched.loca(None).unwrap();
1250
        let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
1251
1252
        assert_eq!(
1253
            vec![
1254
                0,  // gid 0
1255
                6,  // gid 1
1256
                10, // gid 2
1257
                14, // gid 3
1258
                14, // gid 4
1259
                14, // gid 5
1260
                14, // gid 6
1261
                14, // gid 7
1262
                18, // gid 8
1263
                18, // gid 9
1264
                24, // gid 10
1265
                24, // gid 11
1266
                24, // gid 12
1267
                24, // gid 13
1268
                26, // gid 14
1269
                26, // end
1270
            ],
1271
            indices
1272
        );
1273
1274
        check_tables_equal(&font, &patched, [IFT_TAG, Glyf::TAG, Loca::TAG].into());
1275
    }
1276
1277
    #[test]
1278
    fn basic_glyph_keyed_with_long_loca() {
1279
        let patch =
1280
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
1281
        let patch: &[u8] = &patch;
1282
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1283
1284
        let font = test_font_for_patching_with_loca_mod(
1285
            false, // force long loca
1286
            |_| {},
1287
            HashMap::from([(IFT_TAG, vec![0, 0, 0, 0].as_slice())]),
1288
        );
1289
        let font = FontRef::new(&font).unwrap();
1290
1291
        let patch_info = patch_info(IFT_TAG, 28);
1292
        let patched =
1293
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1294
                .unwrap();
1295
        let patched = FontRef::new(&patched).unwrap();
1296
1297
        let new_ift: &[u8] = patched.table_data(IFT_TAG).unwrap().as_bytes();
1298
        assert_eq!(&[0, 0, 0, 0b0001_0000], new_ift);
1299
1300
        let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
1301
        assert_eq!(
1302
            &[
1303
                1, 2, 3, 4, 5, 0, // gid 0
1304
                6, 7, 8, 0, // gid 1
1305
                b'a', b'b', b'c', // gid2
1306
                b'd', b'e', b'f', b'g', // gid 7
1307
                b'h', b'i', b'j', b'k', b'l', // gid 8 + 9
1308
                b'm', b'n', // gid 13
1309
            ],
1310
            new_glyf
1311
        );
1312
1313
        let new_loca = patched.loca(None).unwrap();
1314
        let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
1315
1316
        assert_eq!(
1317
            vec![
1318
                0,  // gid 0
1319
                6,  // gid 1
1320
                10, // gid 2
1321
                13, // gid 3
1322
                13, // gid 4
1323
                13, // gid 5
1324
                13, // gid 6
1325
                13, // gid 7
1326
                17, // gid 8
1327
                17, // gid 9
1328
                22, // gid 10
1329
                22, // gid 11
1330
                22, // gid 12
1331
                22, // gid 13
1332
                24, // gid 14
1333
                24, // end
1334
            ],
1335
            indices
1336
        );
1337
1338
        check_tables_equal(&font, &patched, [IFT_TAG, Glyf::TAG, Loca::TAG].into());
1339
    }
1340
1341
    #[test]
1342
    fn multiple_glyph_keyed() {
1343
        let patch =
1344
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
1345
        let patch: &[u8] = &patch;
1346
        let patch1 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1347
        let patch_info_1 = patch_info(IFTX_TAG, 13);
1348
1349
        let patch =
1350
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches_2());
1351
        let patch: &[u8] = &patch;
1352
        let patch2 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1353
        let patch_info_2 = patch_info(IFTX_TAG, 28);
1354
1355
        let font = test_font_for_patching_with_loca_mod(
1356
            true,
1357
            |_| {},
1358
            HashMap::from([(IFTX_TAG, vec![0, 0, 0, 0].as_slice())]),
1359
        );
1360
        let font = FontRef::new(&font).unwrap();
1361
1362
        let patched = apply_glyph_keyed_patches(
1363
            &[(&patch_info_2, patch2), (&patch_info_1, patch1)],
1364
            &font,
1365
            &BuiltInBrotliDecoder,
1366
        )
1367
        .unwrap();
1368
        let patched = FontRef::new(&patched).unwrap();
1369
1370
        let new_ift: &[u8] = patched.table_data(IFTX_TAG).unwrap().as_bytes();
1371
        assert_eq!(&[0, 0b0010_0000, 0, 0b0001_0000], new_ift);
1372
1373
        let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
1374
        assert_eq!(
1375
            &[
1376
                1, 2, 3, 4, 5, 0, // gid 0
1377
                6, 7, 8, 0, // gid 1
1378
                b'a', b'b', b'c', 0, // gid2
1379
                b'q', b'r', // gid 7
1380
                b'h', b'i', b'j', b'k', b'l', 0, // gid 8 + 9
1381
                b's', b't', b'u', 0, // gid 12
1382
                b'm', b'n', // gid 13
1383
                b'v', 0, // gid 14
1384
            ],
1385
            new_glyf
1386
        );
1387
1388
        let new_loca = patched.loca(None).unwrap();
1389
        let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
1390
1391
        assert_eq!(
1392
            vec![
1393
                0,  // gid 0
1394
                6,  // gid 1
1395
                10, // gid 2
1396
                14, // gid 3
1397
                14, // gid 4
1398
                14, // gid 5
1399
                14, // gid 6
1400
                14, // gid 7
1401
                16, // gid 8
1402
                16, // gid 9
1403
                22, // gid 10
1404
                22, // gid 11
1405
                22, // gid 12
1406
                26, // gid 13
1407
                28, // gid 14
1408
                30, // end
1409
            ],
1410
            indices
1411
        );
1412
1413
        check_tables_equal(&font, &patched, [Glyf::TAG, Loca::TAG, IFTX_TAG].into());
1414
    }
1415
1416
    #[test]
1417
    fn glyph_keyed_glyf_and_gvar() {
1418
        let patch = assemble_glyph_keyed_patch(
1419
            glyph_keyed_patch_header(),
1420
            glyf_and_gvar_u16_glyph_patches(),
1421
        );
1422
        let patch: &[u8] = &patch;
1423
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1424
        let patch_info = patch_info(IFT_TAG, 0);
1425
1426
        let gvar = short_gvar_with_shared_tuples();
1427
1428
        let font = test_font_for_patching_with_loca_mod(
1429
            true,
1430
            |_| {},
1431
            HashMap::from([
1432
                (Gvar::TAG, gvar.as_slice()),
1433
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1434
            ]),
1435
        );
1436
        let font = FontRef::new(&font).unwrap();
1437
1438
        let patched =
1439
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1440
                .unwrap();
1441
        let patched = FontRef::new(&patched).unwrap();
1442
1443
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1444
1445
        let mut expected_gvar: Vec<u8> = vec![];
1446
1447
        let change_start = gvar.offset_for("glyph_offset[3]");
1448
1449
        expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
1450
        // Offsets
1451
        expected_gvar.extend_from_slice(&[
1452
            0x00, 0x03, // gid 3
1453
            0x00, 0x03, // gid 4
1454
            0x00, 0x03, // gid 5
1455
            0x00, 0x03, // gid 6
1456
            0x00, 0x03, // gid 7
1457
            0x00, 0x05, // gid 8
1458
            0x00, 0x06, // gid 9
1459
            0x00, 0x06, // gid 10
1460
            0x00, 0x06, // gid 11
1461
            0x00, 0x06, // gid 12
1462
            0x00, 0x06, // gid 13
1463
            0x00, 0x06, // gid 14
1464
            0x00, 0x06u8, // trailing
1465
        ]);
1466
        // Shared tuples
1467
        expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
1468
        // Data
1469
        expected_gvar.extend_from_slice(&[
1470
            1, 2, 3, 4, // gid 0
1471
            b'm', b'n', // gid 2
1472
            b'o', b'p', b'q', 0, // gid 7
1473
            b'r', 0u8, // gid 8
1474
        ]);
1475
        assert_eq!(&expected_gvar, new_gvar);
1476
    }
1477
1478
    #[test]
1479
    fn glyph_keyed_glyf_and_long_gvar() {
1480
        let patch = assemble_glyph_keyed_patch(
1481
            glyph_keyed_patch_header(),
1482
            glyf_and_gvar_u16_glyph_patches(),
1483
        );
1484
        let patch: &[u8] = &patch;
1485
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1486
        let patch_info = patch_info(IFT_TAG, 0);
1487
1488
        let gvar = long_gvar_with_shared_tuples();
1489
1490
        let font = test_font_for_patching_with_loca_mod(
1491
            true,
1492
            |_| {},
1493
            HashMap::from([
1494
                (Gvar::TAG, gvar.as_slice()),
1495
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1496
            ]),
1497
        );
1498
        let font = FontRef::new(&font).unwrap();
1499
1500
        let patched =
1501
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1502
                .unwrap();
1503
        let patched = FontRef::new(&patched).unwrap();
1504
1505
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1506
1507
        let mut expected_gvar: Vec<u8> = vec![];
1508
1509
        let change_start = gvar.offset_for("glyph_offset[3]");
1510
1511
        expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
1512
        // Offsets
1513
        expected_gvar.extend_from_slice(&[
1514
            0x00, 0x00, 0x00, 0x06, // gid 3
1515
            0x00, 0x00, 0x00, 0x06, // gid 4
1516
            0x00, 0x00, 0x00, 0x06, // gid 5
1517
            0x00, 0x00, 0x00, 0x06, // gid 6
1518
            0x00, 0x00, 0x00, 0x06, // gid 7
1519
            0x00, 0x00, 0x00, 0x09, // gid 8
1520
            0x00, 0x00, 0x00, 0x0A, // gid 9
1521
            0x00, 0x00, 0x00, 0x0A, // gid 10
1522
            0x00, 0x00, 0x00, 0x0A, // gid 11
1523
            0x00, 0x00, 0x00, 0x0A, // gid 12
1524
            0x00, 0x00, 0x00, 0x0A, // gid 13
1525
            0x00, 0x00, 0x00, 0x0A, // gid 14
1526
            0x00, 0x00, 0x00, 0x0Au8, // trailing
1527
        ]);
1528
        // Shared tuples
1529
        expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
1530
        // Data
1531
        expected_gvar.extend_from_slice(&[
1532
            1, 2, 3, 4, // gid 0
1533
            b'm', b'n', // gid 2
1534
            b'o', b'p', b'q', // gid 7
1535
            b'r', // gid 8
1536
        ]);
1537
        assert_eq!(&expected_gvar, new_gvar);
1538
    }
1539
1540
    #[test]
1541
    fn glyph_keyed_glyf_and_gvar_no_shared_tuples() {
1542
        let patch = assemble_glyph_keyed_patch(
1543
            glyph_keyed_patch_header(),
1544
            glyf_and_gvar_u16_glyph_patches(),
1545
        );
1546
        let patch: &[u8] = &patch;
1547
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1548
        let patch_info = patch_info(IFT_TAG, 0);
1549
1550
        let gvar = short_gvar_with_no_shared_tuples();
1551
1552
        let font = test_font_for_patching_with_loca_mod(
1553
            true,
1554
            |_| {},
1555
            HashMap::from([
1556
                (Gvar::TAG, gvar.as_slice()),
1557
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1558
            ]),
1559
        );
1560
        let font = FontRef::new(&font).unwrap();
1561
1562
        let patched =
1563
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1564
                .unwrap();
1565
        let patched = FontRef::new(&patched).unwrap();
1566
1567
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1568
1569
        let mut expected_gvar: Vec<u8> = vec![];
1570
1571
        let change_start = gvar.offset_for("glyph_offset[3]");
1572
1573
        expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
1574
        // Offsets
1575
        expected_gvar.extend_from_slice(&[
1576
            0x00, 0x03, // gid 3
1577
            0x00, 0x03, // gid 4
1578
            0x00, 0x03, // gid 5
1579
            0x00, 0x03, // gid 6
1580
            0x00, 0x03, // gid 7
1581
            0x00, 0x05, // gid 8
1582
            0x00, 0x06, // gid 9
1583
            0x00, 0x06, // gid 10
1584
            0x00, 0x06, // gid 11
1585
            0x00, 0x06, // gid 12
1586
            0x00, 0x06, // gid 13
1587
            0x00, 0x06, // gid 14
1588
            0x00, 0x06u8, // trailing
1589
        ]);
1590
        // Data
1591
        expected_gvar.extend_from_slice(&[
1592
            1, 2, 3, 4, // gid 0
1593
            b'm', b'n', // gid 2
1594
            b'o', b'p', b'q', 0, // gid 7
1595
            b'r', 0u8, // gid 8
1596
        ]);
1597
        assert_eq!(&expected_gvar, new_gvar);
1598
    }
1599
1600
    #[test]
1601
    fn glyph_keyed_glyf_and_gvar_overlapping_shared_tuples() {
1602
        let patch = assemble_glyph_keyed_patch(
1603
            glyph_keyed_patch_header(),
1604
            glyf_and_gvar_u16_glyph_patches(),
1605
        );
1606
        let patch: &[u8] = &patch;
1607
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1608
        let patch_info = patch_info(IFT_TAG, 0);
1609
1610
        let mut gvar = short_gvar_with_no_shared_tuples();
1611
        gvar.write_at("shared_tuple_count", 2u16);
1612
1613
        let font = test_font_for_patching_with_loca_mod(
1614
            true,
1615
            |_| {},
1616
            HashMap::from([
1617
                (Gvar::TAG, gvar.as_slice()),
1618
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1619
            ]),
1620
        );
1621
        let font = FontRef::new(&font).unwrap();
1622
1623
        let patched =
1624
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1625
                .unwrap();
1626
        let patched = FontRef::new(&patched).unwrap();
1627
1628
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1629
1630
        let mut expected_gvar: Vec<u8> = vec![];
1631
1632
        let change_start = gvar.offset_for("glyph_offset[3]");
1633
1634
        gvar.write_at(
1635
            "glyph_variation_data_offset",
1636
            (gvar.offset_for("glyph_0") + 4) as u32,
1637
        ); // glyph variation data gets shifted by 4 bytes due to duplication of 4 bytes of shared tuple data.
1638
        expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
1639
        // Offsets
1640
        expected_gvar.extend_from_slice(&[
1641
            0x00, 0x03, // gid 3
1642
            0x00, 0x03, // gid 4
1643
            0x00, 0x03, // gid 5
1644
            0x00, 0x03, // gid 6
1645
            0x00, 0x03, // gid 7
1646
            0x00, 0x05, // gid 8
1647
            0x00, 0x06, // gid 9
1648
            0x00, 0x06, // gid 10
1649
            0x00, 0x06, // gid 11
1650
            0x00, 0x06, // gid 12
1651
            0x00, 0x06, // gid 13
1652
            0x00, 0x06, // gid 14
1653
            0x00, 0x06u8, // trailing
1654
        ]);
1655
        // Shared tuples
1656
        expected_gvar.extend_from_slice(&[1, 2, 3, 4u8]); // overlapping portion is duplicated into its own region.
1657
                                                          // Data
1658
        expected_gvar.extend_from_slice(&[
1659
            1, 2, 3, 4, // gid 0
1660
            b'm', b'n', // gid 2
1661
            b'o', b'p', b'q', 0, // gid 7
1662
            b'r', 0u8, // gid 8
1663
        ]);
1664
        assert_eq!(&expected_gvar, new_gvar);
1665
    }
1666
1667
    #[test]
1668
    fn glyph_keyed_out_of_order_gvar() {
1669
        let patch = assemble_glyph_keyed_patch(
1670
            glyph_keyed_patch_header(),
1671
            glyf_and_gvar_u16_glyph_patches(),
1672
        );
1673
        let patch: &[u8] = &patch;
1674
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1675
        let patch_info = patch_info(IFT_TAG, 0);
1676
1677
        let gvar = out_of_order_gvar_with_shared_tuples();
1678
1679
        let font = test_font_for_patching_with_loca_mod(
1680
            true,
1681
            |_| {},
1682
            HashMap::from([
1683
                (Gvar::TAG, gvar.as_slice()),
1684
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1685
            ]),
1686
        );
1687
        let font = FontRef::new(&font).unwrap();
1688
1689
        let patched =
1690
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1691
                .unwrap();
1692
        let patched = FontRef::new(&patched).unwrap();
1693
1694
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1695
1696
        let mut expected_gvar: Vec<u8> = vec![];
1697
1698
        // Patching will reorder the gvar table to the expected spec ordering, so for expected compare to the
1699
        // correctly ordered version.
1700
        let gvar = short_gvar_with_shared_tuples();
1701
        let change_start = gvar.offset_for("glyph_offset[3]");
1702
1703
        expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
1704
        // Offsets
1705
        expected_gvar.extend_from_slice(&[
1706
            0x00, 0x03, // gid 3
1707
            0x00, 0x03, // gid 4
1708
            0x00, 0x03, // gid 5
1709
            0x00, 0x03, // gid 6
1710
            0x00, 0x03, // gid 7
1711
            0x00, 0x05, // gid 8
1712
            0x00, 0x06, // gid 9
1713
            0x00, 0x06, // gid 10
1714
            0x00, 0x06, // gid 11
1715
            0x00, 0x06, // gid 12
1716
            0x00, 0x06, // gid 13
1717
            0x00, 0x06, // gid 14
1718
            0x00, 0x06u8, // trailing
1719
        ]);
1720
        // Shared tuples
1721
        expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
1722
        // Data
1723
        expected_gvar.extend_from_slice(&[
1724
            1, 2, 3, 4, // gid 0
1725
            b'm', b'n', // gid 2
1726
            b'o', b'p', b'q', 0, // gid 7
1727
            b'r', 0u8, // gid 8
1728
        ]);
1729
        assert_eq!(&expected_gvar, new_gvar);
1730
    }
1731
1732
    #[test]
1733
    fn glyph_keyed_gvar_requires_offset_type_switch() {
1734
        let patch = assemble_glyph_keyed_patch(
1735
            glyph_keyed_patch_header(),
1736
            glyf_and_gvar_u16_glyph_patches(),
1737
        );
1738
        let patch: &[u8] = &patch;
1739
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1740
        let patch_info = patch_info(IFT_TAG, 0);
1741
1742
        let gvar = short_gvar_near_maximum_offset_size();
1743
1744
        let font = test_font_for_patching_with_loca_mod(
1745
            true,
1746
            |_| {},
1747
            HashMap::from([
1748
                (Gvar::TAG, gvar.as_slice()),
1749
                (Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
1750
            ]),
1751
        );
1752
        let font = FontRef::new(&font).unwrap();
1753
1754
        let patched =
1755
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1756
                .unwrap();
1757
        let patched = FontRef::new(&patched).unwrap();
1758
1759
        let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
1760
        let new_gvar = FontData::new(new_gvar);
1761
        let new_gvar = Gvar::read(new_gvar).unwrap();
1762
1763
        let gid0_data = vec![1u8; 131066];
1764
        assert_eq!(
1765
            new_gvar
1766
                .data_for_gid(GlyphId::new(0))
1767
                .unwrap()
1768
                .unwrap()
1769
                .as_bytes(),
1770
            &gid0_data
1771
        );
1772
1773
        assert!(new_gvar.data_for_gid(GlyphId::new(1)).unwrap().is_none());
1774
1775
        assert_eq!(
1776
            new_gvar
1777
                .data_for_gid(GlyphId::new(2))
1778
                .unwrap()
1779
                .unwrap()
1780
                .as_bytes(),
1781
            b"mn"
1782
        );
1783
1784
        assert!(new_gvar.data_for_gid(GlyphId::new(6)).unwrap().is_none());
1785
1786
        assert_eq!(
1787
            new_gvar
1788
                .data_for_gid(GlyphId::new(7))
1789
                .unwrap()
1790
                .unwrap()
1791
                .as_bytes(),
1792
            b"opq",
1793
        );
1794
1795
        assert_eq!(
1796
            new_gvar
1797
                .data_for_gid(GlyphId::new(8))
1798
                .unwrap()
1799
                .unwrap()
1800
                .as_bytes(),
1801
            b"r",
1802
        );
1803
1804
        assert!(new_gvar.data_for_gid(GlyphId::new(9)).unwrap().is_none());
1805
    }
1806
1807
    #[test]
1808
    fn glyph_keyed_bad_format() {
1809
        let mut header_builder = glyph_keyed_patch_header();
1810
        header_builder.write_at("format", Tag::new(b"iftk"));
1811
        let patch = assemble_glyph_keyed_patch(header_builder, glyf_u16_glyph_patches());
1812
        let patch: &[u8] = &patch;
1813
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1814
        let patch_info = patch_info(IFT_TAG, 0);
1815
1816
        let font = test_font_for_patching();
1817
        let font = FontRef::new(&font).unwrap();
1818
1819
        assert_eq!(
1820
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1821
            Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'"))
1822
        );
1823
    }
1824
1825
    #[test]
1826
    fn glyph_keyed_unknown_table() {
1827
        let mut builder = glyf_and_gvar_u16_glyph_patches();
1828
        builder.write_at("gvar_tag", Tag::new(b"hijk"));
1829
1830
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1831
        let patch: &[u8] = &patch;
1832
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1833
        let patch_info = patch_info(IFT_TAG, 0);
1834
1835
        let font = test_font_for_patching();
1836
        let font = FontRef::new(&font).unwrap();
1837
1838
        let patched =
1839
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
1840
                .unwrap();
1841
        let patched = FontRef::new(&patched).unwrap();
1842
1843
        let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
1844
        assert_eq!(
1845
            &[
1846
                1, 2, 3, 4, 5, 0, // gid 0
1847
                6, 7, 8, 0, // gid 1
1848
                b'a', b'b', b'c', 0, // gid2
1849
                b'd', b'e', b'f', b'g', // gid 7
1850
                b'h', b'i', b'j', b'k', b'l', 0, // gid 8
1851
            ],
1852
            new_glyf
1853
        );
1854
1855
        let new_loca = patched.loca(None).unwrap();
1856
        let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
1857
1858
        assert_eq!(
1859
            vec![
1860
                0,  // gid 0
1861
                6,  // gid 1
1862
                10, // gid 2
1863
                14, // gid 3
1864
                14, // gid 4
1865
                14, // gid 5
1866
                14, // gid 6
1867
                14, // gid 7
1868
                18, // gid 8
1869
                24, // gid 9
1870
                24, // gid 10
1871
                24, // gid 11
1872
                24, // gid 12
1873
                24, // gid 13
1874
                24, // gid 14
1875
                24, // end
1876
            ],
1877
            indices
1878
        );
1879
1880
        check_tables_equal(&font, &patched, [Glyf::TAG, Loca::TAG, IFT_TAG].into());
1881
    }
1882
1883
    #[test]
1884
    fn glyph_keyed_unsorted_tables() {
1885
        let mut builder = glyf_and_gvar_u16_glyph_patches();
1886
        builder.write_at("gvar_tag", Tag::new(b"glye"));
1887
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1888
        let patch: &[u8] = &patch;
1889
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1890
        let patch_info = patch_info(IFT_TAG, 0);
1891
1892
        let font = test_font_for_patching();
1893
        let font = FontRef::new(&font).unwrap();
1894
1895
        assert_eq!(
1896
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1897
            Err(PatchingError::InvalidPatch(
1898
                "Duplicate or unsorted table tag."
1899
            ))
1900
        );
1901
    }
1902
1903
    #[test]
1904
    fn glyph_keyed_duplicate_tables() {
1905
        let mut builder = glyf_and_gvar_u16_glyph_patches();
1906
        builder.write_at("gvar_tag", Glyf::TAG);
1907
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1908
        let patch: &[u8] = &patch;
1909
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1910
        let patch_info = patch_info(IFT_TAG, 0);
1911
1912
        let font = test_font_for_patching();
1913
        let font = FontRef::new(&font).unwrap();
1914
1915
        assert_eq!(
1916
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1917
            Err(PatchingError::InvalidPatch(
1918
                "Duplicate or unsorted table tag."
1919
            ))
1920
        );
1921
    }
1922
1923
    #[test]
1924
    fn glyph_keyed_unsorted_gids() {
1925
        let mut builder = glyf_u16_glyph_patches();
1926
        builder.write_at("gid_8", 6);
1927
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1928
        let patch: &[u8] = &patch;
1929
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1930
        let patch_info = patch_info(IFT_TAG, 0);
1931
1932
        let font = test_font_for_patching();
1933
        let font = FontRef::new(&font).unwrap();
1934
1935
        assert_eq!(
1936
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1937
            Err(PatchingError::PatchParsingFailed(ReadError::MalformedData(
1938
                "Glyph IDs are unsorted or duplicated."
1939
            ))),
1940
        );
1941
    }
1942
1943
    #[test]
1944
    fn glyph_keyed_duplicate_gids() {
1945
        let mut builder = glyf_u16_glyph_patches();
1946
        builder.write_at("gid_8", 7);
1947
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1948
        let patch: &[u8] = &patch;
1949
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1950
        let patch_info = patch_info(IFT_TAG, 0);
1951
1952
        let font = test_font_for_patching();
1953
        let font = FontRef::new(&font).unwrap();
1954
1955
        assert_eq!(
1956
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1957
            Err(PatchingError::PatchParsingFailed(ReadError::MalformedData(
1958
                "Glyph IDs are unsorted or duplicated."
1959
            ))),
1960
        );
1961
    }
1962
1963
    #[test]
1964
    fn glyph_keyed_uncompressed_length_to_small() {
1965
        let len = glyf_u16_glyph_patches().as_slice().len();
1966
        let mut patch =
1967
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
1968
        patch.write_at("max_uncompressed_length", len as u32 - 1);
1969
        let patch: &[u8] = &patch;
1970
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1971
        let patch_info = patch_info(IFT_TAG, 0);
1972
1973
        let font = test_font_for_patching();
1974
        let font = FontRef::new(&font).unwrap();
1975
1976
        assert_eq!(
1977
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1978
            Err(PatchingError::InvalidPatch("Max size exceeded.")),
1979
        );
1980
    }
1981
1982
    #[test]
1983
    fn glyph_keyed_max_glyph_exceeded() {
1984
        let mut builder = glyf_u16_glyph_patches();
1985
        builder.write_at("gid_13", 15u16);
1986
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
1987
        let patch: &[u8] = &patch;
1988
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
1989
        let patch_info = patch_info(IFT_TAG, 0);
1990
1991
        let font = test_font_for_patching();
1992
        let font = FontRef::new(&font).unwrap();
1993
1994
        assert_eq!(
1995
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
1996
            Err(PatchingError::InvalidPatch(
1997
                "Patch would add a glyph beyond this fonts maximum."
1998
            )),
1999
        );
2000
    }
2001
2002
    #[test]
2003
    fn glyph_keyed_unordered_loca_offsets() {
2004
        let patch =
2005
            assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
2006
        let patch: &[u8] = &patch;
2007
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
2008
        let patch_info = patch_info(IFT_TAG, 0);
2009
2010
        // unorder offsets related to a glyph not being replaced
2011
        let font = test_font_for_patching_with_loca_mod(
2012
            true,
2013
            |loca| {
2014
                let loca_gid_1 = loca[1];
2015
                let loca_gid_2 = loca[2];
2016
                loca[1] = loca_gid_2;
2017
                loca[2] = loca_gid_1;
2018
            },
2019
            Default::default(),
2020
        );
2021
2022
        let font = FontRef::new(&font).unwrap();
2023
2024
        assert_eq!(
2025
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
2026
            Err(PatchingError::FontParsingFailed(ReadError::MalformedData(
2027
                "offset array contains unordered offsets."
2028
            ))),
2029
        );
2030
    }
2031
2032
    #[test]
2033
    fn cff_patching() {
2034
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), cff_u16_glyph_patches());
2035
        let patch: &[u8] = &patch;
2036
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
2037
        let patch_info = patch_info(IFT_TAG, 0);
2038
2039
        let cff_font = FontRef::new(CFF_FONT).unwrap();
2040
        let mut font_builder = FontBuilder::new();
2041
        font_builder.copy_missing_tables(cff_font);
2042
2043
        let mut ift_table = format2_with_one_charstrings_offset();
2044
        ift_table.write_at("charstrings_offset", CFF_FONT_CHARSTRINGS_OFFSET);
2045
        font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
2046
2047
        let cff_font_data = font_builder.build();
2048
        let cff_font = FontRef::new(cff_font_data.as_slice()).unwrap();
2049
2050
        let patched =
2051
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff_font, &BuiltInBrotliDecoder)
2052
                .unwrap();
2053
        let patched = FontRef::new(&patched).unwrap();
2054
2055
        let old_cff = CFFAndCharStrings::from_cff_font(
2056
            &cff_font,
2057
            CFF_FONT_CHARSTRINGS_OFFSET,
2058
            GlyphId::new(59),
2059
        )
2060
        .unwrap();
2061
        let new_cff = CFFAndCharStrings::from_cff_font(
2062
            &patched,
2063
            CFF_FONT_CHARSTRINGS_OFFSET,
2064
            GlyphId::new(59),
2065
        )
2066
        .unwrap();
2067
2068
        assert_eq!(new_cff.charstrings.off_size(), 2);
2069
        assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
2070
2071
        // Unmodified glyphs
2072
        assert_eq!(
2073
            old_cff.charstrings.get(0).unwrap(),
2074
            new_cff.charstrings.get(0).unwrap()
2075
        );
2076
        assert_eq!(
2077
            old_cff.charstrings.get(2).unwrap(),
2078
            new_cff.charstrings.get(2).unwrap()
2079
        );
2080
        assert_eq!(
2081
            old_cff.charstrings.get(34).unwrap(),
2082
            new_cff.charstrings.get(34).unwrap()
2083
        );
2084
        assert_eq!(
2085
            old_cff.charstrings.get(37).unwrap(),
2086
            new_cff.charstrings.get(37).unwrap()
2087
        );
2088
        assert_eq!(
2089
            old_cff.charstrings.get(39).unwrap(),
2090
            new_cff.charstrings.get(39).unwrap()
2091
        );
2092
2093
        // Inserted glyphs
2094
        assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
2095
        assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
2096
        assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
2097
        assert_eq!(b"mn", new_cff.charstrings.get(59).unwrap());
2098
    }
2099
2100
    #[test]
2101
    fn cff_patching_changes_offset_size() {
2102
        let patch_buffer = cff_u16_glyph_patches();
2103
        let mut patch_buffer = patch_buffer.extend(iter::repeat_n(42u8, 70_000));
2104
        patch_buffer.write_at("end_offset", patch_buffer.len() as u32);
2105
2106
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), patch_buffer);
2107
        let patch: &[u8] = &patch;
2108
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
2109
        let patch_info = patch_info(IFT_TAG, 0);
2110
2111
        let cff_font = FontRef::new(CFF_FONT).unwrap();
2112
        let mut font_builder = FontBuilder::new();
2113
        font_builder.copy_missing_tables(cff_font);
2114
2115
        let mut ift_table = format2_with_one_charstrings_offset();
2116
        ift_table.write_at("charstrings_offset", CFF_FONT_CHARSTRINGS_OFFSET);
2117
        font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
2118
2119
        let cff_font_data = font_builder.build();
2120
        let cff_font = FontRef::new(cff_font_data.as_slice()).unwrap();
2121
2122
        let patched =
2123
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff_font, &BuiltInBrotliDecoder)
2124
                .unwrap();
2125
        let patched = FontRef::new(&patched).unwrap();
2126
2127
        let old_cff = CFFAndCharStrings::from_cff_font(
2128
            &cff_font,
2129
            CFF_FONT_CHARSTRINGS_OFFSET,
2130
            GlyphId::new(59),
2131
        )
2132
        .unwrap();
2133
        let new_cff = CFFAndCharStrings::from_cff_font(
2134
            &patched,
2135
            CFF_FONT_CHARSTRINGS_OFFSET, // patching doesn't ever change the offsets location
2136
            GlyphId::new(59),
2137
        )
2138
        .unwrap();
2139
2140
        assert_eq!(new_cff.charstrings.off_size(), 3);
2141
        assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
2142
2143
        // Unmodified glyphs
2144
        assert_eq!(
2145
            old_cff.charstrings.get(0).unwrap(),
2146
            new_cff.charstrings.get(0).unwrap()
2147
        );
2148
        assert_eq!(
2149
            old_cff.charstrings.get(2).unwrap(),
2150
            new_cff.charstrings.get(2).unwrap()
2151
        );
2152
        assert_eq!(
2153
            old_cff.charstrings.get(34).unwrap(),
2154
            new_cff.charstrings.get(34).unwrap()
2155
        );
2156
        assert_eq!(
2157
            old_cff.charstrings.get(37).unwrap(),
2158
            new_cff.charstrings.get(37).unwrap()
2159
        );
2160
        assert_eq!(
2161
            old_cff.charstrings.get(39).unwrap(),
2162
            new_cff.charstrings.get(39).unwrap()
2163
        );
2164
2165
        // Inserted glyphs
2166
        assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
2167
        assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
2168
        assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
2169
        assert_eq!(
2170
            [b'm', b'n', 42, 42, 42],
2171
            &new_cff.charstrings.get(59).unwrap()[0..5]
2172
        );
2173
        assert_eq!(70_002, new_cff.charstrings.get(59).unwrap().len());
2174
    }
2175
2176
    #[test]
2177
    fn cff2_patching() {
2178
        let mut patches = cff_u16_glyph_patches();
2179
        patches.write_at("tag", Cff2::TAG);
2180
2181
        let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), patches);
2182
        let patch: &[u8] = &patch;
2183
        let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
2184
        let patch_info = patch_info(IFT_TAG, 0);
2185
2186
        let cff2_font = FontRef::new(CFF2_FONT).unwrap();
2187
        let mut font_builder = FontBuilder::new();
2188
        font_builder.copy_missing_tables(cff2_font);
2189
2190
        let mut ift_table = format2_with_one_charstrings_offset();
2191
        ift_table.write_at("field_flags", 0b00000010u8);
2192
        ift_table.write_at("charstrings_offset", CFF2_FONT_CHARSTRINGS_OFFSET);
2193
        font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
2194
2195
        let cff2_font_data = font_builder.build();
2196
        let cff2_font = FontRef::new(cff2_font_data.as_slice()).unwrap();
2197
2198
        let patched =
2199
            apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff2_font, &BuiltInBrotliDecoder)
2200
                .unwrap();
2201
        let patched = FontRef::new(&patched).unwrap();
2202
2203
        let old_cff = CFFAndCharStrings::from_cff2_font(
2204
            &cff2_font,
2205
            CFF2_FONT_CHARSTRINGS_OFFSET,
2206
            GlyphId::new(59),
2207
        )
2208
        .unwrap();
2209
        let new_cff = CFFAndCharStrings::from_cff2_font(
2210
            &patched,
2211
            CFF2_FONT_CHARSTRINGS_OFFSET,
2212
            GlyphId::new(59),
2213
        )
2214
        .unwrap();
2215
2216
        assert_eq!(new_cff.charstrings.off_size(), 2);
2217
        assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
2218
2219
        // Unmodified glyphs
2220
        assert_eq!(
2221
            old_cff.charstrings.get(0).unwrap(),
2222
            new_cff.charstrings.get(0).unwrap()
2223
        );
2224
        assert_eq!(
2225
            old_cff.charstrings.get(2).unwrap(),
2226
            new_cff.charstrings.get(2).unwrap()
2227
        );
2228
        assert_eq!(
2229
            old_cff.charstrings.get(34).unwrap(),
2230
            new_cff.charstrings.get(34).unwrap()
2231
        );
2232
        assert_eq!(
2233
            old_cff.charstrings.get(37).unwrap(),
2234
            new_cff.charstrings.get(37).unwrap()
2235
        );
2236
        assert_eq!(
2237
            old_cff.charstrings.get(39).unwrap(),
2238
            new_cff.charstrings.get(39).unwrap()
2239
        );
2240
2241
        // Inserted glyphs
2242
        assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
2243
        assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
2244
        assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
2245
        assert_eq!(b"mn", new_cff.charstrings.get(59).unwrap());
2246
    }
2247
2248
    #[test]
2249
    fn max_representable_size() {
2250
        assert_eq!(
2251
            OffsetType::ShortDivByTwo(ShortDivByTwoInfo).max_representable_size(),
2252
            131070
2253
        );
2254
        assert_eq!(
2255
            OffsetType::Long(LongInfo).max_representable_size(),
2256
            4_294_967_295
2257
        );
2258
        assert_eq!(
2259
            OffsetType::CffFour(CffFourInfo).max_representable_size(),
2260
            4_294_967_294
2261
        );
2262
    }
2263
2264
    // TODO test of invalid cases:
2265
    // - patch data offsets unordered.
2266
    // - loca offset type switch required.
2267
    // - glyph keyed test with large number of offsets to check type conversion on (glyphCount * tableCount)
2268
    // - test that glyph keyed patches are idempotent.
2269
}