use crate::patchmap::IftTableTag;
use crate::table_keyed::copy_unprocessed_tables;
use crate::{font_patch::PatchingError, patch_group::PatchInfo};
use font_types::{Scalar, Uint24};
use read_fonts::ps::cff::{index::Index, v1::Index as Index1, v2::Index as Index2};
use read_fonts::{
collections::IntSet,
tables::{
cff::Cff,
cff2::Cff2,
glyf::Glyf,
gvar::{Gvar, GvarFlags},
ift::{GlyphKeyedPatch, GlyphPatches},
ift::{IFTX_TAG, IFT_TAG},
loca::Loca,
},
types::Tag,
FontData, FontRead, FontRef, ReadError, TableProvider, TopLevelTable,
};
use shared_brotli_patch_decoder::SharedBrotliDecoder;
use skera::serialize::{OffsetWhence, SerializeErrorFlags, Serializer};
use skrifa::GlyphId;
use std::collections::{BTreeSet, HashMap};
use std::ops::{Range, RangeInclusive};
use write_fonts::FontBuilder;
pub(crate) fn apply_glyph_keyed_patches<D: SharedBrotliDecoder>(
patches: &[(&PatchInfo, GlyphKeyedPatch<'_>)],
font: &FontRef,
brotli_decoder: &D,
) -> Result<Vec<u8>, PatchingError> {
let mut decompression_buffer: Vec<Vec<u8>> = Vec::with_capacity(patches.len());
for (_, patch) in patches {
if patch.format() != Tag::new(b"ifgk") {
return Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'"));
}
decompression_buffer.push(
brotli_decoder
.decode(
patch.brotli_stream(),
None,
patch.max_uncompressed_length() as usize,
)
.map_err(PatchingError::from)?,
);
}
let mut glyph_patches: Vec<GlyphPatches<'_>> = vec![];
for (raw_data, patch) in decompression_buffer.iter().zip(patches) {
glyph_patches.push(
GlyphPatches::read(FontData::new(raw_data), patch.1.flags())
.map_err(PatchingError::PatchParsingFailed)?,
);
}
let num_glyphs = font
.maxp()
.map_err(PatchingError::FontParsingFailed)?
.num_glyphs();
let max_glyph_id = GlyphId::new(num_glyphs.checked_sub(1).ok_or(
PatchingError::FontParsingFailed(ReadError::MalformedData("Font has no glyphs.")),
)? as u32);
let mut processed_tables = BTreeSet::from([IFT_TAG, IFTX_TAG]);
let mut font_builder = FontBuilder::new();
for table_tag in table_tag_list(&glyph_patches)? {
if table_tag == Glyf::TAG {
let (Some(glyf), Ok(loca)) = (font.table_data(Glyf::TAG), font.loca(None)) else {
return Err(PatchingError::InvalidPatch(
"Trying to patch glyf/loca but base font doesn't have them.",
));
};
patch_offset_array(
Glyf::TAG,
&glyph_patches,
GlyfAndLoca {
loca,
glyf: glyf.as_bytes(),
},
max_glyph_id,
&mut font_builder,
)?;
processed_tables.insert(table_tag);
processed_tables.insert(Loca::TAG);
} else if table_tag == Gvar::TAG {
let Ok(gvar) = font.gvar() else {
return Err(PatchingError::InvalidPatch(
"Trying to patch gvar but base font doesn't have them.",
));
};
patch_offset_array(
Gvar::TAG,
&glyph_patches,
gvar,
max_glyph_id,
&mut font_builder,
)?;
processed_tables.insert(Gvar::TAG);
} else if table_tag == Cff::TAG {
let Some(charstrings_offset) = font
.ift()
.ok()
.as_ref()
.and_then(|t| t.cff_charstrings_offset())
else {
return Err(PatchingError::InvalidPatch(
"Required CFF charstrings offset is missing from IFT table.",
));
};
patch_offset_array(
table_tag,
&glyph_patches,
CFFAndCharStrings::from_cff_font(font, charstrings_offset, max_glyph_id)
.map_err(PatchingError::from)?,
max_glyph_id,
&mut font_builder,
)?;
processed_tables.insert(table_tag);
} else if table_tag == Cff2::TAG {
let Some(charstrings_offset) = font
.ift()
.ok()
.as_ref()
.and_then(|t| t.cff2_charstrings_offset())
else {
return Err(PatchingError::InvalidPatch(
"Required CFF2 charstrings offset is missing from IFT table.",
));
};
patch_offset_array(
table_tag,
&glyph_patches,
CFFAndCharStrings::from_cff2_font(font, charstrings_offset, max_glyph_id)
.map_err(PatchingError::from)?,
max_glyph_id,
&mut font_builder,
)?;
processed_tables.insert(table_tag);
} else {
continue;
}
}
let mut new_itf_data = font
.table_data(IFT_TAG)
.map(|data| data.as_bytes().to_vec());
let mut new_itfx_data = font
.table_data(IFTX_TAG)
.map(|data| data.as_bytes().to_vec());
for (info, _) in patches {
let data = match info.tag() {
IftTableTag::Ift(_) => new_itf_data.as_mut().ok_or(PatchingError::InternalError)?,
IftTableTag::Iftx(_) => new_itfx_data.as_mut().ok_or(PatchingError::InternalError)?,
};
for bit_index in info.application_flag_bit_indices() {
let byte_index = (bit_index as usize) / 8;
let bit_index = (bit_index as usize % 8) as u8;
let byte = data
.get_mut(byte_index)
.ok_or(PatchingError::InternalError)?;
*byte |= 1 << bit_index;
}
}
if let Some(data) = new_itf_data {
font_builder.add_raw(IFT_TAG, data);
}
if let Some(data) = new_itfx_data {
font_builder.add_raw(IFTX_TAG, data);
}
copy_unprocessed_tables(font, processed_tables, &mut font_builder);
Ok(font_builder.build())
}
fn table_tag_list(glyph_patches: &[GlyphPatches]) -> Result<BTreeSet<Tag>, PatchingError> {
for patches in glyph_patches {
if patches
.tables()
.iter()
.zip(patches.tables().iter().skip(1))
.any(|(prev_tag, next_tag)| next_tag <= prev_tag)
{
return Err(PatchingError::InvalidPatch(
"Duplicate or unsorted table tag.",
));
}
}
Ok(glyph_patches
.iter()
.flat_map(|patch| patch.tables())
.map(|tag| tag.get())
.collect::<BTreeSet<Tag>>())
}
fn dedup_gid_replacement_data<'a>(
glyph_patches: impl Iterator<Item = &'a GlyphPatches<'a>>,
table_tag: Tag,
) -> Result<(IntSet<GlyphId>, Vec<&'a [u8]>), ReadError> {
let mut gids: IntSet<GlyphId> = IntSet::default();
let mut data_for_gid: HashMap<GlyphId, &'a [u8]> = HashMap::default();
for glyph_patch in glyph_patches {
let Some((table_index, _)) = glyph_patch
.tables()
.iter()
.enumerate()
.find(|(_, tag)| tag.get() == table_tag)
else {
continue;
};
glyph_patch
.glyph_data_for_table(table_index)
.try_for_each(|result| {
let (gid, data) = result?;
data_for_gid.entry(gid).or_insert(data);
gids.insert(gid);
Ok(())
})?;
}
let mut deduped: Vec<&'a [u8]> = Vec::with_capacity(data_for_gid.len());
gids.iter().for_each(|gid| {
deduped.push(data_for_gid.get(&gid).unwrap());
});
Ok((gids, deduped))
}
fn retained_glyphs_in_font(
replace_gids: &IntSet<GlyphId>,
max_glyph_id: GlyphId,
) -> impl Iterator<Item = RangeInclusive<GlyphId>> + '_ {
replace_gids
.iter_excluded_ranges()
.filter_map(move |range| {
if *range.start() > max_glyph_id {
return None;
}
if *range.end() > max_glyph_id {
return Some(*range.start()..=max_glyph_id);
}
Some(range)
})
}
fn retained_glyphs_total_size<T: GlyphDataOffsetArray>(
gids: &IntSet<GlyphId>,
offsets: &T,
max_glyph_id: GlyphId,
) -> Result<usize, PatchingError> {
let mut total_size = 0usize;
for keep_range in retained_glyphs_in_font(gids, max_glyph_id) {
let start = *keep_range.start();
let end: GlyphId = keep_range
.end()
.to_u32()
.checked_add(1)
.ok_or(PatchingError::InternalError)?
.into();
let start_offset = offsets.offset_for(start)?;
let end_offset = offsets.offset_for(end)?;
total_size += end_offset
.checked_sub(start_offset) .ok_or(PatchingError::FontParsingFailed(ReadError::MalformedData(
"offset entries are not in ascending order",
)))? as usize;
}
Ok(total_size)
}
struct OffsetArrayAndData {
data: Vec<u8>,
offset_array: Vec<u8>,
}
struct OffsetArrayBuilder<'a, T> {
gids: &'a IntSet<GlyphId>,
max_glyph_id: GlyphId,
replacement_data: &'a [&'a [u8]],
offset_array: &'a T,
new_data_len: usize,
new_offsets_len: usize,
}
impl<T: GlyphDataOffsetArray> OffsetArrayBuilder<'_, T> {
fn build<OffsetInfo: OffsetTypeInfo, OffsetType: Scalar + TryFrom<usize>>(
self,
) -> Result<OffsetArrayAndData, PatchingError> {
if !self.offset_array.all_offsets_are_ascending() {
return Err(PatchingError::FontParsingFailed(ReadError::MalformedData(
"offset array contains unordered offsets.",
)));
}
let mut new_data = Serializer::new(self.new_data_len);
new_data
.start_serialize()
.map_err(PatchingError::SerializationError)?;
let mut new_offsets = Serializer::new(self.new_offsets_len);
new_offsets
.start_serialize()
.map_err(PatchingError::SerializationError)?;
let divisor = OffsetInfo::DIVISOR;
let bias = OffsetInfo::BIAS as usize;
let mut replace_it = self.gids.iter_ranges().peekable();
let mut keep_it = retained_glyphs_in_font(self.gids, self.max_glyph_id).peekable();
let mut replacement_data_it = self.replacement_data.iter();
let mut write_index = 0;
loop {
let (range, replace) = match (replace_it.peek(), keep_it.peek()) {
(Some(replace), Some(keep)) => {
if replace.start() <= keep.start() {
(replace_it.next().unwrap(), true)
} else {
(keep_it.next().unwrap(), false)
}
}
(Some(_), None) => (replace_it.next().unwrap(), true),
(None, Some(_)) => (keep_it.next().unwrap(), false),
(None, None) => break,
};
let (start, end) = (range.start().to_u32(), range.end().to_u32());
if replace {
for _ in start..=end {
let data = *replacement_data_it
.next()
.ok_or(PatchingError::InternalError)?;
new_data
.embed_bytes(data)
.map_err(PatchingError::SerializationError)?;
let new_off: OffsetType = ((write_index / divisor) + bias)
.try_into()
.map_err(|_| PatchingError::InternalError)?;
new_offsets
.embed(new_off)
.map_err(PatchingError::SerializationError)?;
write_index += data.len();
if divisor > 1 {
let padding = data.len() % divisor;
write_index += padding;
new_data
.pad(padding)
.map_err(PatchingError::SerializationError)?;
}
}
} else {
let start_off = self.offset_array.offset_for(start.into())? as usize;
let end_off = self.offset_array.offset_for(
end.checked_add(1)
.ok_or(PatchingError::InternalError)?
.into(),
)? as usize;
let len = end_off
.checked_sub(start_off)
.ok_or(PatchingError::InternalError)?;
new_data
.embed_bytes(self.offset_array.get(start_off..end_off)?)
.map_err(PatchingError::SerializationError)?;
for gid in start..=end {
let cur_off = self.offset_array.offset_for(gid.into())? as usize;
let new_off = cur_off - start_off + write_index;
let new_off: OffsetType = ((new_off / divisor) + bias)
.try_into()
.map_err(|_| PatchingError::InternalError)?;
new_offsets
.embed(new_off)
.map_err(PatchingError::SerializationError)?;
}
write_index += len;
}
}
let new_off: OffsetType = ((write_index / divisor) + bias)
.try_into()
.map_err(|_| PatchingError::InternalError)?;
new_offsets
.embed(new_off)
.map_err(PatchingError::SerializationError)?;
new_data.end_serialize();
new_offsets.end_serialize();
Ok(OffsetArrayAndData {
data: new_data.copy_bytes(),
offset_array: new_offsets.copy_bytes(),
})
}
}
fn patch_offset_array<'a, T: GlyphDataOffsetArray>(
table_tag: Tag,
glyph_patches: &'a [GlyphPatches<'a>],
offset_array: T,
max_glyph_id: GlyphId,
font_builder: &mut FontBuilder,
) -> Result<(), PatchingError> {
let offset_type = offset_array.offset_type();
let (gids, replacement_data) = dedup_gid_replacement_data(glyph_patches.iter(), table_tag)
.map_err(PatchingError::PatchParsingFailed)?;
let mut total_data_size = retained_glyphs_total_size(&gids, &offset_array, max_glyph_id)?;
for data in replacement_data.iter() {
let len = data.len();
total_data_size += len + (len % offset_type.offset_divisor());
}
let new_offset_type = if total_data_size > offset_type.max_representable_size() {
offset_array
.available_offset_types()
.find(|candidate_type| candidate_type.max_representable_size() >= total_data_size)
.ok_or(PatchingError::SerializationError(
SerializeErrorFlags::SERIALIZE_ERROR_OFFSET_OVERFLOW,
))?
} else {
offset_type
};
if gids.last().unwrap_or(GlyphId::new(0)) > max_glyph_id {
return Err(PatchingError::InvalidPatch(
"Patch would add a glyph beyond this fonts maximum.",
));
}
let offsets_size = (max_glyph_id.to_u32() as usize + 2) * new_offset_type.offset_width();
let offset_array_builder = OffsetArrayBuilder {
gids: &gids,
max_glyph_id,
replacement_data: &replacement_data,
offset_array: &offset_array,
new_data_len: total_data_size as usize,
new_offsets_len: offsets_size,
};
let new_offsets = match new_offset_type {
OffsetType::CffOne(_) => offset_array_builder.build::<CffOneInfo, u8>()?,
OffsetType::CffTwo(_) => offset_array_builder.build::<CffTwoInfo, u16>()?,
OffsetType::CffThree(_) => offset_array_builder.build::<CffThreeInfo, Uint24>()?,
OffsetType::CffFour(_) => offset_array_builder.build::<CffFourInfo, u32>()?,
OffsetType::ShortDivByTwo(_) => offset_array_builder.build::<ShortDivByTwoInfo, u16>()?,
OffsetType::Long(_) => offset_array_builder.build::<LongInfo, u32>()?,
};
offset_array.add_to_font(font_builder, new_offsets, new_offset_type)?;
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum OffsetType {
CffOne(CffOneInfo),
CffTwo(CffTwoInfo),
CffThree(CffThreeInfo),
CffFour(CffFourInfo),
ShortDivByTwo(ShortDivByTwoInfo),
Long(LongInfo),
}
trait OffsetTypeInfo {
const WIDTH: usize;
const DIVISOR: usize;
const BIAS: u32;
fn width(&self) -> usize {
Self::WIDTH
}
fn divisor(&self) -> usize {
Self::DIVISOR
}
fn bias(&self) -> u32 {
Self::BIAS
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct CffOneInfo;
impl OffsetTypeInfo for CffOneInfo {
const WIDTH: usize = 1;
const DIVISOR: usize = 1;
const BIAS: u32 = 1;
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct CffTwoInfo;
impl OffsetTypeInfo for CffTwoInfo {
const WIDTH: usize = 2;
const DIVISOR: usize = 1;
const BIAS: u32 = 1;
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct CffThreeInfo;
impl OffsetTypeInfo for CffThreeInfo {
const WIDTH: usize = 3;
const DIVISOR: usize = 1;
const BIAS: u32 = 1;
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct CffFourInfo;
impl OffsetTypeInfo for CffFourInfo {
const WIDTH: usize = 4;
const DIVISOR: usize = 1;
const BIAS: u32 = 1;
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct ShortDivByTwoInfo;
impl OffsetTypeInfo for ShortDivByTwoInfo {
const WIDTH: usize = 2;
const DIVISOR: usize = 2;
const BIAS: u32 = 0;
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct LongInfo;
impl OffsetTypeInfo for LongInfo {
const WIDTH: usize = 4;
const DIVISOR: usize = 1;
const BIAS: u32 = 0;
}
impl OffsetType {
fn max_representable_size(self) -> usize {
match self {
Self::ShortDivByTwo(_) => ((1 << 16) - 1) * 2,
_ => ((1 << (self.offset_width() as u64 * 8)) - 1 - self.offset_bias() as u64) as usize,
}
}
fn offset_width(&self) -> usize {
match self {
Self::CffOne(info) => info.width(),
Self::CffTwo(info) => info.width(),
Self::CffThree(info) => info.width(),
Self::CffFour(info) => info.width(),
Self::ShortDivByTwo(info) => info.width(),
Self::Long(info) => info.width(),
}
}
fn offset_divisor(&self) -> usize {
match self {
Self::CffOne(info) => info.divisor(),
Self::CffTwo(info) => info.divisor(),
Self::CffThree(info) => info.divisor(),
Self::CffFour(info) => info.divisor(),
Self::ShortDivByTwo(info) => info.divisor(),
Self::Long(info) => info.divisor(),
}
}
fn offset_bias(&self) -> u32 {
match self {
Self::CffOne(info) => info.bias(),
Self::CffTwo(info) => info.bias(),
Self::CffThree(info) => info.bias(),
Self::CffFour(info) => info.bias(),
Self::ShortDivByTwo(info) => info.bias(),
Self::Long(info) => info.bias(),
}
}
}
struct GlyfAndLoca<'a> {
loca: Loca<'a>,
glyf: &'a [u8],
}
struct CFFAndCharStrings<'a> {
cff_data: &'a [u8],
charstrings: Index<'a>,
charstrings_object_data: &'a [u8],
charstrings_offset: usize,
offset_type: OffsetType,
}
impl CFFAndCharStrings<'_> {
fn from_cff_font<'a>(
font: &FontRef<'a>,
charstrings_offset: u32,
max_glyph_id: GlyphId,
) -> Result<CFFAndCharStrings<'a>, ReadError> {
let cff = font.cff()?;
let charstrings_data =
Self::charstrings_data(cff.offset_data(), charstrings_offset as usize)?;
let charstrings = Index1::read(charstrings_data)?;
let offset_type = Self::offset_type(charstrings.off_size())?;
let offset_base = charstrings.data_byte_range().start;
let charstrings_object_data = charstrings_data
.split_off(offset_base)
.ok_or(ReadError::OutOfBounds)?
.as_bytes();
Self::check_glyph_count(charstrings.count() as u32, max_glyph_id)?;
Ok(CFFAndCharStrings {
cff_data: cff.offset_data().as_bytes(),
charstrings: Index::Format1(charstrings),
charstrings_object_data,
charstrings_offset: charstrings_offset as usize,
offset_type,
})
}
fn from_cff2_font<'a>(
font: &FontRef<'a>,
charstrings_offset: u32,
max_glyph_id: GlyphId,
) -> Result<CFFAndCharStrings<'a>, ReadError> {
let cff2 = font.cff2()?;
let charstrings_data =
Self::charstrings_data(cff2.offset_data(), charstrings_offset as usize)?;
let charstrings = Index2::read(charstrings_data)?;
let offset_type = Self::offset_type(charstrings.off_size())?;
let offset_base = charstrings.data_byte_range().start;
let charstrings_object_data = charstrings_data
.split_off(offset_base)
.ok_or(ReadError::OutOfBounds)?
.as_bytes();
Self::check_glyph_count(charstrings.count(), max_glyph_id)?;
Ok(CFFAndCharStrings {
cff_data: cff2.offset_data().as_bytes(),
charstrings: Index::Format2(charstrings),
charstrings_object_data,
charstrings_offset: charstrings_offset as usize,
offset_type,
})
}
fn check_glyph_count(count: u32, max_glyph_id: GlyphId) -> Result<(), ReadError> {
if count != max_glyph_id.to_u32() + 1 {
return Err(ReadError::MalformedData(
"CFF/CFF2 charstrings glyph count does not match maxp's.",
));
}
Ok(())
}
fn charstrings_data(
table_data: FontData,
charstrings_offset: usize,
) -> Result<FontData, ReadError> {
table_data
.split_off(charstrings_offset)
.ok_or(ReadError::OutOfBounds)
}
fn offset_type(size: u8) -> Result<OffsetType, ReadError> {
Ok(match size {
1 => OffsetType::CffOne(Default::default()),
2 => OffsetType::CffTwo(Default::default()),
3 => OffsetType::CffThree(Default::default()),
4 => OffsetType::CffFour(Default::default()),
_ => {
return Err(ReadError::MalformedData(
"Invalid charstrings offset size (is not 1, 2, 3, or 4).",
))
}
})
}
}
trait GlyphDataOffsetArray {
fn offset_type(&self) -> OffsetType;
fn available_offset_types(&self) -> impl Iterator<Item = OffsetType>;
fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError>;
fn all_offsets_are_ascending(&self) -> bool;
fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError>;
fn add_to_font(
&self,
font_builder: &mut FontBuilder,
offsets: OffsetArrayAndData,
offset_type: OffsetType,
) -> Result<(), PatchingError>;
}
impl GlyphDataOffsetArray for GlyfAndLoca<'_> {
fn offset_type(&self) -> OffsetType {
match self.loca {
Loca::Short(_) => OffsetType::ShortDivByTwo(Default::default()),
Loca::Long(_) => OffsetType::Long(Default::default()),
}
}
fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
std::iter::once(self.offset_type())
}
fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
self.loca
.get_raw(gid.to_u32() as usize)
.ok_or(PatchingError::InvalidPatch("Start loca entry is missing."))
}
fn all_offsets_are_ascending(&self) -> bool {
self.loca.all_offsets_are_ascending()
}
fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
self.glyf
.get(range)
.ok_or(PatchingError::from(ReadError::OutOfBounds))
}
fn add_to_font(
&self,
font_builder: &mut FontBuilder,
offsets: OffsetArrayAndData,
new_offset_type: OffsetType,
) -> Result<(), PatchingError> {
if new_offset_type != self.offset_type() {
return Err(PatchingError::SerializationError(
SerializeErrorFlags::SERIALIZE_ERROR_OFFSET_OVERFLOW,
));
}
font_builder.add_raw(Glyf::TAG, offsets.data);
font_builder.add_raw(Loca::TAG, offsets.offset_array);
Ok(())
}
}
impl GlyphDataOffsetArray for Gvar<'_> {
fn offset_type(&self) -> OffsetType {
if self.flags().contains(GvarFlags::LONG_OFFSETS) {
OffsetType::Long(Default::default())
} else {
OffsetType::ShortDivByTwo(Default::default())
}
}
fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
[
OffsetType::ShortDivByTwo(ShortDivByTwoInfo),
OffsetType::Long(LongInfo),
]
.iter()
.copied()
}
fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
Ok(self
.glyph_variation_data_offsets()
.get(gid.to_u32() as usize)
.map_err(PatchingError::FontParsingFailed)?
.get())
}
fn all_offsets_are_ascending(&self) -> bool {
let mut prev: Option<u32> = None;
for v in self.glyph_variation_data_offsets().iter() {
let Ok(v) = v else {
return false;
};
let v = v.get();
if let Some(prev_value) = prev {
if prev_value > v {
return false;
}
}
prev = Some(v);
}
true
}
fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
self.glyph_variation_data_for_range(range)
.map_err(PatchingError::FontParsingFailed)
.map(|fd| fd.as_bytes())
}
fn add_to_font(
&self,
font_builder: &mut FontBuilder,
offsets: OffsetArrayAndData,
new_offset_type: OffsetType,
) -> Result<(), PatchingError> {
const GVAR_FLAGS_OFFSET: usize = 15;
let orig_bytes = self.as_bytes();
let orig_size = orig_bytes.len();
let original_offsets_range = self.glyph_variation_data_offsets_byte_range();
if new_offset_type == self.offset_type()
&& offsets.offset_array.len()
!= original_offsets_range.end - original_offsets_range.start
{
return Err(PatchingError::InternalError);
}
let part1_header_pre_flag = orig_bytes
.get(0..GVAR_FLAGS_OFFSET)
.ok_or(PatchingError::InternalError)?;
let part1_header_post_flag = orig_bytes
.get(GVAR_FLAGS_OFFSET + 1..original_offsets_range.start)
.ok_or(PatchingError::InternalError)?;
let mut flags: u8 = orig_bytes
.get(GVAR_FLAGS_OFFSET)
.copied()
.ok_or(PatchingError::InternalError)?;
if new_offset_type.offset_width() == 4 {
flags |= 0b00000001;
} else {
flags &= 0b11111110;
}
let max_new_size = orig_size + offsets.data.len();
let mut serializer = Serializer::new(max_new_size);
serializer
.start_serialize()
.and(serializer.embed_bytes(part1_header_pre_flag))
.and(serializer.embed(flags))
.and(serializer.embed_bytes(part1_header_post_flag))
.and(serializer.embed_bytes(&offsets.offset_array))
.map_err(PatchingError::from)?;
serializer
.push()
.and(serializer.embed_bytes(&offsets.data))
.map_err(PatchingError::from)?;
let glyph_data_obj = serializer
.pop_pack(false)
.ok_or_else(|| PatchingError::SerializationError(serializer.error()))?;
let shared_tuples = self.shared_tuples().map_err(PatchingError::from)?;
let shared_tuples_bytes = shared_tuples
.offset_data()
.as_bytes()
.get(shared_tuples.tuples_byte_range())
.ok_or_else(|| PatchingError::SerializationError(serializer.error()))?;
let shared_tuples_obj = if !shared_tuples_bytes.is_empty() {
serializer
.push()
.and(serializer.embed_bytes(shared_tuples_bytes))
.map_err(PatchingError::from)?;
serializer.add_virtual_link(glyph_data_obj)?;
serializer
.pop_pack(false)
.ok_or_else(|| PatchingError::SerializationError(serializer.error()))?
} else {
glyph_data_obj
};
serializer
.add_link(
self.shared_tuples_offset_byte_range(),
shared_tuples_obj,
OffsetWhence::Head,
0,
false,
)
.and(serializer.add_link(
self.glyph_variation_data_array_offset_byte_range(),
glyph_data_obj,
OffsetWhence::Head,
0,
false,
))
.map_err(PatchingError::from)?;
serializer.end_serialize();
let new_gvar = serializer.copy_bytes();
font_builder.add_raw(Gvar::TAG, new_gvar);
Ok(())
}
}
impl GlyphDataOffsetArray for CFFAndCharStrings<'_> {
fn offset_type(&self) -> OffsetType {
self.offset_type
}
fn available_offset_types(&self) -> impl Iterator<Item = OffsetType> {
[
OffsetType::CffOne(CffOneInfo),
OffsetType::CffTwo(CffTwoInfo),
OffsetType::CffThree(CffThreeInfo),
OffsetType::CffFour(CffFourInfo),
]
.iter()
.copied()
}
fn offset_for(&self, gid: GlyphId) -> Result<u32, PatchingError> {
self.charstrings
.get_offset(gid.to_u32() as usize)
.map_err(|_| PatchingError::FontParsingFailed(ReadError::OutOfBounds))
.map(|offset| offset as u32)
}
fn all_offsets_are_ascending(&self) -> bool {
let it1 = (0..self.charstrings.count()).map(|index| self.offset_for(GlyphId::new(index)));
let it2 = it1.clone().skip(1);
!it1.zip(it2).any(|(start, end)| {
let (Ok(start), Ok(end)) = (start, end) else {
return true;
};
start > end
})
}
fn get(&self, range: Range<usize>) -> Result<&[u8], PatchingError> {
self.charstrings_object_data
.get(range)
.ok_or(PatchingError::FontParsingFailed(ReadError::OutOfBounds))
}
fn add_to_font(
&self,
font_builder: &mut FontBuilder,
offsets: OffsetArrayAndData,
offset_type: OffsetType,
) -> Result<(), PatchingError> {
let (count_width, table_tag) = match &self.charstrings {
Index::Format1(_) => (2, Cff::TAG),
Index::Format2(_) => (4, Cff2::TAG),
Index::Empty => return Err(PatchingError::InternalError),
};
let offset_data: &[u8] = &offsets.offset_array;
let outline_data: &[u8] = &offsets.data;
let max_new_size = self.charstrings_offset + outline_data.len() +
offset_data.len() +
1 + count_width;
let mut serializer = Serializer::new(max_new_size);
let r = serializer.start_serialize().and(
serializer.embed_bytes(
self.cff_data
.get(0..self.charstrings_offset)
.ok_or(PatchingError::FontParsingFailed(ReadError::OutOfBounds))?,
),
);
let r = match &self.charstrings {
Index::Format1(charstrings) => r.and(serializer.embed(charstrings.count())),
Index::Format2(charstrings) => r.and(serializer.embed(charstrings.count())),
Index::Empty => return Err(PatchingError::InternalError),
};
r.and(serializer.embed(offset_type.offset_width() as u8))
.and(serializer.embed_bytes(offset_data))
.and(serializer.embed_bytes(outline_data))
.map_err(PatchingError::SerializationError)?;
serializer.end_serialize();
let new_cff = serializer.copy_bytes();
font_builder.add_raw(table_tag, new_cff);
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::{
collections::{BTreeSet, HashMap},
io::Write,
iter,
};
use brotlic::CompressorWriter;
use read_fonts::{
collections::IntSet,
tables::{
cff2::Cff2,
glyf::Glyf,
gvar::Gvar,
ift::{CompatibilityId, GlyphKeyedPatch, IFTX_TAG, IFT_TAG},
loca::Loca,
},
FontData, FontRead, ReadError, TableProvider, TopLevelTable,
};
use shared_brotli_patch_decoder::BuiltInBrotliDecoder;
use font_test_data::{
bebuffer::BeBuffer,
ift::{
cff_u16_glyph_patches, format2_with_one_charstrings_offset,
glyf_and_gvar_u16_glyph_patches, glyf_u16_glyph_patches, glyf_u16_glyph_patches_2,
glyph_keyed_patch_header, long_gvar_with_shared_tuples, noop_glyf_glyph_patches,
out_of_order_gvar_with_shared_tuples, short_gvar_near_maximum_offset_size,
short_gvar_with_no_shared_tuples, short_gvar_with_shared_tuples, CFF2_FONT,
CFF2_FONT_CHARSTRINGS_OFFSET, CFF_FONT, CFF_FONT_CHARSTRINGS_OFFSET,
},
};
use skrifa::{FontRef, GlyphId, Tag};
use write_fonts::FontBuilder;
use crate::{
font_patch::PatchingError,
glyph_keyed::{apply_glyph_keyed_patches, CffFourInfo, ShortDivByTwoInfo},
patchmap::{PatchId, PatchUrl},
testdata::{test_font_for_patching, test_font_for_patching_with_loca_mod},
};
use super::{CFFAndCharStrings, IftTableTag, LongInfo, OffsetType, PatchInfo};
pub(crate) fn assemble_glyph_keyed_patch(mut header: BeBuffer, payload: BeBuffer) -> BeBuffer {
let payload_data: &[u8] = &payload;
let mut compressor = CompressorWriter::new(Vec::new());
compressor.write_all(payload_data).unwrap();
let compressed = compressor.into_inner().unwrap();
header.write_at("max_uncompressed_length", payload_data.len() as u32);
header.extend(compressed)
}
fn check_tables_equal(a: &FontRef, b: &FontRef, excluding: BTreeSet<Tag>) {
let it_a = a
.table_directory()
.table_records()
.iter()
.map(|r| r.tag())
.filter(|tag| !excluding.contains(tag));
let it_b = b
.table_directory()
.table_records()
.iter()
.map(|r| r.tag())
.filter(|tag| !excluding.contains(tag));
for (tag_a, tag_b) in it_a.zip(it_b) {
assert_eq!(tag_a, tag_b);
let data_a = a.table_data(tag_a).unwrap();
let data_b = b.table_data(tag_b).unwrap();
if tag_a == Tag::new(b"head") {
assert_eq!(data_a.as_bytes()[..8], data_b.as_bytes()[..8]);
assert_eq!(data_a.as_bytes()[12..], data_b.as_bytes()[12..]);
} else {
assert_eq!(data_a.as_bytes(), data_b.as_bytes(), "{}", tag_a);
}
}
}
fn patch_info(tag: Tag, bit_index: usize) -> PatchInfo {
let source = match &tag.to_be_bytes() {
b"IFT " => IftTableTag::Ift(CompatibilityId::from_u32s([0, 0, 0, 0])),
b"IFTX" => IftTableTag::Iftx(CompatibilityId::from_u32s([0, 0, 0, 0])),
_ => panic!("Unexpected tag value."),
};
let mut info = PatchInfo {
url: PatchUrl::expand_template(&[], &PatchId::Numeric(0)).unwrap(),
source_table: source,
application_flag_bit_indices: IntSet::<u32>::empty(),
};
info.application_flag_bit_indices.insert(bit_index as u32);
info
}
#[test]
fn noop_glyph_keyed() {
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), noop_glyf_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
let patch_info = patch_info(IFT_TAG, 4);
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let expected_font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([(IFT_TAG, vec![0b0001_0000, 0, 0, 0].as_slice())]),
);
let expected_font = FontRef::new(&expected_font).unwrap();
check_tables_equal(&expected_font, &patched, BTreeSet::default());
}
#[test]
fn basic_glyph_keyed() {
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
let patch_info = patch_info(IFT_TAG, 28);
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_ift: &[u8] = patched.table_data(IFT_TAG).unwrap().as_bytes();
assert_eq!(&[0, 0, 0, 0b0001_0000], new_ift);
let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
assert_eq!(
&[
1, 2, 3, 4, 5, 0, 6, 7, 8, 0, b'a', b'b', b'c', 0, b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', 0, b'm', b'n', ],
new_glyf
);
let new_loca = patched.loca(None).unwrap();
let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
assert_eq!(
vec![
0, 6, 10, 14, 14, 14, 14, 14, 18, 18, 24, 24, 24, 24, 26, 26, ],
indices
);
check_tables_equal(&font, &patched, [IFT_TAG, Glyf::TAG, Loca::TAG].into());
}
#[test]
fn basic_glyph_keyed_with_long_loca() {
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let font = test_font_for_patching_with_loca_mod(
false, |_| {},
HashMap::from([(IFT_TAG, vec![0, 0, 0, 0].as_slice())]),
);
let font = FontRef::new(&font).unwrap();
let patch_info = patch_info(IFT_TAG, 28);
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_ift: &[u8] = patched.table_data(IFT_TAG).unwrap().as_bytes();
assert_eq!(&[0, 0, 0, 0b0001_0000], new_ift);
let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
assert_eq!(
&[
1, 2, 3, 4, 5, 0, 6, 7, 8, 0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', ],
new_glyf
);
let new_loca = patched.loca(None).unwrap();
let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
assert_eq!(
vec![
0, 6, 10, 13, 13, 13, 13, 13, 17, 17, 22, 22, 22, 22, 24, 24, ],
indices
);
check_tables_equal(&font, &patched, [IFT_TAG, Glyf::TAG, Loca::TAG].into());
}
#[test]
fn multiple_glyph_keyed() {
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch1 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info_1 = patch_info(IFTX_TAG, 13);
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches_2());
let patch: &[u8] = &patch;
let patch2 = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info_2 = patch_info(IFTX_TAG, 28);
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([(IFTX_TAG, vec![0, 0, 0, 0].as_slice())]),
);
let font = FontRef::new(&font).unwrap();
let patched = apply_glyph_keyed_patches(
&[(&patch_info_2, patch2), (&patch_info_1, patch1)],
&font,
&BuiltInBrotliDecoder,
)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_ift: &[u8] = patched.table_data(IFTX_TAG).unwrap().as_bytes();
assert_eq!(&[0, 0b0010_0000, 0, 0b0001_0000], new_ift);
let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
assert_eq!(
&[
1, 2, 3, 4, 5, 0, 6, 7, 8, 0, b'a', b'b', b'c', 0, b'q', b'r', b'h', b'i', b'j', b'k', b'l', 0, b's', b't', b'u', 0, b'm', b'n', b'v', 0, ],
new_glyf
);
let new_loca = patched.loca(None).unwrap();
let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
assert_eq!(
vec![
0, 6, 10, 14, 14, 14, 14, 14, 16, 16, 22, 22, 22, 26, 28, 30, ],
indices
);
check_tables_equal(&font, &patched, [Glyf::TAG, Loca::TAG, IFTX_TAG].into());
}
#[test]
fn glyph_keyed_glyf_and_gvar() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let gvar = short_gvar_with_shared_tuples();
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let mut expected_gvar: Vec<u8> = vec![];
let change_start = gvar.offset_for("glyph_offset[3]");
expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
expected_gvar.extend_from_slice(&[
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06u8, ]);
expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
expected_gvar.extend_from_slice(&[
1, 2, 3, 4, b'm', b'n', b'o', b'p', b'q', 0, b'r', 0u8, ]);
assert_eq!(&expected_gvar, new_gvar);
}
#[test]
fn glyph_keyed_glyf_and_long_gvar() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let gvar = long_gvar_with_shared_tuples();
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let mut expected_gvar: Vec<u8> = vec![];
let change_start = gvar.offset_for("glyph_offset[3]");
expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
expected_gvar.extend_from_slice(&[
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0Au8, ]);
expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
expected_gvar.extend_from_slice(&[
1, 2, 3, 4, b'm', b'n', b'o', b'p', b'q', b'r', ]);
assert_eq!(&expected_gvar, new_gvar);
}
#[test]
fn glyph_keyed_glyf_and_gvar_no_shared_tuples() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let gvar = short_gvar_with_no_shared_tuples();
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let mut expected_gvar: Vec<u8> = vec![];
let change_start = gvar.offset_for("glyph_offset[3]");
expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
expected_gvar.extend_from_slice(&[
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06u8, ]);
expected_gvar.extend_from_slice(&[
1, 2, 3, 4, b'm', b'n', b'o', b'p', b'q', 0, b'r', 0u8, ]);
assert_eq!(&expected_gvar, new_gvar);
}
#[test]
fn glyph_keyed_glyf_and_gvar_overlapping_shared_tuples() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let mut gvar = short_gvar_with_no_shared_tuples();
gvar.write_at("shared_tuple_count", 2u16);
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let mut expected_gvar: Vec<u8> = vec![];
let change_start = gvar.offset_for("glyph_offset[3]");
gvar.write_at(
"glyph_variation_data_offset",
(gvar.offset_for("glyph_0") + 4) as u32,
); expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
expected_gvar.extend_from_slice(&[
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06u8, ]);
expected_gvar.extend_from_slice(&[1, 2, 3, 4u8]); expected_gvar.extend_from_slice(&[
1, 2, 3, 4, b'm', b'n', b'o', b'p', b'q', 0, b'r', 0u8, ]);
assert_eq!(&expected_gvar, new_gvar);
}
#[test]
fn glyph_keyed_out_of_order_gvar() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let gvar = out_of_order_gvar_with_shared_tuples();
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let mut expected_gvar: Vec<u8> = vec![];
let gvar = short_gvar_with_shared_tuples();
let change_start = gvar.offset_for("glyph_offset[3]");
expected_gvar.extend_from_slice(gvar.get(0..change_start).unwrap());
expected_gvar.extend_from_slice(&[
0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06u8, ]);
expected_gvar.extend_from_slice(&[0, 42, 0, 13, 0, 25u8]);
expected_gvar.extend_from_slice(&[
1, 2, 3, 4, b'm', b'n', b'o', b'p', b'q', 0, b'r', 0u8, ]);
assert_eq!(&expected_gvar, new_gvar);
}
#[test]
fn glyph_keyed_gvar_requires_offset_type_switch() {
let patch = assemble_glyph_keyed_patch(
glyph_keyed_patch_header(),
glyf_and_gvar_u16_glyph_patches(),
);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let gvar = short_gvar_near_maximum_offset_size();
let font = test_font_for_patching_with_loca_mod(
true,
|_| {},
HashMap::from([
(Gvar::TAG, gvar.as_slice()),
(Tag::new(b"IFT "), vec![0, 0, 0, 0].as_slice()),
]),
);
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_gvar: &[u8] = patched.table_data(Gvar::TAG).unwrap().as_bytes();
let new_gvar = FontData::new(new_gvar);
let new_gvar = Gvar::read(new_gvar).unwrap();
let gid0_data = vec![1u8; 131066];
assert_eq!(
new_gvar
.data_for_gid(GlyphId::new(0))
.unwrap()
.unwrap()
.as_bytes(),
&gid0_data
);
assert!(new_gvar.data_for_gid(GlyphId::new(1)).unwrap().is_none());
assert_eq!(
new_gvar
.data_for_gid(GlyphId::new(2))
.unwrap()
.unwrap()
.as_bytes(),
b"mn"
);
assert!(new_gvar.data_for_gid(GlyphId::new(6)).unwrap().is_none());
assert_eq!(
new_gvar
.data_for_gid(GlyphId::new(7))
.unwrap()
.unwrap()
.as_bytes(),
b"opq",
);
assert_eq!(
new_gvar
.data_for_gid(GlyphId::new(8))
.unwrap()
.unwrap()
.as_bytes(),
b"r",
);
assert!(new_gvar.data_for_gid(GlyphId::new(9)).unwrap().is_none());
}
#[test]
fn glyph_keyed_bad_format() {
let mut header_builder = glyph_keyed_patch_header();
header_builder.write_at("format", Tag::new(b"iftk"));
let patch = assemble_glyph_keyed_patch(header_builder, glyf_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::InvalidPatch("Patch file tag is not 'ifgk'"))
);
}
#[test]
fn glyph_keyed_unknown_table() {
let mut builder = glyf_and_gvar_u16_glyph_patches();
builder.write_at("gvar_tag", Tag::new(b"hijk"));
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let new_glyf: &[u8] = patched.table_data(Glyf::TAG).unwrap().as_bytes();
assert_eq!(
&[
1, 2, 3, 4, 5, 0, 6, 7, 8, 0, b'a', b'b', b'c', 0, b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', 0, ],
new_glyf
);
let new_loca = patched.loca(None).unwrap();
let indices: Vec<u32> = (0..=15).map(|gid| new_loca.get_raw(gid).unwrap()).collect();
assert_eq!(
vec![
0, 6, 10, 14, 14, 14, 14, 14, 18, 24, 24, 24, 24, 24, 24, 24, ],
indices
);
check_tables_equal(&font, &patched, [Glyf::TAG, Loca::TAG, IFT_TAG].into());
}
#[test]
fn glyph_keyed_unsorted_tables() {
let mut builder = glyf_and_gvar_u16_glyph_patches();
builder.write_at("gvar_tag", Tag::new(b"glye"));
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::InvalidPatch(
"Duplicate or unsorted table tag."
))
);
}
#[test]
fn glyph_keyed_duplicate_tables() {
let mut builder = glyf_and_gvar_u16_glyph_patches();
builder.write_at("gvar_tag", Glyf::TAG);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::InvalidPatch(
"Duplicate or unsorted table tag."
))
);
}
#[test]
fn glyph_keyed_unsorted_gids() {
let mut builder = glyf_u16_glyph_patches();
builder.write_at("gid_8", 6);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::PatchParsingFailed(ReadError::MalformedData(
"Glyph IDs are unsorted or duplicated."
))),
);
}
#[test]
fn glyph_keyed_duplicate_gids() {
let mut builder = glyf_u16_glyph_patches();
builder.write_at("gid_8", 7);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::PatchParsingFailed(ReadError::MalformedData(
"Glyph IDs are unsorted or duplicated."
))),
);
}
#[test]
fn glyph_keyed_uncompressed_length_to_small() {
let len = glyf_u16_glyph_patches().as_slice().len();
let mut patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
patch.write_at("max_uncompressed_length", len as u32 - 1);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::InvalidPatch("Max size exceeded.")),
);
}
#[test]
fn glyph_keyed_max_glyph_exceeded() {
let mut builder = glyf_u16_glyph_patches();
builder.write_at("gid_13", 15u16);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), builder);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching();
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::InvalidPatch(
"Patch would add a glyph beyond this fonts maximum."
)),
);
}
#[test]
fn glyph_keyed_unordered_loca_offsets() {
let patch =
assemble_glyph_keyed_patch(glyph_keyed_patch_header(), glyf_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let font = test_font_for_patching_with_loca_mod(
true,
|loca| {
let loca_gid_1 = loca[1];
let loca_gid_2 = loca[2];
loca[1] = loca_gid_2;
loca[2] = loca_gid_1;
},
Default::default(),
);
let font = FontRef::new(&font).unwrap();
assert_eq!(
apply_glyph_keyed_patches(&[(&patch_info, patch)], &font, &BuiltInBrotliDecoder),
Err(PatchingError::FontParsingFailed(ReadError::MalformedData(
"offset array contains unordered offsets."
))),
);
}
#[test]
fn cff_patching() {
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), cff_u16_glyph_patches());
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let cff_font = FontRef::new(CFF_FONT).unwrap();
let mut font_builder = FontBuilder::new();
font_builder.copy_missing_tables(cff_font);
let mut ift_table = format2_with_one_charstrings_offset();
ift_table.write_at("charstrings_offset", CFF_FONT_CHARSTRINGS_OFFSET);
font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
let cff_font_data = font_builder.build();
let cff_font = FontRef::new(cff_font_data.as_slice()).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff_font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let old_cff = CFFAndCharStrings::from_cff_font(
&cff_font,
CFF_FONT_CHARSTRINGS_OFFSET,
GlyphId::new(59),
)
.unwrap();
let new_cff = CFFAndCharStrings::from_cff_font(
&patched,
CFF_FONT_CHARSTRINGS_OFFSET,
GlyphId::new(59),
)
.unwrap();
assert_eq!(new_cff.charstrings.off_size(), 2);
assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
assert_eq!(
old_cff.charstrings.get(0).unwrap(),
new_cff.charstrings.get(0).unwrap()
);
assert_eq!(
old_cff.charstrings.get(2).unwrap(),
new_cff.charstrings.get(2).unwrap()
);
assert_eq!(
old_cff.charstrings.get(34).unwrap(),
new_cff.charstrings.get(34).unwrap()
);
assert_eq!(
old_cff.charstrings.get(37).unwrap(),
new_cff.charstrings.get(37).unwrap()
);
assert_eq!(
old_cff.charstrings.get(39).unwrap(),
new_cff.charstrings.get(39).unwrap()
);
assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
assert_eq!(b"mn", new_cff.charstrings.get(59).unwrap());
}
#[test]
fn cff_patching_changes_offset_size() {
let patch_buffer = cff_u16_glyph_patches();
let mut patch_buffer = patch_buffer.extend(iter::repeat_n(42u8, 70_000));
patch_buffer.write_at("end_offset", patch_buffer.len() as u32);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), patch_buffer);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let cff_font = FontRef::new(CFF_FONT).unwrap();
let mut font_builder = FontBuilder::new();
font_builder.copy_missing_tables(cff_font);
let mut ift_table = format2_with_one_charstrings_offset();
ift_table.write_at("charstrings_offset", CFF_FONT_CHARSTRINGS_OFFSET);
font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
let cff_font_data = font_builder.build();
let cff_font = FontRef::new(cff_font_data.as_slice()).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff_font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let old_cff = CFFAndCharStrings::from_cff_font(
&cff_font,
CFF_FONT_CHARSTRINGS_OFFSET,
GlyphId::new(59),
)
.unwrap();
let new_cff = CFFAndCharStrings::from_cff_font(
&patched,
CFF_FONT_CHARSTRINGS_OFFSET, GlyphId::new(59),
)
.unwrap();
assert_eq!(new_cff.charstrings.off_size(), 3);
assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
assert_eq!(
old_cff.charstrings.get(0).unwrap(),
new_cff.charstrings.get(0).unwrap()
);
assert_eq!(
old_cff.charstrings.get(2).unwrap(),
new_cff.charstrings.get(2).unwrap()
);
assert_eq!(
old_cff.charstrings.get(34).unwrap(),
new_cff.charstrings.get(34).unwrap()
);
assert_eq!(
old_cff.charstrings.get(37).unwrap(),
new_cff.charstrings.get(37).unwrap()
);
assert_eq!(
old_cff.charstrings.get(39).unwrap(),
new_cff.charstrings.get(39).unwrap()
);
assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
assert_eq!(
[b'm', b'n', 42, 42, 42],
&new_cff.charstrings.get(59).unwrap()[0..5]
);
assert_eq!(70_002, new_cff.charstrings.get(59).unwrap().len());
}
#[test]
fn cff2_patching() {
let mut patches = cff_u16_glyph_patches();
patches.write_at("tag", Cff2::TAG);
let patch = assemble_glyph_keyed_patch(glyph_keyed_patch_header(), patches);
let patch: &[u8] = &patch;
let patch = GlyphKeyedPatch::read(FontData::new(patch)).unwrap();
let patch_info = patch_info(IFT_TAG, 0);
let cff2_font = FontRef::new(CFF2_FONT).unwrap();
let mut font_builder = FontBuilder::new();
font_builder.copy_missing_tables(cff2_font);
let mut ift_table = format2_with_one_charstrings_offset();
ift_table.write_at("field_flags", 0b00000010u8);
ift_table.write_at("charstrings_offset", CFF2_FONT_CHARSTRINGS_OFFSET);
font_builder.add_raw(Tag::new(b"IFT "), ift_table.data());
let cff2_font_data = font_builder.build();
let cff2_font = FontRef::new(cff2_font_data.as_slice()).unwrap();
let patched =
apply_glyph_keyed_patches(&[(&patch_info, patch)], &cff2_font, &BuiltInBrotliDecoder)
.unwrap();
let patched = FontRef::new(&patched).unwrap();
let old_cff = CFFAndCharStrings::from_cff2_font(
&cff2_font,
CFF2_FONT_CHARSTRINGS_OFFSET,
GlyphId::new(59),
)
.unwrap();
let new_cff = CFFAndCharStrings::from_cff2_font(
&patched,
CFF2_FONT_CHARSTRINGS_OFFSET,
GlyphId::new(59),
)
.unwrap();
assert_eq!(new_cff.charstrings.off_size(), 2);
assert_eq!(old_cff.charstrings.count(), new_cff.charstrings.count());
assert_eq!(
old_cff.charstrings.get(0).unwrap(),
new_cff.charstrings.get(0).unwrap()
);
assert_eq!(
old_cff.charstrings.get(2).unwrap(),
new_cff.charstrings.get(2).unwrap()
);
assert_eq!(
old_cff.charstrings.get(34).unwrap(),
new_cff.charstrings.get(34).unwrap()
);
assert_eq!(
old_cff.charstrings.get(37).unwrap(),
new_cff.charstrings.get(37).unwrap()
);
assert_eq!(
old_cff.charstrings.get(39).unwrap(),
new_cff.charstrings.get(39).unwrap()
);
assert_eq!(b"abc", new_cff.charstrings.get(1).unwrap());
assert_eq!(b"defg", new_cff.charstrings.get(38).unwrap());
assert_eq!(b"hijkl", new_cff.charstrings.get(47).unwrap());
assert_eq!(b"mn", new_cff.charstrings.get(59).unwrap());
}
#[test]
fn max_representable_size() {
assert_eq!(
OffsetType::ShortDivByTwo(ShortDivByTwoInfo).max_representable_size(),
131070
);
assert_eq!(
OffsetType::Long(LongInfo).max_representable_size(),
4_294_967_295
);
assert_eq!(
OffsetType::CffFour(CffFourInfo).max_representable_size(),
4_294_967_294
);
}
}