Skip to content

Commit

Permalink
Better testing
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje committed Jun 8, 2022
1 parent e5783ea commit 72f28af
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 92 deletions.
146 changes: 75 additions & 71 deletions src/cff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct CidData<'a> {
/// An FD Select dat structure.
struct FdSelect<'a>(Cow<'a, [u8]>);

/// Data specific to PRivate DICTs.
/// Data specific to Private DICTs.
struct PrivateData<'a> {
dict: Dict<'a>,
subrs: Option<Index<Opaque<'a>>>,
Expand Down Expand Up @@ -92,6 +92,7 @@ pub(crate) fn subset(ctx: &mut Context) -> Result<()> {
for dict in cid.array.iter_mut() {
dict.keep(top::KEEP);
}

for private in &mut cid.private {
private.dict.keep(private::KEEP);
}
Expand All @@ -101,9 +102,11 @@ pub(crate) fn subset(ctx: &mut Context) -> Result<()> {
let mut sub_cff = vec![];
let mut offsets = create_offsets(&table);

// Write twice because we first need to find out the offsets of various data
// structures.
for _ in 0 .. 2 {
insert_offsets(&mut table, &offsets);
let mut w = Writer::new();
insert_offsets(&mut table, &offsets);
write_table(&mut w, &table, &mut offsets);
sub_cff = w.finish();
}
Expand All @@ -116,10 +119,10 @@ pub(crate) fn subset(ctx: &mut Context) -> Result<()> {
/// Subset the glyph descriptions.
fn subset_char_strings<'a>(ctx: &Context, strings: &mut Index<Opaque<'a>>) -> Result<()> {
// The set of all glyphs we will include in the subset.
let subset: HashSet<u16> = ctx.profile.glyphs.iter().copied().collect();
let kept_glyphs: HashSet<u16> = ctx.profile.glyphs.iter().copied().collect();

for glyph in 0 .. ctx.num_glyphs {
if !subset.contains(&glyph) {
if !kept_glyphs.contains(&glyph) {
// The byte sequence [14] is the minimal valid charstring consisting
// of just a single `endchar` operator.
*strings.get_mut(glyph as usize).ok_or(Error::InvalidOffset)? = Opaque(&[14]);
Expand All @@ -131,81 +134,23 @@ fn subset_char_strings<'a>(ctx: &Context, strings: &mut Index<Opaque<'a>>) -> Re

/// Subset CID-related data.
fn subset_font_dicts(ctx: &Context, cid: &mut CidData) -> Result<()> {
// Determine which font dicts to keep.
let mut sub_fds = HashSet::new();
// Determine which subroutine indices to keep.
let mut kept_subrs = HashSet::new();
for &glyph in ctx.profile.glyphs {
sub_fds.insert(*cid.select.0.get(usize::from(glyph)).ok_or(Error::MissingData)?);
kept_subrs
.insert(*cid.select.0.get(usize::from(glyph)).ok_or(Error::MissingData)?);
}

// Remove subroutines for unused Private DICTs.
for (i, dict) in cid.private.iter_mut().enumerate() {
if !sub_fds.contains(&(i as u8)) {
if !kept_subrs.contains(&(i as u8)) {
dict.subrs = None;
}
}

Ok(())
}

/// Create initial zero offsets for all data structures.
fn create_offsets(table: &Table) -> Offsets {
Offsets {
char_strings: 0,
charset: table.charset.as_ref().map(|_| 0),
private: table.private.as_ref().map(create_private_offsets),
cid: table.cid.as_ref().map(create_cid_offsets),
}
}

/// Create initial zero offsets for all CID-related data structures.
fn create_cid_offsets(cid: &CidData) -> CidOffsets {
CidOffsets {
array: 0,
select: 0,
private: cid.private.iter().map(create_private_offsets).collect(),
}
}

/// Create initial zero offsets for a Private DICT.
fn create_private_offsets(private: &PrivateData) -> PrivateOffsets {
PrivateOffsets {
dict: 0 .. 0,
subrs: private.subrs.as_ref().map(|_| 0),
}
}

/// Insert the offsets of various parts of the font into the relevant DICTs.
fn insert_offsets(table: &mut Table, offsets: &Offsets) {
if let Some(offset) = offsets.charset {
table.top.set_offset(top::CHARSET, offset);
}

table.top.set_offset(top::CHAR_STRINGS, offsets.char_strings);

if let (Some(private), Some(offsets)) = (&mut table.private, &offsets.private) {
table.top.set_range(top::PRIVATE, &offsets.dict);

if let Some(offset) = offsets.subrs {
private.dict.set_offset(private::SUBRS, offset);
}
}

if let (Some(cid), Some(offsets)) = (&mut table.cid, &offsets.cid) {
table.top.set_offset(top::FD_ARRAY, offsets.array);
table.top.set_offset(top::FD_SELECT, offsets.select);

for (dict, offsets) in cid.array.iter_mut().zip(&offsets.private) {
dict.set_range(top::PRIVATE, &offsets.dict);
}

for (private, offsets) in cid.private.iter_mut().zip(&offsets.private) {
if let Some(offset) = offsets.subrs {
private.dict.set_offset(private::SUBRS, offset);
}
}
}
}

/// Parse a CFF table.
fn read_table<'a>(ctx: &Context, cff: &'a [u8]) -> Result<Table<'a>> {
// Skip header.
Expand Down Expand Up @@ -312,7 +257,7 @@ fn read_cid_data<'a>(
cff: &'a [u8],
top: &Dict<'a>,
) -> Result<CidData<'a>> {
// Read FD ARRAY.
// Read FD Array.
let array = {
let offset = top.get_offset(top::FD_ARRAY).ok_or(Error::MissingData)?;
Index::<Dict<'a>>::read_at(cff, offset)?
Expand All @@ -325,7 +270,7 @@ fn read_cid_data<'a>(
read_fd_select(sub, ctx.num_glyphs)?
};

// Read CID private dicts.
// Read Private DICTs.
let mut private = vec![];
for dict in array.iter() {
let range = dict.get_range(top::PRIVATE).ok_or(Error::MissingData)?;
Expand All @@ -347,7 +292,7 @@ fn write_cid_data(w: &mut Writer, cid: &CidData, offsets: &mut CidOffsets) {
write_fd_select(w, &cid.select);
w.inspect("FD Select");

// Write Private DICTS.
// Write Private DICTs.
for (private, offsets) in cid.private.iter().zip(&mut offsets.private) {
write_private_data(w, private, offsets);
}
Expand Down Expand Up @@ -427,7 +372,7 @@ fn write_charset(w: &mut Writer, charset: &Charset) {
w.write_ref(&charset.0);
}

/// Returns the font dict index for each glyph.
/// Read the FD Select data structure.
fn read_fd_select(data: &[u8], num_glyphs: u16) -> Result<FdSelect<'_>> {
let mut r = Reader::new(data);
let format = r.read::<u8>()?;
Expand Down Expand Up @@ -457,6 +402,65 @@ fn write_fd_select(w: &mut Writer, select: &FdSelect) {
w.give(&select.0);
}

/// Create initial zero offsets for all data structures.
fn create_offsets(table: &Table) -> Offsets {
Offsets {
char_strings: 0,
charset: table.charset.as_ref().map(|_| 0),
private: table.private.as_ref().map(create_private_offsets),
cid: table.cid.as_ref().map(create_cid_offsets),
}
}

/// Create initial zero offsets for all CID-related data structures.
fn create_cid_offsets(cid: &CidData) -> CidOffsets {
CidOffsets {
array: 0,
select: 0,
private: cid.private.iter().map(create_private_offsets).collect(),
}
}

/// Create initial zero offsets for a Private DICT.
fn create_private_offsets(private: &PrivateData) -> PrivateOffsets {
PrivateOffsets {
dict: 0 .. 0,
subrs: private.subrs.as_ref().map(|_| 0),
}
}

/// Insert the offsets of various parts of the font into the relevant DICTs.
fn insert_offsets(table: &mut Table, offsets: &Offsets) {
if let Some(offset) = offsets.charset {
table.top.set_offset(top::CHARSET, offset);
}

table.top.set_offset(top::CHAR_STRINGS, offsets.char_strings);

if let (Some(private), Some(offsets)) = (&mut table.private, &offsets.private) {
table.top.set_range(top::PRIVATE, &offsets.dict);

if let Some(offset) = offsets.subrs {
private.dict.set_offset(private::SUBRS, offset);
}
}

if let (Some(cid), Some(offsets)) = (&mut table.cid, &offsets.cid) {
table.top.set_offset(top::FD_ARRAY, offsets.array);
table.top.set_offset(top::FD_SELECT, offsets.select);

for (dict, offsets) in cid.array.iter_mut().zip(&offsets.private) {
dict.set_range(top::PRIVATE, &offsets.dict);
}

for (private, offsets) in cid.private.iter_mut().zip(&offsets.private) {
if let Some(offset) = offsets.subrs {
private.dict.set_offset(private::SUBRS, offset);
}
}
}
}

/// An opaque binary data structure.
struct Opaque<'a>(&'a [u8]);

Expand Down
88 changes: 67 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,23 @@ mod tests {
}

fn test(path: &str, text: &str) {
test_impl(path, &text, true);
test_full(path);
}

fn test_full(path: &str) {
let data = std::fs::read(Path::new("fonts").join(path)).unwrap();
let ttf = ttf_parser::Face::from_slice(&data, 0).unwrap();
let mut text = String::new();
for subtable in ttf.tables().cmap.unwrap().subtables {
if subtable.is_unicode() {
subtable.codepoints(|c| text.push(char::try_from(c).unwrap()));
}
}
test_impl(path, &text, false);
}

fn test_impl(path: &str, text: &str, write: bool) {
eprintln!("==============================================");
eprintln!("Testing {path}");

Expand All @@ -474,38 +491,67 @@ mod tests {
let subs = subset(&face, profile).unwrap();
let stem = Path::new(path).file_stem().unwrap().to_str().unwrap();
let out = Path::new("target").join(Path::new(stem)).with_extension("ttf");
std::fs::write(out, &subs).unwrap();

if write {
std::fs::write(out, &subs).unwrap();
}

let ttfs = ttf_parser::Face::from_slice(&subs, 0).unwrap();
let cff = ttfs.tables().cff;
for c in text.chars() {
let id = ttf.glyph_index(c).unwrap();
if let Some(cff) = &cff {
cff.outline(id, &mut Sink).unwrap();
}
let bbox = ttf.glyph_bounding_box(id);

macro_rules! same {
($method:ident, $($args:tt)*) => {
assert_eq!(
ttf.$method($($args)*),
ttfs.$method($($args)*),
);
};
if let Some(cff) = &cff {
if bbox.is_some() {
cff.outline(id, &mut Sink::default()).unwrap();
}
}

same!(glyph_index, c);
same!(glyph_hor_advance, id);
same!(glyph_hor_side_bearing, id);
same!(glyph_bounding_box, id);
let mut sink1 = Sink::default();
let mut sink2 = Sink::default();
ttf.outline_glyph(id, &mut sink1);
ttfs.outline_glyph(id, &mut sink2);
assert_eq!(sink1, sink2);
assert_eq!(ttf.glyph_hor_advance(id), ttfs.glyph_hor_advance(id));
assert_eq!(
ttf.glyph_hor_side_bearing(id),
ttfs.glyph_hor_side_bearing(id)
);
}
}

struct Sink;
#[derive(Debug, Default, PartialEq)]
struct Sink(Vec<Inst>);

#[derive(Debug, PartialEq)]
enum Inst {
MoveTo(f32, f32),
LineTo(f32, f32),
QuadTo(f32, f32, f32, f32),
CurveTo(f32, f32, f32, f32, f32, f32),
Close,
}

impl ttf_parser::OutlineBuilder for Sink {
fn move_to(&mut self, _: f32, _: f32) {}
fn line_to(&mut self, _: f32, _: f32) {}
fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) {}
fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) {}
fn close(&mut self) {}
fn move_to(&mut self, x: f32, y: f32) {
self.0.push(Inst::MoveTo(x, y));
}

fn line_to(&mut self, x: f32, y: f32) {
self.0.push(Inst::LineTo(x, y));
}

fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.push(Inst::QuadTo(x1, y1, x, y));
}

fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.push(Inst::CurveTo(x1, y1, x2, y2, x, y));
}

fn close(&mut self) {
self.0.push(Inst::Close);
}
}
}

0 comments on commit 72f28af

Please sign in to comment.