1use std::collections::HashMap;
14
15use crate::patch_group::PatchInfo;
16
17use crate::glyph_keyed::apply_glyph_keyed_patches;
18
19use crate::table_keyed::apply_table_keyed_patch;
20use font_types::Tag;
21use read_fonts::tables::ift::{CompatibilityId, GlyphKeyedPatch, TableKeyedPatch};
22use skera::serialize::SerializeErrorFlags;
23
24use read_fonts::{FontData, FontRead, FontRef, ReadError};
25
26use shared_brotli_patch_decoder::decode_error::DecodeError;
27use shared_brotli_patch_decoder::SharedBrotliDecoder;
28
29pub trait IncrementalFontPatchBase {
33 fn apply_table_keyed_patch<D: SharedBrotliDecoder>(
39 &self,
40 patch: &PatchInfo,
41 patch_data: &[u8],
42 brotli_decoder: &D,
43 ) -> Result<Vec<u8>, PatchingError>;
44
45 fn apply_glyph_keyed_patches<'a, D: SharedBrotliDecoder>(
51 &self,
52 patches: impl Iterator<Item = (&'a PatchInfo, &'a [u8])>,
53 brotli_decoder: &D,
54 ) -> Result<Vec<u8>, PatchingError>;
55}
56
57#[derive(Debug, Clone, PartialEq)]
59pub enum PatchingError {
60 PatchParsingFailed(ReadError),
61 FontParsingFailed(ReadError),
62 SerializationError(SerializeErrorFlags),
63 IncompatiblePatch,
64 NonIncrementalFont,
65 InvalidPatch(&'static str),
66 EmptyPatchList,
67 InternalError,
68 MissingPatches,
69}
70
71impl From<SerializeErrorFlags> for PatchingError {
72 fn from(err: SerializeErrorFlags) -> Self {
73 PatchingError::SerializationError(err)
74 }
75}
76
77impl From<ReadError> for PatchingError {
78 fn from(err: ReadError) -> Self {
79 PatchingError::FontParsingFailed(err)
80 }
81}
82
83impl From<DecodeError> for PatchingError {
84 fn from(decoding_error: DecodeError) -> Self {
85 match decoding_error {
86 DecodeError::InitFailure => {
87 PatchingError::InvalidPatch("Failure to init brotli encoder.")
88 }
89 DecodeError::InvalidStream => PatchingError::InvalidPatch("Malformed brotli stream."),
90 DecodeError::InvalidDictionary => PatchingError::InvalidPatch("Malformed dictionary."),
91 DecodeError::MaxSizeExceeded => PatchingError::InvalidPatch("Max size exceeded."),
92 DecodeError::ExcessInputData => {
93 PatchingError::InvalidPatch("Input brotli stream has excess bytes.")
94 }
95 DecodeError::IoError(_) => {
96 PatchingError::InvalidPatch("IO error decoding input brotli stream.")
97 }
98 }
99 }
100}
101
102impl std::fmt::Display for PatchingError {
103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 match self {
105 PatchingError::PatchParsingFailed(err) => {
106 write!(f, "Failed to parse patch file: {}", err)
107 }
108 PatchingError::FontParsingFailed(err) => {
109 write!(f, "Failed to parse font file: {}", err)
110 }
111 PatchingError::SerializationError(err) => {
112 write!(f, "serialization failure constructing patched table: {err}")
113 }
114 PatchingError::IncompatiblePatch => {
115 write!(f, "Compatibility ID of the patch does not match the font.")
116 }
117
118 PatchingError::NonIncrementalFont => {
119 write!(
120 f,
121 "Can't patch font as it's not an incremental transfer font."
122 )
123 }
124 PatchingError::InvalidPatch(msg) => write!(f, "Invalid patch file: '{msg}'"),
125 PatchingError::EmptyPatchList => write!(f, "At least one patch file must be provided."),
126 PatchingError::InternalError => write!(
127 f,
128 "Internal constraint violated, typically should not happen."
129 ),
130 PatchingError::MissingPatches => write!(f, "Not all patch data has been supplied."),
131 }
132 }
133}
134
135impl std::error::Error for PatchingError {}
136
137impl IncrementalFontPatchBase for FontRef<'_> {
138 fn apply_table_keyed_patch<D: SharedBrotliDecoder>(
139 &self,
140 patch: &PatchInfo,
141 patch_data: &[u8],
142 brotli_decoder: &D,
143 ) -> Result<Vec<u8>, PatchingError> {
144 let font_compat_id = patch
145 .tag()
146 .font_compat_id(self)
147 .map_err(PatchingError::FontParsingFailed)?;
148 if font_compat_id != *patch.tag().expected_compat_id() {
149 return Err(PatchingError::IncompatiblePatch);
150 }
151
152 let patch = TableKeyedPatch::read(FontData::new(patch_data))
153 .map_err(PatchingError::PatchParsingFailed)?;
154
155 if patch.compatibility_id() != font_compat_id {
156 return Err(PatchingError::IncompatiblePatch);
157 }
158
159 apply_table_keyed_patch(&patch, self, brotli_decoder)
160 }
161
162 fn apply_glyph_keyed_patches<'a, D: SharedBrotliDecoder>(
163 &self,
164 patches: impl Iterator<Item = (&'a PatchInfo, &'a [u8])>,
165 brotli_decoder: &D,
166 ) -> Result<Vec<u8>, PatchingError> {
167 let mut cached_compat_ids: HashMap<Tag, Result<CompatibilityId, PatchingError>> =
168 Default::default();
169
170 let mut raw_patches: Vec<(&PatchInfo, GlyphKeyedPatch<'_>)> = vec![];
171 for (patch_info, patch_data) in patches {
172 let tag = patch_info.tag();
173 let font_compat_id = cached_compat_ids
174 .entry(tag.tag())
175 .or_insert_with(|| {
176 tag.font_compat_id(self)
177 .map_err(PatchingError::FontParsingFailed)
178 })
179 .as_ref()
180 .map_err(Clone::clone)?;
181 if font_compat_id != tag.expected_compat_id() {
182 return Err(PatchingError::IncompatiblePatch);
183 }
184
185 let patch = GlyphKeyedPatch::read(FontData::new(patch_data))
186 .map_err(PatchingError::PatchParsingFailed)?;
187
188 if *font_compat_id != patch.compatibility_id() {
189 return Err(PatchingError::IncompatiblePatch);
190 }
191
192 raw_patches.push((patch_info, patch));
193 }
194
195 apply_glyph_keyed_patches(&raw_patches, self, brotli_decoder)
196 }
197}
198
199impl IncrementalFontPatchBase for &[u8] {
200 fn apply_table_keyed_patch<D: SharedBrotliDecoder>(
201 &self,
202 patch: &PatchInfo,
203 patch_data: &[u8],
204 brotli_decoder: &D,
205 ) -> Result<Vec<u8>, PatchingError> {
206 FontRef::new(self)
207 .map_err(PatchingError::FontParsingFailed)?
208 .apply_table_keyed_patch(patch, patch_data, brotli_decoder)
209 }
210
211 fn apply_glyph_keyed_patches<'a, D: SharedBrotliDecoder>(
212 &self,
213 patches: impl Iterator<Item = (&'a PatchInfo, &'a [u8])>,
214 brotli_decoder: &D,
215 ) -> Result<Vec<u8>, PatchingError> {
216 FontRef::new(self)
217 .map_err(PatchingError::FontParsingFailed)?
218 .apply_glyph_keyed_patches(patches, brotli_decoder)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224
225 use std::collections::HashMap;
226
227 use font_test_data::ift::{
228 codepoints_only_format2, glyf_u16_glyph_patches, glyph_keyed_patch_header,
229 table_keyed_patch,
230 };
231 use read_fonts::{
232 collections::IntSet,
233 tables::ift::{CompatibilityId, IFTX_TAG, IFT_TAG},
234 };
235 use shared_brotli_patch_decoder::BuiltInBrotliDecoder;
236
237 use crate::{
238 font_patch::PatchingError,
239 glyph_keyed::tests::assemble_glyph_keyed_patch,
240 patchmap::{IftTableTag, PatchId, PatchUrl},
241 testdata::test_font_for_patching_with_loca_mod,
242 };
243
244 use super::{IncrementalFontPatchBase, PatchInfo};
245
246 #[test]
249 fn table_keyed_patch_and_font_compat_id_mismatch() {
250 let info = PatchInfo {
251 url: PatchUrl::expand_template(
252 &[8, b'f', b'o', b'o', b'.', b'b', b'a', b'r', b'/', 128],
253 &PatchId::Numeric(0),
254 )
255 .unwrap(),
256 source_table: IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])),
257 application_flag_bit_indices: IntSet::<u32>::empty(),
258 };
259
260 let ift_table = codepoints_only_format2();
261 let mut iftx_table = codepoints_only_format2();
262 iftx_table.write_at("compat_id[0]", 2u32);
263
264 let font = test_font_for_patching_with_loca_mod(
265 true,
266 |_| {},
267 HashMap::from([
268 (IFT_TAG, ift_table.as_slice()),
269 (IFTX_TAG, iftx_table.as_slice()),
270 ]),
271 );
272
273 let mut patch = table_keyed_patch();
274 patch.write_at("compat_id", 2);
275 assert_eq!(
276 font.as_slice()
277 .apply_table_keyed_patch(&info, &patch, &BuiltInBrotliDecoder),
278 Err(PatchingError::IncompatiblePatch)
279 );
280 }
281
282 #[test]
283 fn table_keyed_patch_info_and_font_compat_id_mismatch() {
284 let info = PatchInfo {
285 url: PatchUrl::expand_template(
286 &[8, b'f', b'o', b'o', b'.', b'b', b'a', b'r', b'/', 128],
287 &PatchId::Numeric(0),
288 )
289 .unwrap(),
290 source_table: IftTableTag::Ift(CompatibilityId::from_u32s([2, 2, 3, 4])),
291 application_flag_bit_indices: IntSet::<u32>::empty(),
292 };
293
294 let ift_table = codepoints_only_format2();
295 let font = test_font_for_patching_with_loca_mod(
296 true,
297 |_| {},
298 HashMap::from([(IFT_TAG, ift_table.as_slice())]),
299 );
300
301 let patch = table_keyed_patch();
302 assert_eq!(
303 font.as_slice()
304 .apply_table_keyed_patch(&info, &patch, &BuiltInBrotliDecoder),
305 Err(PatchingError::IncompatiblePatch)
306 );
307 }
308
309 #[test]
310 fn glyph_keyed_patch_and_font_compat_id_mismatch() {
311 let info = PatchInfo {
312 url: PatchUrl::expand_template(
313 &[8, b'f', b'o', b'o', b'.', b'b', b'a', b'r', b'/', 128],
314 &PatchId::Numeric(0),
315 )
316 .unwrap(),
317 source_table: IftTableTag::Ift(CompatibilityId::from_u32s([1, 2, 3, 4])),
318 application_flag_bit_indices: IntSet::<u32>::empty(),
319 };
320
321 let ift_table = codepoints_only_format2();
322 let font = test_font_for_patching_with_loca_mod(
323 true,
324 |_| {},
325 HashMap::from([(IFT_TAG, ift_table.as_slice())]),
326 );
327
328 let patch =
329 assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
330
331 let input = vec![(&info, patch.as_slice())];
332 assert_eq!(
333 font.as_slice()
334 .apply_glyph_keyed_patches(input.into_iter(), &BuiltInBrotliDecoder),
335 Err(PatchingError::IncompatiblePatch)
336 );
337 }
338
339 #[test]
340 fn glyph_keyed_patch_info_and_font_compat_id_mismatch() {
341 let info = PatchInfo {
342 url: PatchUrl::expand_template(
343 &[8, b'f', b'o', b'o', b'.', b'b', b'a', b'r', b'/', 128],
344 &PatchId::Numeric(0),
345 )
346 .unwrap(),
347 source_table: IftTableTag::Ift(CompatibilityId::from_u32s([6, 7, 9, 9])),
348 application_flag_bit_indices: IntSet::<u32>::empty(),
349 };
350
351 let mut ift_table = codepoints_only_format2();
352 ift_table.write_at("compat_id[0]", 6u32);
353 ift_table.write_at("compat_id[1]", 7u32);
354 ift_table.write_at("compat_id[2]", 8u32);
355 ift_table.write_at("compat_id[3]", 9u32);
356
357 let font = test_font_for_patching_with_loca_mod(
358 true,
359 |_| {},
360 HashMap::from([(IFT_TAG, ift_table.as_slice())]),
361 );
362
363 let patch =
364 assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
365
366 let input = vec![(&info, patch.as_slice())];
367 assert_eq!(
368 font.as_slice()
369 .apply_glyph_keyed_patches(input.into_iter(), &BuiltInBrotliDecoder),
370 Err(PatchingError::IncompatiblePatch)
371 );
372 }
373}