diff --git a/src/lib.rs b/src/lib.rs index a128436..dd1fd65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1337,7 +1337,7 @@ impl<'a> Face<'a> { bdat, cbdt, - cff: raw_tables.cff.and_then(cff::Table::parse), + cff: raw_tables.cff.and_then(|data| cff::Table::parse(data, head.units_per_em)), cmap: raw_tables.cmap.and_then(cmap::Table::parse), colr, ebdt, diff --git a/src/tables/cff/cff1.rs b/src/tables/cff/cff1.rs index 428884b..6747a73 100644 --- a/src/tables/cff/cff1.rs +++ b/src/tables/cff/cff1.rs @@ -122,7 +122,7 @@ pub(crate) struct CIDMetadata<'a> { /// An affine transformation matrix. #[allow(missing_docs)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Matrix { pub sx: f32, pub ky: f32, @@ -374,9 +374,12 @@ fn parse_char_string( local_subrs, }; + let transform = (metadata.units_per_em, metadata.matrix()); + let transform = (transform != (1000, Matrix::default())).then_some(transform); let mut inner_builder = Builder { builder, bbox: RectF::new(), + transform, }; let stack = ArgumentsStack { @@ -846,11 +849,15 @@ pub struct Table<'a> { matrix: Matrix, char_strings: Index<'a>, kind: FontKind<'a>, + + // Copy of Face::units_per_em(). + // Required to do glyph outlining, since coordinates must be scaled up by this before applying the `matrix`. + units_per_em: u16, } impl<'a> Table<'a> { /// Parses a table from raw data. - pub fn parse(data: &'a [u8]) -> Option { + pub fn parse(data: &'a [u8], units_per_em: u16) -> Option { let mut s = Stream::new(data); // Parse Header. @@ -930,6 +937,7 @@ impl<'a> Table<'a> { matrix, char_strings, kind, + units_per_em, }) } diff --git a/src/tables/cff/cff2.rs b/src/tables/cff/cff2.rs index acb9b90..68e7a20 100644 --- a/src/tables/cff/cff2.rs +++ b/src/tables/cff/cff2.rs @@ -250,6 +250,7 @@ fn parse_char_string( let mut inner_builder = Builder { builder, bbox: RectF::new(), + transform: None, }; let stack = ArgumentsStack { diff --git a/src/tables/cff/mod.rs b/src/tables/cff/mod.rs index a88738c..9b6c14d 100644 --- a/src/tables/cff/mod.rs +++ b/src/tables/cff/mod.rs @@ -45,23 +45,29 @@ pub enum CFFError { pub(crate) struct Builder<'a> { builder: &'a mut dyn OutlineBuilder, bbox: RectF, + transform: Option<(u16, cff1::Matrix)>, } impl<'a> Builder<'a> { #[inline] fn move_to(&mut self, x: f32, y: f32) { + let (x, y) = self.transform(x, y); self.bbox.extend_by(x, y); self.builder.move_to(x, y); } #[inline] fn line_to(&mut self, x: f32, y: f32) { + let (x, y) = self.transform(x, y); self.bbox.extend_by(x, y); self.builder.line_to(x, y); } #[inline] fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + let (x1, y1) = self.transform(x1, y1); + let (x2, y2) = self.transform(x2, y2); + let (x, y) = self.transform(x, y); self.bbox.extend_by(x1, y1); self.bbox.extend_by(x2, y2); self.bbox.extend_by(x, y); @@ -72,6 +78,21 @@ impl<'a> Builder<'a> { fn close(&mut self) { self.builder.close(); } + + #[inline] + fn transform(&self, x: f32, y: f32) -> (f32, f32) { + let (units_per_em, matrix) = if let Some(transform) = self.transform { + transform + } else { + return (x, y); + }; + let (mut tx, mut ty) = (x, y); + tx = tx * matrix.sx + ty * matrix.kx + matrix.tx; + ty = tx * matrix.ky + ty * matrix.sy + matrix.ty; + tx *= units_per_em as f32; + ty *= units_per_em as f32; + (tx, ty) + } } /// A type-safe wrapper for string ID. diff --git a/tests/tables/cff1.rs b/tests/tables/cff1.rs index 261e1d5..dd59751 100644 --- a/tests/tables/cff1.rs +++ b/tests/tables/cff1.rs @@ -312,7 +312,7 @@ fn unsupported_version() { UInt8(0), // absolute offset ]); - assert!(cff::Table::parse(&data).is_none()); + assert!(cff::Table::parse(&data, 1000).is_none()); } #[test] @@ -360,7 +360,7 @@ fn non_default_header_size() { UInt8(operator::ENDCHAR), ]); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let rect = table.outline(GlyphId(0), &mut builder).unwrap(); @@ -377,7 +377,7 @@ macro_rules! test_cs_with_subrs { #[test] fn $name() { let data = gen_cff($glob, $loc, $values); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let rect = table.outline(GlyphId(0), &mut builder).unwrap(); @@ -398,7 +398,7 @@ macro_rules! test_cs_err { #[test] fn $name() { let data = gen_cff(&[], &[], $values); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), $err); @@ -575,7 +575,7 @@ test_cs!(vv_curve_to_with_x, &[ #[test] fn only_endchar() { let data = gen_cff(&[], &[], &[UInt8(operator::ENDCHAR)]); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); assert!(table.outline(GlyphId(0), &mut builder).is_err()); } @@ -800,7 +800,7 @@ fn endchar_in_subr_with_extra_data_1() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar); @@ -827,7 +827,7 @@ fn endchar_in_subr_with_extra_data_2() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar); @@ -854,7 +854,7 @@ fn subr_without_return() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::DataAfterEndChar); @@ -876,7 +876,7 @@ fn recursive_local_subr() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached); @@ -898,7 +898,7 @@ fn recursive_global_subr() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached); @@ -923,7 +923,7 @@ fn recursive_mixed_subr() { ] ); - let table = cff::Table::parse(&data).unwrap(); + let table = cff::Table::parse(&data, 1000).unwrap(); let mut builder = Builder(String::new()); let res = table.outline(GlyphId(0), &mut builder); assert_eq!(res.unwrap_err(), CFFError::NestingLimitReached); @@ -952,7 +952,7 @@ fn zero_char_string_offset() { UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8), ]); - assert!(cff::Table::parse(&data).is_none()); + assert!(cff::Table::parse(&data, 1000).is_none()); } #[test] @@ -978,7 +978,7 @@ fn invalid_char_string_offset() { UInt8(top_dict_operator::CHAR_STRINGS_OFFSET as u8), ]); - assert!(cff::Table::parse(&data).is_none()); + assert!(cff::Table::parse(&data, 1000).is_none()); } // TODO: return from main