use crate::{
serialize::{OffsetWhence, Serializer},
Plan, Subset,
SubsetError::{self, SubsetTableError},
SubsetFlags,
};
use write_fonts::{
read::{
tables::name::{Name, NameRecord},
FontRef, TopLevelTable,
},
types::FixedSize,
FontBuilder,
};
impl Subset for Name<'_> {
fn subset(
&self,
plan: &Plan,
_font: &FontRef,
s: &mut Serializer,
_builder: &mut FontBuilder,
) -> Result<(), SubsetError> {
let name_records = self.name_record();
let mut retained_name_record_idxes = name_records
.iter()
.enumerate()
.filter_map(|(idx, record)| {
if !plan.name_ids.contains(record.name_id())
|| !plan.name_languages.contains(record.language_id())
|| (!plan
.subset_flags
.contains(SubsetFlags::SUBSET_FLAGS_NAME_LEGACY)
&& !record.is_unicode())
{
return None;
}
Some(idx)
})
.collect::<Vec<_>>();
retained_name_record_idxes.sort_unstable_by_key(|nr| {
let nr = name_records[*nr];
(
nr.platform_id(),
nr.encoding_id(),
nr.language_id(),
nr.name_id().to_u16(),
nr.length(),
)
});
s.embed(0_u16)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
let count = retained_name_record_idxes.len() as u16;
s.embed(count)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
let storage_offset = count * NameRecord::RAW_BYTE_LEN as u16 + 6;
s.embed(storage_offset)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
serialize_name_records(self, s, &retained_name_record_idxes)
}
}
fn serialize_name_records(
name: &Name,
s: &mut Serializer,
retained_name_record_idxes: &[usize],
) -> Result<(), SubsetError> {
let data = name.offset_data().as_bytes();
let name_records = name.name_record();
let name_records_bytes = data
.get(name.name_record_byte_range())
.ok_or(SubsetError::SubsetTableError(Name::TAG))?;
let storage_start = name.storage_offset() as usize;
for idx in retained_name_record_idxes.iter() {
let len = s.length();
let record_pos = idx * NAME_RECORD_SIZE;
let record_bytes = name_records_bytes
.get(record_pos..record_pos + NAME_RECORD_SIZE)
.ok_or(SubsetError::SubsetTableError(Name::TAG))?;
s.embed_bytes(record_bytes)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
let record = name_records[*idx];
let offset = record.string_offset().to_u32() as usize;
let offset_pos = len + 10;
let str_len = record.length();
if str_len == 0 {
s.copy_assign(offset_pos, 0_u16);
continue;
}
s.push()
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
let str_start = storage_start + offset;
let str_bytes = data
.get(str_start..str_start + str_len as usize)
.ok_or(SubsetTableError(Name::TAG))?;
s.embed_bytes(str_bytes)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
let obj_idx = s
.pop_pack(true)
.ok_or(SubsetError::SubsetTableError(Name::TAG))?;
s.add_link(
offset_pos..offset_pos + 2,
obj_idx,
OffsetWhence::Tail,
0,
false,
)
.map_err(|_| SubsetError::SubsetTableError(Name::TAG))?;
}
Ok(())
}
const NAME_RECORD_SIZE: usize = NameRecord::RAW_BYTE_LEN;
#[cfg(test)]
mod test {
use super::*;
use write_fonts::read::{types::NameId, TableProvider};
use write_fonts::types::Tag;
fn font_with_bad_name_count(font_bytes: &[u8], count: u16) -> Vec<u8> {
let mut bytes = font_bytes.to_vec();
let font = FontRef::new(font_bytes).unwrap();
let record = font
.table_directory()
.table_records()
.iter()
.find(|r| r.tag() == Name::TAG)
.unwrap();
let count_pos = record.offset() as usize + 2;
bytes[count_pos..count_pos + 2].copy_from_slice(&count.to_be_bytes());
bytes
}
#[test]
fn test_subset_name_record_count_out_of_bounds() {
let ttf: &[u8] = include_bytes!("../test-data/fonts/Roboto-Regular.abc.ttf");
let bytes = font_with_bad_name_count(ttf, 0xFFFF);
let font = FontRef::new(&bytes).unwrap();
let name = font.name().unwrap();
let mut builder = FontBuilder::new();
let mut plan = Plan::default();
plan.name_ids.insert(NameId::new(1));
plan.name_languages.insert(0x0409);
let mut s = Serializer::new(1024);
assert_eq!(s.start_serialize(), Ok(()));
let ret = name.subset(&plan, &font, &mut s, &mut builder);
assert!(matches!(
ret,
Err(SubsetError::SubsetTableError(tag)) if tag == Name::TAG
));
}
#[test]
fn test_subset_font_name_record_count_out_of_bounds() {
use write_fonts::read::collections::IntSet;
let ttf: &[u8] = include_bytes!("../test-data/fonts/Roboto-Regular.abc.ttf");
let bytes = font_with_bad_name_count(ttf, 0xFFFF);
let font = FontRef::new(&bytes).unwrap();
let mut unicodes = IntSet::<u32>::empty();
unicodes.insert_range(0x61..=0x63);
let mut name_ids = IntSet::<NameId>::empty();
name_ids.insert_range(NameId::from(0)..=NameId::from(6));
let mut name_languages = IntSet::<u16>::empty();
name_languages.insert(0x0409);
let mut layout_features = IntSet::empty();
layout_features.extend_unsorted(crate::DEFAULT_LAYOUT_FEATURES.iter().copied());
let plan = Plan::new(
&IntSet::empty(),
&unicodes,
&font,
SubsetFlags::SUBSET_FLAGS_DEFAULT,
&IntSet::empty(),
&IntSet::<Tag>::all(),
&layout_features,
&name_ids,
&name_languages,
);
let out = crate::subset_font(&font, &plan).unwrap();
let subset = FontRef::new(&out).unwrap();
assert!(subset.table_data(Name::TAG).is_none());
}
}