| /*! |
| A high-level, safe, zero-allocation TrueType font parser. |
| |
| ## Features |
| |
| - A high-level API, for people who doesn't know how TrueType works internally. |
| Basically, no direct access to font tables. |
| - Zero heap allocations. |
| - Zero unsafe. |
| - Zero dependencies. |
| - `no_std`/WASM compatible. |
| - Fast. |
| - Stateless. All parsing methods are immutable methods. |
| - Simple and maintainable code (no magic numbers). |
| |
| ## Safety |
| |
| - The library must not panic. Any panic considered as a critical bug and should be reported. |
| - The library forbids the unsafe code. |
| - No heap allocations, so crash due to OOM is not possible. |
| - All recursive methods have a depth limit. |
| - Technically, should use less than 64KiB of stack in worst case scenario. |
| - Most of arithmetic operations are checked. |
| - Most of numeric casts are checked. |
| */ |
| |
| #![doc(html_root_url = "https://docs.rs/ttf-parser/0.8.2")] |
| |
| #![no_std] |
| #![forbid(unsafe_code)] |
| #![warn(missing_docs)] |
| #![warn(missing_copy_implementations)] |
| #![warn(missing_debug_implementations)] |
| |
| #[cfg(feature = "std")] |
| #[macro_use] |
| extern crate std; |
| |
| use core::fmt; |
| use core::num::NonZeroU16; |
| |
| macro_rules! try_opt_or { |
| ($value:expr, $ret:expr) => { |
| match $value { |
| Some(v) => v, |
| None => return $ret, |
| } |
| }; |
| } |
| |
| mod ggg; |
| mod parser; |
| mod tables; |
| mod var_store; |
| |
| #[cfg(feature = "std")] |
| mod writer; |
| |
| use tables::*; |
| use parser::{Stream, FromData, NumFrom, TryNumFrom, LazyArray16, Offset32, Offset}; |
| use parser::{i16_bound, f32_bound}; |
| use head::IndexToLocationFormat; |
| pub use fvar::{VariationAxes, VariationAxis}; |
| pub use gdef::GlyphClass; |
| pub use ggg::*; |
| pub use name::*; |
| pub use os2::*; |
| pub use tables::{cmap, kern}; |
| |
| |
| /// A type-safe wrapper for glyph ID. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default, Debug)] |
| pub struct GlyphId(pub u16); |
| |
| impl FromData for GlyphId { |
| const SIZE: usize = 2; |
| |
| #[inline] |
| fn parse(data: &[u8]) -> Option<Self> { |
| u16::parse(data).map(GlyphId) |
| } |
| } |
| |
| |
| /// A TrueType font magic. |
| /// |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| enum Magic { |
| TrueType, |
| OpenType, |
| FontCollection, |
| } |
| |
| impl FromData for Magic { |
| const SIZE: usize = 4; |
| |
| #[inline] |
| fn parse(data: &[u8]) -> Option<Self> { |
| match u32::parse(data)? { |
| 0x00010000 | 0x74727565 => Some(Magic::TrueType), |
| 0x4F54544F => Some(Magic::OpenType), |
| 0x74746366 => Some(Magic::FontCollection), |
| _ => None, |
| } |
| } |
| } |
| |
| |
| /// A variation coordinate in a normalized coordinate system. |
| /// |
| /// Basically any number in a -1.0..1.0 range. |
| /// Where 0 is a default value. |
| /// |
| /// The number is stored as f2.16 |
| #[repr(transparent)] |
| #[derive(Clone, Copy, PartialEq, Default, Debug)] |
| pub struct NormalizedCoordinate(i16); |
| |
| impl From<i16> for NormalizedCoordinate { |
| /// Creates a new coordinate. |
| /// |
| /// The provided number will be clamped to the -16384..16384 range. |
| #[inline] |
| fn from(n: i16) -> Self { |
| NormalizedCoordinate(i16_bound(-16384, n, 16384)) |
| } |
| } |
| |
| impl From<f32> for NormalizedCoordinate { |
| /// Creates a new coordinate. |
| /// |
| /// The provided number will be clamped to the -1.0..1.0 range. |
| #[inline] |
| fn from(n: f32) -> Self { |
| NormalizedCoordinate((f32_bound(-1.0, n, 1.0) * 16384.0) as i16) |
| } |
| } |
| |
| impl NormalizedCoordinate { |
| /// Returns the coordinate value as f2.14. |
| #[inline] |
| pub fn get(self) -> i16 { |
| self.0 |
| } |
| } |
| |
| |
| /// A font variation value. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use ttf_parser::{Variation, Tag}; |
| /// |
| /// Variation { axis: Tag::from_bytes(b"wght"), value: 500.0 }; |
| /// ``` |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub struct Variation { |
| /// An axis tag name. |
| pub axis: Tag, |
| /// An axis value. |
| pub value: f32, |
| } |
| |
| |
| /// A 4-byte tag. |
| #[repr(transparent)] |
| #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct Tag(pub u32); |
| |
| impl Tag { |
| /// Creates a `Tag` from bytes. |
| #[inline] |
| pub const fn from_bytes(bytes: &[u8; 4]) -> Self { |
| Tag(((bytes[0] as u32) << 24) | ((bytes[1] as u32) << 16) | |
| ((bytes[2] as u32) << 8) | (bytes[3] as u32)) |
| } |
| |
| /// Creates a `Tag` from bytes. |
| /// |
| /// In case of empty data will return `Tag` set to 0. |
| /// |
| /// When `bytes` are shorter than 4, will set missing bytes to ` `. |
| /// |
| /// Data after first 4 bytes is ignored. |
| #[inline] |
| pub fn from_bytes_lossy(bytes: &[u8]) -> Self { |
| if bytes.is_empty() { |
| return Tag::from_bytes(&[0, 0, 0, 0]); |
| } |
| |
| let mut iter = bytes.iter().cloned().chain(core::iter::repeat(b' ')); |
| Tag::from_bytes(&[ |
| iter.next().unwrap(), |
| iter.next().unwrap(), |
| iter.next().unwrap(), |
| iter.next().unwrap(), |
| ]) |
| } |
| |
| /// Returns tag as 4-element byte array. |
| #[inline] |
| pub const fn to_bytes(self) -> [u8; 4] { |
| [ |
| (self.0 >> 24 & 0xff) as u8, |
| (self.0 >> 16 & 0xff) as u8, |
| (self.0 >> 8 & 0xff) as u8, |
| (self.0 >> 0 & 0xff) as u8, |
| ] |
| } |
| |
| /// Returns tag as 4-element byte array. |
| #[inline] |
| pub const fn to_chars(self) -> [char; 4] { |
| [ |
| (self.0 >> 24 & 0xff) as u8 as char, |
| (self.0 >> 16 & 0xff) as u8 as char, |
| (self.0 >> 8 & 0xff) as u8 as char, |
| (self.0 >> 0 & 0xff) as u8 as char, |
| ] |
| } |
| |
| /// Checks if tag is null / `[0, 0, 0, 0]`. |
| #[inline] |
| pub const fn is_null(&self) -> bool { |
| self.0 == 0 |
| } |
| |
| /// Returns tag value as `u32` number. |
| #[inline] |
| pub const fn as_u32(&self) -> u32 { |
| self.0 |
| } |
| |
| /// Converts tag to lowercase. |
| #[inline] |
| pub fn to_lowercase(&self) -> Self { |
| let b = self.to_bytes(); |
| Tag::from_bytes(&[ |
| b[0].to_ascii_lowercase(), |
| b[1].to_ascii_lowercase(), |
| b[2].to_ascii_lowercase(), |
| b[3].to_ascii_lowercase(), |
| ]) |
| } |
| |
| /// Converts tag to uppercase. |
| #[inline] |
| pub fn to_uppercase(&self) -> Self { |
| let b = self.to_bytes(); |
| Tag::from_bytes(&[ |
| b[0].to_ascii_uppercase(), |
| b[1].to_ascii_uppercase(), |
| b[2].to_ascii_uppercase(), |
| b[3].to_ascii_uppercase(), |
| ]) |
| } |
| } |
| |
| impl core::fmt::Debug for Tag { |
| #[inline] |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| write!(f, "Tag({})", self) |
| } |
| } |
| |
| impl core::fmt::Display for Tag { |
| #[inline] |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| let b = self.to_chars(); |
| write!( |
| f, |
| "{}{}{}{}", |
| b.get(0).unwrap_or(&' '), |
| b.get(1).unwrap_or(&' '), |
| b.get(2).unwrap_or(&' '), |
| b.get(3).unwrap_or(&' ') |
| ) |
| } |
| } |
| |
| impl FromData for Tag { |
| const SIZE: usize = 4; |
| |
| #[inline] |
| fn parse(data: &[u8]) -> Option<Self> { |
| u32::parse(data).map(Tag) |
| } |
| } |
| |
| |
| |
| /// A line metrics. |
| /// |
| /// Used for underline and strikeout. |
| #[repr(C)] |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub struct LineMetrics { |
| /// Line position. |
| pub position: i16, |
| |
| /// Line thickness. |
| pub thickness: i16, |
| } |
| |
| |
| /// A rectangle. |
| #[repr(C)] |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| #[allow(missing_docs)] |
| pub struct Rect { |
| pub x_min: i16, |
| pub y_min: i16, |
| pub x_max: i16, |
| pub y_max: i16, |
| } |
| |
| impl Rect { |
| /// Returns rect's width. |
| #[inline] |
| pub fn width(&self) -> i16 { |
| self.x_max - self.x_min |
| } |
| |
| /// Returns rect's height. |
| #[inline] |
| pub fn height(&self) -> i16 { |
| self.y_max - self.y_min |
| } |
| } |
| |
| |
| #[derive(Clone, Copy, Debug)] |
| pub(crate) struct BBox { |
| x_min: f32, |
| y_min: f32, |
| x_max: f32, |
| y_max: f32, |
| } |
| |
| impl BBox { |
| #[inline] |
| fn new() -> Self { |
| BBox { |
| x_min: core::f32::MAX, |
| y_min: core::f32::MAX, |
| x_max: core::f32::MIN, |
| y_max: core::f32::MIN, |
| } |
| } |
| |
| #[inline] |
| fn is_default(&self) -> bool { |
| self.x_min == core::f32::MAX && |
| self.y_min == core::f32::MAX && |
| self.x_max == core::f32::MIN && |
| self.y_max == core::f32::MIN |
| } |
| |
| #[inline] |
| fn extend_by(&mut self, x: f32, y: f32) { |
| self.x_min = self.x_min.min(x); |
| self.y_min = self.y_min.min(y); |
| self.x_max = self.x_max.max(x); |
| self.y_max = self.y_max.max(y); |
| } |
| |
| #[inline] |
| fn to_rect(&self) -> Option<Rect> { |
| Some(Rect { |
| x_min: i16::try_num_from(self.x_min)?, |
| y_min: i16::try_num_from(self.y_min)?, |
| x_max: i16::try_num_from(self.x_max)?, |
| y_max: i16::try_num_from(self.y_max)?, |
| }) |
| } |
| } |
| |
| |
| /// A trait for glyph outline construction. |
| pub trait OutlineBuilder { |
| /// Appends a MoveTo segment. |
| /// |
| /// Start of a contour. |
| fn move_to(&mut self, x: f32, y: f32); |
| |
| /// Appends a LineTo segment. |
| fn line_to(&mut self, x: f32, y: f32); |
| |
| /// Appends a QuadTo segment. |
| fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32); |
| |
| /// Appends a CurveTo segment. |
| fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32); |
| |
| /// Appends a ClosePath segment. |
| /// |
| /// End of a contour. |
| fn close(&mut self); |
| } |
| |
| |
| struct DummyOutline; |
| impl OutlineBuilder for DummyOutline { |
| 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) {} |
| } |
| |
| |
| /// A glyph raster image format. |
| #[allow(missing_docs)] |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub enum RasterImageFormat { |
| PNG, |
| } |
| |
| |
| /// A glyph's raster image. |
| /// |
| /// Note, that glyph metrics are in pixels and not in font units. |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub struct RasterGlyphImage<'a> { |
| /// Horizontal offset. |
| pub x: i16, |
| |
| /// Vertical offset. |
| pub y: i16, |
| |
| /// Image width. |
| /// |
| /// It doesn't guarantee that this value is the same as set in the `data`. |
| pub width: u16, |
| |
| /// Image height. |
| /// |
| /// It doesn't guarantee that this value is the same as set in the `data`. |
| pub height: u16, |
| |
| /// A pixels per em of the selected strike. |
| pub pixels_per_em: u16, |
| |
| /// An image format. |
| pub format: RasterImageFormat, |
| |
| /// A raw image data. It's up to the caller to decode it. |
| pub data: &'a [u8], |
| } |
| |
| |
| /// A table name. |
| #[repr(C)] |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| #[allow(missing_docs)] |
| pub enum TableName { |
| AxisVariations = 0, |
| CharacterToGlyphIndexMapping, |
| ColorBitmapData, |
| ColorBitmapLocation, |
| CompactFontFormat, |
| CompactFontFormat2, |
| FontVariations, |
| GlyphData, |
| GlyphDefinition, |
| GlyphVariations, |
| Header, |
| HorizontalHeader, |
| HorizontalMetrics, |
| HorizontalMetricsVariations, |
| IndexToLocation, |
| Kerning, |
| MaximumProfile, |
| MetricsVariations, |
| Naming, |
| PostScript, |
| ScalableVectorGraphics, |
| StandardBitmapGraphics, |
| VerticalHeader, |
| VerticalMetrics, |
| VerticalMetricsVariations, |
| VerticalOrigin, |
| WindowsMetrics, |
| } |
| |
| |
| #[derive(Clone, Copy)] |
| struct TableRecord { |
| table_tag: Tag, |
| #[allow(dead_code)] |
| check_sum: u32, |
| offset: u32, |
| length: u32, |
| } |
| |
| impl FromData for TableRecord { |
| const SIZE: usize = 16; |
| |
| #[inline] |
| fn parse(data: &[u8]) -> Option<Self> { |
| let mut s = Stream::new(data); |
| Some(TableRecord { |
| table_tag: s.read::<Tag>()?, |
| check_sum: s.read::<u32>()?, |
| offset: s.read::<u32>()?, |
| length: s.read::<u32>()?, |
| }) |
| } |
| } |
| |
| |
| const MAX_VAR_COORDS: u8 = 32; |
| |
| #[derive(Clone, Default)] |
| struct VarCoords { |
| data: [NormalizedCoordinate; MAX_VAR_COORDS as usize], |
| len: u8, |
| } |
| |
| impl VarCoords { |
| #[inline] |
| fn as_slice(&self) -> &[NormalizedCoordinate] { |
| &self.data[0..usize::from(self.len)] |
| } |
| |
| #[inline] |
| fn as_mut_slice(&mut self) -> &mut [NormalizedCoordinate] { |
| let end = usize::from(self.len); |
| &mut self.data[0..end] |
| } |
| } |
| |
| |
| /// A list of font face parsing errors. |
| #[derive(Clone, Copy, PartialEq, Debug)] |
| pub enum FaceParsingError { |
| /// An attempt to read out of bounds detected. |
| /// |
| /// Should occur only on malformed fonts. |
| MalformedFont, |
| |
| /// Face data must start with `0x00010000`, `0x74727565`, `0x4F54544F` or `0x74746366`. |
| UnknownMagic, |
| |
| /// The face index is larger than the number of faces in the font. |
| FaceIndexOutOfBounds, |
| |
| /// The `head` table is missing or malformed. |
| NoHeadTable, |
| |
| /// The `hhea` table is missing or malformed. |
| NoHheaTable, |
| |
| /// The `maxp` table is missing or malformed. |
| NoMaxpTable, |
| } |
| |
| impl core::fmt::Display for FaceParsingError { |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| match self { |
| FaceParsingError::MalformedFont => write!(f, "malformed font"), |
| FaceParsingError::UnknownMagic => write!(f, "unknown magic"), |
| FaceParsingError::FaceIndexOutOfBounds => write!(f, "face index is out of bounds"), |
| FaceParsingError::NoHeadTable => write!(f, "the head table is missing or malformed"), |
| FaceParsingError::NoHheaTable => write!(f, "the hhea table is missing or malformed"), |
| FaceParsingError::NoMaxpTable => write!(f, "the maxp table is missing or malformed"), |
| } |
| } |
| } |
| |
| #[cfg(feature = "std")] |
| impl std::error::Error for FaceParsingError {} |
| |
| |
| /// A font face handle. |
| #[derive(Clone)] |
| pub struct Face<'a> { |
| font_data: &'a [u8], // The input data. Used by Face::table_data. |
| table_records: LazyArray16<'a, TableRecord>, |
| avar: Option<avar::Table<'a>>, |
| cbdt: Option<&'a [u8]>, |
| cblc: Option<&'a [u8]>, |
| cff1: Option<cff1::Metadata<'a>>, |
| cff2: Option<cff2::Metadata<'a>>, |
| cmap: Option<cmap::Subtables<'a>>, |
| fvar: Option<fvar::Table<'a>>, |
| gdef: Option<gdef::Table<'a>>, |
| glyf: Option<&'a [u8]>, |
| gvar: Option<gvar::Table<'a>>, |
| head: &'a [u8], |
| hhea: &'a [u8], |
| hmtx: Option<hmtx::Table<'a>>, |
| hvar: Option<hvar::Table<'a>>, |
| kern: Option<kern::Subtables<'a>>, |
| loca: Option<loca::Table<'a>>, |
| mvar: Option<mvar::Table<'a>>, |
| name: Option<name::Names<'a>>, |
| os_2: Option<os2::Table<'a>>, |
| post: Option<post::Table<'a>>, |
| vhea: Option<&'a [u8]>, |
| vmtx: Option<hmtx::Table<'a>>, |
| sbix: Option<&'a [u8]>, |
| svg_: Option<&'a [u8]>, |
| vorg: Option<vorg::Table<'a>>, |
| vvar: Option<hvar::Table<'a>>, |
| number_of_glyphs: NonZeroU16, |
| coordinates: VarCoords, |
| } |
| |
| impl<'a> Face<'a> { |
| /// Creates a new `Face` object from a raw data. |
| /// |
| /// `index` indicates the specific font face in a font collection. |
| /// Use `fonts_in_collection` to get the total number of font faces. |
| /// Set to 0 if unsure. |
| /// |
| /// This method will do some parsing and sanitization, so it's a bit expensive. |
| /// |
| /// Required tables: `head`, `hhea` and `maxp`. |
| /// |
| /// If an optional table has an invalid data it will be skipped. |
| pub fn from_slice(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> { |
| // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font |
| |
| let mut s = Stream::new(data); |
| |
| // Read **font** magic. |
| let magic: Magic = s.read().ok_or(FaceParsingError::UnknownMagic)?; |
| if magic == Magic::FontCollection { |
| s.skip::<u32>(); // version |
| let number_of_faces: u32 = s.read().ok_or(FaceParsingError::MalformedFont)?; |
| let offsets = s.read_array32::<Offset32>(number_of_faces) |
| .ok_or(FaceParsingError::MalformedFont)?; |
| |
| let face_offset = offsets.get(index).ok_or(FaceParsingError::FaceIndexOutOfBounds)?; |
| // Face offset is from the start of the font data, |
| // so we have to adjust it to the current parser offset. |
| let face_offset = face_offset.to_usize().checked_sub(s.offset()) |
| .ok_or(FaceParsingError::MalformedFont)?; |
| s.advance_checked(face_offset).ok_or(FaceParsingError::MalformedFont)?; |
| |
| // Read **face** magic. |
| // Each face in a font collection also starts with a magic. |
| let magic: Magic = s.read().ok_or(FaceParsingError::UnknownMagic)?; |
| // And face in a font collection can't be another collection. |
| if magic == Magic::FontCollection { |
| return Err(FaceParsingError::UnknownMagic); |
| } |
| } |
| |
| let num_tables: u16 = s.read().ok_or(FaceParsingError::MalformedFont)?; |
| s.advance(6); // searchRange (u16) + entrySelector (u16) + rangeShift (u16) |
| let tables = s.read_array16::<TableRecord>(num_tables) |
| .ok_or(FaceParsingError::MalformedFont)?; |
| |
| let mut face = Face { |
| font_data: data, |
| table_records: tables, |
| avar: None, |
| cbdt: None, |
| cblc: None, |
| cff1: None, |
| cff2: None, |
| cmap: None, |
| fvar: None, |
| gdef: None, |
| glyf: None, |
| gvar: None, |
| head: &[], |
| hhea: &[], |
| hmtx: None, |
| hvar: None, |
| kern: None, |
| loca: None, |
| mvar: None, |
| name: None, |
| os_2: None, |
| post: None, |
| vhea: None, |
| vmtx: None, |
| sbix: None, |
| svg_: None, |
| vorg: None, |
| vvar: None, |
| number_of_glyphs: NonZeroU16::new(1).unwrap(), // dummy |
| coordinates: VarCoords::default(), |
| }; |
| |
| let mut number_of_glyphs = None; |
| let mut hmtx = None; |
| let mut vmtx = None; |
| let mut loca = None; |
| |
| for table in tables { |
| let offset = usize::num_from(table.offset); |
| let length = usize::num_from(table.length); |
| let end = offset.checked_add(length).ok_or(FaceParsingError::MalformedFont)?; |
| let range = offset..end; |
| |
| match &table.table_tag.to_bytes() { |
| b"CBDT" => face.cbdt = data.get(range), |
| b"CBLC" => face.cblc = data.get(range), |
| b"CFF " => face.cff1 = data.get(range).and_then(|data| cff1::parse_metadata(data)), |
| b"CFF2" => face.cff2 = data.get(range).and_then(|data| cff2::parse_metadata(data)), |
| b"GDEF" => face.gdef = data.get(range).and_then(|data| gdef::Table::parse(data)), |
| b"HVAR" => face.hvar = data.get(range).and_then(|data| hvar::Table::parse(data)), |
| b"MVAR" => face.mvar = data.get(range).and_then(|data| mvar::Table::parse(data)), |
| b"OS/2" => face.os_2 = data.get(range).and_then(|data| os2::Table::parse(data)), |
| b"SVG " => face.svg_ = data.get(range), |
| b"VORG" => face.vorg = data.get(range).and_then(|data| vorg::Table::parse(data)), |
| b"VVAR" => face.vvar = data.get(range).and_then(|data| hvar::Table::parse(data)), |
| b"avar" => face.avar = data.get(range).and_then(|data| avar::Table::parse(data)), |
| b"cmap" => face.cmap = data.get(range).and_then(|data| cmap::parse(data)), |
| b"fvar" => face.fvar = data.get(range).and_then(|data| fvar::Table::parse(data)), |
| b"glyf" => face.glyf = data.get(range), |
| b"gvar" => face.gvar = data.get(range).and_then(|data| gvar::Table::parse(data)), |
| b"head" => face.head = data.get(range).and_then(|data| head::parse(data)).unwrap_or_default(), |
| b"hhea" => face.hhea = data.get(range).and_then(|data| hhea::parse(data)).unwrap_or_default(), |
| b"hmtx" => hmtx = data.get(range), |
| b"kern" => face.kern = data.get(range).and_then(|data| kern::parse(data)), |
| b"loca" => loca = data.get(range), |
| b"maxp" => number_of_glyphs = data.get(range).and_then(|data| maxp::parse(data)), |
| b"name" => face.name = data.get(range).and_then(|data| name::parse(data)), |
| b"post" => face.post = data.get(range).and_then(|data| post::Table::parse(data)), |
| b"sbix" => face.sbix = data.get(range), |
| b"vhea" => face.vhea = data.get(range).and_then(|data| vhea::parse(data)), |
| b"vmtx" => vmtx = data.get(range), |
| _ => {} |
| } |
| } |
| |
| if face.head.is_empty() { |
| return Err(FaceParsingError::NoHeadTable); |
| } |
| |
| if face.hhea.is_empty() { |
| return Err(FaceParsingError::NoHheaTable); |
| } |
| |
| face.number_of_glyphs = match number_of_glyphs { |
| Some(n) => n, |
| None => return Err(FaceParsingError::NoMaxpTable), |
| }; |
| |
| if let Some(ref fvar) = face.fvar { |
| face.coordinates.len = fvar.axes().count().min(MAX_VAR_COORDS as usize) as u8; |
| } |
| |
| if let Some(data) = hmtx { |
| if let Some(number_of_h_metrics) = hhea::number_of_h_metrics(face.hhea) { |
| face.hmtx = hmtx::Table::parse(data, number_of_h_metrics, face.number_of_glyphs); |
| } |
| } |
| |
| if let (Some(vhea), Some(data)) = (face.vhea, vmtx) { |
| if let Some(number_of_v_metrics) = vhea::num_of_long_ver_metrics(vhea) { |
| face.vmtx = hmtx::Table::parse(data, number_of_v_metrics, face.number_of_glyphs); |
| } |
| } |
| |
| if let Some(data) = loca { |
| if let Some(format) = head::index_to_loc_format(face.head) { |
| face.loca = loca::Table::parse(data, face.number_of_glyphs, format); |
| } |
| } |
| |
| Ok(face) |
| } |
| |
| /// Checks that face has a specified table. |
| /// |
| /// Will return `true` only for tables that were successfully parsed. |
| #[inline] |
| pub fn has_table(&self, name: TableName) -> bool { |
| match name { |
| TableName::Header => true, |
| TableName::HorizontalHeader => true, |
| TableName::MaximumProfile => true, |
| TableName::AxisVariations => self.avar.is_some(), |
| TableName::CharacterToGlyphIndexMapping => self.cmap.is_some(), |
| TableName::ColorBitmapData => self.cbdt.is_some(), |
| TableName::ColorBitmapLocation => self.cblc.is_some(), |
| TableName::CompactFontFormat => self.cff1.is_some(), |
| TableName::CompactFontFormat2 => self.cff2.is_some(), |
| TableName::FontVariations => self.fvar.is_some(), |
| TableName::GlyphData => self.glyf.is_some(), |
| TableName::GlyphDefinition => self.gdef.is_some(), |
| TableName::GlyphVariations => self.gvar.is_some(), |
| TableName::HorizontalMetrics => self.hmtx.is_some(), |
| TableName::HorizontalMetricsVariations => self.hvar.is_some(), |
| TableName::IndexToLocation => self.loca.is_some(), |
| TableName::Kerning => self.kern.is_some(), |
| TableName::MetricsVariations => self.mvar.is_some(), |
| TableName::Naming => self.name.is_some(), |
| TableName::PostScript => self.post.is_some(), |
| TableName::ScalableVectorGraphics => self.svg_.is_some(), |
| TableName::StandardBitmapGraphics => self.sbix.is_some(), |
| TableName::VerticalHeader => self.vhea.is_some(), |
| TableName::VerticalMetrics => self.vmtx.is_some(), |
| TableName::VerticalMetricsVariations => self.vvar.is_some(), |
| TableName::VerticalOrigin => self.vorg.is_some(), |
| TableName::WindowsMetrics => self.os_2.is_some(), |
| } |
| } |
| |
| /// Returns the raw data of a selected table. |
| /// |
| /// Useful if you want to parse the data manually. |
| pub fn table_data(&self, tag: Tag) -> Option<&'a [u8]> { |
| let (_, table) = self.table_records.binary_search_by(|record| record.table_tag.cmp(&tag))?; |
| let offset = usize::num_from(table.offset); |
| let length = usize::num_from(table.length); |
| let end = offset.checked_add(length)?; |
| self.font_data.get(offset..end) |
| } |
| |
| /// Returns an iterator over [Name Records]. |
| /// |
| /// An iterator can be empty. |
| /// |
| /// [Name Records]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records |
| #[inline] |
| pub fn names(&self) -> Names { |
| self.name.unwrap_or_default() |
| } |
| |
| /// Checks that face is marked as *Regular*. |
| /// |
| /// Returns `false` when OS/2 table is not present. |
| #[inline] |
| pub fn is_regular(&self) -> bool { |
| try_opt_or!(self.os_2, false).is_regular() |
| } |
| |
| /// Checks that face is marked as *Italic*. |
| /// |
| /// Returns `false` when OS/2 table is not present. |
| #[inline] |
| pub fn is_italic(&self) -> bool { |
| try_opt_or!(self.os_2, false).is_italic() |
| } |
| |
| /// Checks that face is marked as *Bold*. |
| /// |
| /// Returns `false` when OS/2 table is not present. |
| #[inline] |
| pub fn is_bold(&self) -> bool { |
| try_opt_or!(self.os_2, false).is_bold() |
| } |
| |
| /// Checks that face is marked as *Oblique*. |
| /// |
| /// Returns `false` when OS/2 table is not present or when its version is < 4. |
| #[inline] |
| pub fn is_oblique(&self) -> bool { |
| try_opt_or!(self.os_2, false).is_oblique() |
| } |
| |
| /// Checks that face is marked as *Monospaced*. |
| /// |
| /// Returns `false` when `post` table is not present. |
| #[inline] |
| pub fn is_monospaced(&self) -> bool { |
| try_opt_or!(self.post, false).is_monospaced() |
| } |
| |
| /// Checks that face is variable. |
| /// |
| /// Simply checks the presence of a `fvar` table. |
| #[inline] |
| pub fn is_variable(&self) -> bool { |
| // `fvar::Table::parse` already checked that `axisCount` is non-zero. |
| self.fvar.is_some() |
| } |
| |
| /// Returns face's weight. |
| /// |
| /// Returns `Weight::Normal` when OS/2 table is not present. |
| #[inline] |
| pub fn weight(&self) -> Weight { |
| try_opt_or!(self.os_2, Weight::default()).weight() |
| } |
| |
| /// Returns face's width. |
| /// |
| /// Returns `Width::Normal` when OS/2 table is not present or when value is invalid. |
| #[inline] |
| pub fn width(&self) -> Width { |
| try_opt_or!(self.os_2, Width::default()).width() |
| } |
| |
| /// Returns face's italic angle. |
| /// |
| /// Returns `None` when `post` table is not present. |
| #[inline] |
| pub fn italic_angle(&self) -> Option<f32> { |
| self.post.map(|table| table.italic_angle()) |
| } |
| |
| #[inline] |
| fn use_typo_metrics(&self) -> Option<os2::Table> { |
| self.os_2.filter(|table| table.is_use_typo_metrics()) |
| } |
| |
| /// Returns a horizontal face ascender. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn ascender(&self) -> i16 { |
| if let Some(os_2) = self.use_typo_metrics() { |
| let v = os_2.typo_ascender(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hasc"), v) |
| } else { |
| hhea::ascender(self.hhea) |
| } |
| } |
| |
| /// Returns a horizontal face descender. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn descender(&self) -> i16 { |
| if let Some(os_2) = self.use_typo_metrics() { |
| let v = os_2.typo_descender(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), v) |
| } else { |
| hhea::descender(self.hhea) |
| } |
| } |
| |
| /// Returns face's height. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn height(&self) -> i16 { |
| self.ascender() - self.descender() |
| } |
| |
| /// Returns a horizontal face line gap. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn line_gap(&self) -> i16 { |
| if let Some(os_2) = self.use_typo_metrics() { |
| let v = os_2.typo_line_gap(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), v) |
| } else { |
| hhea::line_gap(self.hhea) |
| } |
| } |
| |
| /// Returns a horizontal typographic face ascender. |
| /// |
| /// Prefer `Face::ascender` unless you explicitly want this. This is a more |
| /// low-level alternative. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn typographic_ascender(&self) -> Option<i16> { |
| self.os_2.map(|table| { |
| let v = table.typo_ascender(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hasc"), v) |
| }) |
| } |
| |
| /// Returns a horizontal typographic face descender. |
| /// |
| /// Prefer `Face::descender` unless you explicitly want this. This is a more |
| /// low-level alternative. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn typographic_descender(&self) -> Option<i16> { |
| self.os_2.map(|table| { |
| let v = table.typo_descender(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), v) |
| }) |
| } |
| |
| /// Returns a horizontal typographic face line gap. |
| /// |
| /// Prefer `Face::line_gap` unless you explicitly want this. This is a more |
| /// low-level alternative. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn typographic_line_gap(&self) -> Option<i16> { |
| self.os_2.map(|table| { |
| let v = table.typo_line_gap(); |
| self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), v) |
| }) |
| } |
| |
| // TODO: does this affected by USE_TYPO_METRICS? |
| |
| /// Returns a vertical face ascender. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn vertical_ascender(&self) -> Option<i16> { |
| self.vhea.map(vhea::ascender) |
| .map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vasc"), v)) |
| } |
| |
| /// Returns a vertical face descender. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn vertical_descender(&self) -> Option<i16> { |
| self.vhea.map(vhea::descender) |
| .map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vdsc"), v)) |
| } |
| |
| /// Returns a vertical face height. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn vertical_height(&self) -> Option<i16> { |
| Some(self.vertical_ascender()? - self.vertical_descender()?) |
| } |
| |
| /// Returns a vertical face line gap. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn vertical_line_gap(&self) -> Option<i16> { |
| self.vhea.map(vhea::line_gap) |
| .map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vlgp"), v)) |
| } |
| |
| /// Returns face's units per EM. |
| /// |
| /// Returns `None` when value is not in a 16..=16384 range. |
| #[inline] |
| pub fn units_per_em(&self) -> Option<u16> { |
| head::units_per_em(self.head) |
| } |
| |
| /// Returns face's x height. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present or when its version is < 2. |
| #[inline] |
| pub fn x_height(&self) -> Option<i16> { |
| self.os_2.and_then(|os_2| os_2.x_height()) |
| .map(|v| self.apply_metrics_variation(Tag::from_bytes(b"xhgt"), v)) |
| } |
| |
| /// Returns face's capital height. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present or when its version is < 2. |
| #[inline] |
| pub fn capital_height(&self) -> Option<i16> { |
| self.os_2.and_then(|os_2| os_2.cap_height()) |
| .map(|v| self.apply_metrics_variation(Tag::from_bytes(b"cpht"), v)) |
| } |
| |
| /// Returns face's underline metrics. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when `post` table is not present. |
| #[inline] |
| pub fn underline_metrics(&self) -> Option<LineMetrics> { |
| let mut metrics = self.post?.underline_metrics(); |
| if self.is_variable() { |
| self.apply_metrics_variation_to(Tag::from_bytes(b"undo"), &mut metrics.position); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"unds"), &mut metrics.thickness); |
| } |
| Some(metrics) |
| } |
| |
| /// Returns face's strikeout metrics. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn strikeout_metrics(&self) -> Option<LineMetrics> { |
| let mut metrics = self.os_2?.strikeout_metrics(); |
| if self.is_variable() { |
| self.apply_metrics_variation_to(Tag::from_bytes(b"stro"), &mut metrics.position); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"strs"), &mut metrics.thickness); |
| } |
| Some(metrics) |
| } |
| |
| /// Returns face's subscript metrics. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn subscript_metrics(&self) -> Option<ScriptMetrics> { |
| let mut metrics = self.os_2?.subscript_metrics(); |
| if self.is_variable() { |
| self.apply_metrics_variation_to(Tag::from_bytes(b"sbxs"), &mut metrics.x_size); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"sbys"), &mut metrics.y_size); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"sbxo"), &mut metrics.x_offset); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"sbyo"), &mut metrics.y_offset); |
| } |
| Some(metrics) |
| } |
| |
| /// Returns face's superscript metrics. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when OS/2 table is not present. |
| #[inline] |
| pub fn superscript_metrics(&self) -> Option<ScriptMetrics> { |
| let mut metrics = self.os_2?.superscript_metrics(); |
| if self.is_variable() { |
| self.apply_metrics_variation_to(Tag::from_bytes(b"spxs"), &mut metrics.x_size); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"spys"), &mut metrics.y_size); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"spxo"), &mut metrics.x_offset); |
| self.apply_metrics_variation_to(Tag::from_bytes(b"spyo"), &mut metrics.y_offset); |
| } |
| Some(metrics) |
| } |
| |
| /// Returns a total number of glyphs in the face. |
| /// |
| /// Never zero. |
| /// |
| /// The value was already parsed, so this function doesn't involve any parsing. |
| #[inline] |
| pub fn number_of_glyphs(&self) -> u16 { |
| self.number_of_glyphs.get() |
| } |
| |
| /// Returns an iterator over |
| /// [character to glyph index mapping](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap). |
| /// |
| /// This is a more low-level alternative to `Face::glyph_index`. |
| /// |
| /// An iterator can be empty. |
| #[inline] |
| pub fn character_mapping_subtables(&self) -> cmap::Subtables { |
| self.cmap.unwrap_or_default() |
| } |
| |
| /// Resolves a Glyph ID for a code point. |
| /// |
| /// Returns `None` instead of `0` when glyph is not found. |
| /// |
| /// All subtable formats except Mixed Coverage (8) are supported. |
| /// |
| /// If you need a more low-level control, prefer `Face::character_mapping_subtables`. |
| #[inline] |
| pub fn glyph_index(&self, c: char) -> Option<GlyphId> { |
| for encoding in self.character_mapping_subtables() { |
| if !encoding.is_unicode() { |
| continue; |
| } |
| |
| if let Some(id) = encoding.glyph_index(u32::from(c)) { |
| return Some(id); |
| } |
| } |
| |
| None |
| } |
| |
| /// Resolves a variation of a Glyph ID from two code points. |
| /// |
| /// Implemented according to |
| /// [Unicode Variation Sequences]( |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-14-unicode-variation-sequences). |
| /// |
| /// Returns `None` instead of `0` when glyph is not found. |
| #[inline] |
| pub fn glyph_variation_index(&self, c: char, variation: char) -> Option<GlyphId> { |
| let res = self.character_mapping_subtables() |
| .find(|e| e.format() == cmap::Format::UnicodeVariationSequences) |
| .and_then(|e| e.glyph_variation_index(c, variation))?; |
| |
| match res { |
| cmap::GlyphVariationResult::Found(v) => Some(v), |
| cmap::GlyphVariationResult::UseDefault => self.glyph_index(c), |
| } |
| } |
| |
| /// Returns glyph's horizontal advance. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn glyph_hor_advance(&self, glyph_id: GlyphId) -> Option<u16> { |
| let mut advance = self.hmtx?.advance(glyph_id)? as f32; |
| |
| if self.is_variable() { |
| // Ignore variation offset when `hvar` is not set. |
| if let Some(hvar_data) = self.hvar { |
| // We can't use `round()` in `no_std`, so this is the next best thing. |
| advance += hvar::glyph_advance_offset(hvar_data, glyph_id, self.coords())? + 0.5; |
| } |
| } |
| |
| u16::try_num_from(advance) |
| } |
| |
| /// Returns glyph's vertical advance. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn glyph_ver_advance(&self, glyph_id: GlyphId) -> Option<u16> { |
| let mut advance = self.vmtx?.advance(glyph_id)? as f32; |
| |
| if self.is_variable() { |
| // Ignore variation offset when `vvar` is not set. |
| if let Some(vvar_data) = self.vvar { |
| // We can't use `round()` in `no_std`, so this is the next best thing. |
| advance += hvar::glyph_advance_offset(vvar_data, glyph_id, self.coords())? + 0.5; |
| } |
| } |
| |
| u16::try_num_from(advance) |
| } |
| |
| /// Returns glyph's horizontal side bearing. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn glyph_hor_side_bearing(&self, glyph_id: GlyphId) -> Option<i16> { |
| let mut bearing = self.hmtx?.side_bearing(glyph_id)? as f32; |
| |
| if self.is_variable() { |
| // Ignore variation offset when `hvar` is not set. |
| if let Some(hvar_data) = self.hvar { |
| // We can't use `round()` in `no_std`, so this is the next best thing. |
| bearing += hvar::glyph_side_bearing_offset(hvar_data, glyph_id, self.coords())? + 0.5; |
| } |
| } |
| |
| i16::try_num_from(bearing) |
| } |
| |
| /// Returns glyph's vertical side bearing. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn glyph_ver_side_bearing(&self, glyph_id: GlyphId) -> Option<i16> { |
| let mut bearing = self.vmtx?.side_bearing(glyph_id)? as f32; |
| |
| if self.is_variable() { |
| // Ignore variation offset when `vvar` is not set. |
| if let Some(vvar_data) = self.vvar { |
| // We can't use `round()` in `no_std`, so this is the next best thing. |
| bearing += hvar::glyph_side_bearing_offset(vvar_data, glyph_id, self.coords())? + 0.5; |
| } |
| } |
| |
| i16::try_num_from(bearing) |
| } |
| |
| /// Returns glyph's vertical origin according to |
| /// [Vertical Origin Table](https://docs.microsoft.com/en-us/typography/opentype/spec/vorg). |
| pub fn glyph_y_origin(&self, glyph_id: GlyphId) -> Option<i16> { |
| self.vorg.map(|vorg| vorg.glyph_y_origin(glyph_id)) |
| } |
| |
| /// Returns glyph's name. |
| /// |
| /// Uses the `post` and `CFF` tables as sources. |
| /// |
| /// Returns `None` when no name is associated with a `glyph`. |
| #[inline] |
| pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&str> { |
| if let Some(name) = self.post.and_then(|post| post.glyph_name(glyph_id)) { |
| return Some(name); |
| } |
| |
| if let Some(name) = self.cff1.as_ref().and_then(|cff1| cff1::glyph_name(cff1, glyph_id)) { |
| return Some(name); |
| } |
| |
| None |
| } |
| |
| /// Checks that face has |
| /// [Glyph Class Definition Table]( |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/gdef#glyph-class-definition-table). |
| pub fn has_glyph_classes(&self) -> bool { |
| try_opt_or!(self.gdef, false).has_glyph_classes() |
| } |
| |
| /// Returns glyph's class according to |
| /// [Glyph Class Definition Table]( |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/gdef#glyph-class-definition-table). |
| /// |
| /// Returns `None` when *Glyph Class Definition Table* is not set |
| /// or glyph class is not set or invalid. |
| pub fn glyph_class(&self, glyph_id: GlyphId) -> Option<GlyphClass> { |
| self.gdef.and_then(|gdef| gdef.glyph_class(glyph_id)) |
| } |
| |
| /// Returns glyph's mark attachment class according to |
| /// [Mark Attachment Class Definition Table]( |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/gdef#mark-attachment-class-definition-table). |
| /// |
| /// All glyphs not assigned to a class fall into Class 0. |
| pub fn glyph_mark_attachment_class(&self, glyph_id: GlyphId) -> Class { |
| try_opt_or!(self.gdef, Class(0)).glyph_mark_attachment_class(glyph_id) |
| } |
| |
| /// Checks that glyph is a mark according to |
| /// [Mark Glyph Sets Table]( |
| /// https://docs.microsoft.com/en-us/typography/opentype/spec/gdef#mark-glyph-sets-table). |
| /// |
| /// `set_index` allows checking a specific glyph coverage set. |
| /// Otherwise all sets will be checked. |
| #[inline] |
| pub fn is_mark_glyph(&self, glyph_id: GlyphId, set_index: Option<u16>) -> bool { |
| try_opt_or!(self.gdef, false).is_mark_glyph(glyph_id, set_index) |
| } |
| |
| /// Returns a iterator over kerning subtables. |
| /// |
| /// Supports both |
| /// [OpenType](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) |
| /// and |
| /// [Apple Advanced Typography](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html) |
| /// variants. |
| pub fn kerning_subtables(&self) -> kern::Subtables { |
| self.kern.unwrap_or_default() |
| } |
| |
| /// Outlines a glyph and returns its tight bounding box. |
| /// |
| /// **Warning**: since `ttf-parser` is a pull parser, |
| /// `OutlineBuilder` will emit segments even when outline is partially malformed. |
| /// You must check `outline_glyph()` result before using |
| /// `OutlineBuilder`'s output. |
| /// |
| /// `glyf`, `gvar`, `CFF` and `CFF2` tables are supported. |
| /// |
| /// This method is affected by variation axes. |
| /// |
| /// Returns `None` when glyph has no outline or on error. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use std::fmt::Write; |
| /// use ttf_parser; |
| /// |
| /// struct Builder(String); |
| /// |
| /// impl ttf_parser::OutlineBuilder for Builder { |
| /// fn move_to(&mut self, x: f32, y: f32) { |
| /// write!(&mut self.0, "M {} {} ", x, y).unwrap(); |
| /// } |
| /// |
| /// fn line_to(&mut self, x: f32, y: f32) { |
| /// write!(&mut self.0, "L {} {} ", x, y).unwrap(); |
| /// } |
| /// |
| /// fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { |
| /// write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap(); |
| /// } |
| /// |
| /// fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { |
| /// write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap(); |
| /// } |
| /// |
| /// fn close(&mut self) { |
| /// write!(&mut self.0, "Z ").unwrap(); |
| /// } |
| /// } |
| /// |
| /// let data = std::fs::read("fonts/SourceSansPro-Regular-Tiny.ttf").unwrap(); |
| /// let face = ttf_parser::Face::from_slice(&data, 0).unwrap(); |
| /// let mut builder = Builder(String::new()); |
| /// let bbox = face.outline_glyph(ttf_parser::GlyphId(13), &mut builder).unwrap(); |
| /// assert_eq!(builder.0, "M 90 0 L 90 656 L 173 656 L 173 71 L 460 71 L 460 0 L 90 0 Z "); |
| /// assert_eq!(bbox, ttf_parser::Rect { x_min: 90, y_min: 0, x_max: 460, y_max: 656 }); |
| /// ``` |
| #[inline] |
| pub fn outline_glyph( |
| &self, |
| glyph_id: GlyphId, |
| builder: &mut dyn OutlineBuilder, |
| ) -> Option<Rect> { |
| if let Some(ref gvar_table) = self.gvar { |
| return gvar::outline(self.loca?, self.glyf?, gvar_table, self.coords(), glyph_id, builder); |
| } |
| |
| if let Some(glyf_table) = self.glyf { |
| return glyf::outline(self.loca?, glyf_table, glyph_id, builder); |
| } |
| |
| if let Some(ref metadata) = self.cff1 { |
| return cff1::outline(metadata, glyph_id, builder); |
| } |
| |
| if let Some(ref metadata) = self.cff2 { |
| return cff2::outline(metadata, self.coords(), glyph_id, builder); |
| } |
| |
| None |
| } |
| |
| /// Returns a tight glyph bounding box. |
| /// |
| /// Unless the current face has a `glyf` table, this is just a shorthand for `outline_glyph()` |
| /// since only the `glyf` table stores a bounding box. In case of CFF and variable fonts |
| /// we have to actually outline a glyph to find it's bounding box. |
| /// |
| /// When a glyph is defined by a raster or a vector image, |
| /// that can be obtained via `glyph_image()`, |
| /// the bounding box must be calculated manually and this method will return `None`. |
| /// |
| /// This method is affected by variation axes. |
| #[inline] |
| pub fn glyph_bounding_box(&self, glyph_id: GlyphId) -> Option<Rect> { |
| if !self.is_variable() { |
| if let Some(glyf_table) = self.glyf { |
| return glyf::glyph_bbox(self.loca?, glyf_table, glyph_id); |
| } |
| } |
| |
| self.outline_glyph(glyph_id, &mut DummyOutline) |
| } |
| |
| /// Returns a bounding box that large enough to enclose any glyph from the face. |
| #[inline] |
| pub fn global_bounding_box(&self) -> Rect { |
| // unwrap is safe, because this method cannot fail. |
| head::global_bbox(self.head).unwrap() |
| } |
| |
| /// Returns a reference to a glyph's raster image. |
| /// |
| /// A font can define a glyph using a raster or a vector image instead of a simple outline. |
| /// Which is primarily used for emojis. This method should be used to access raster images. |
| /// |
| /// `pixels_per_em` allows selecting a preferred image size. The chosen size will |
| /// be closer to an upper one. So when font has 64px and 96px images and `pixels_per_em` |
| /// is set to 72, 96px image will be returned. |
| /// To get the largest image simply use `std::u16::MAX`. |
| /// |
| /// Note that this method will return an encoded image. It should be decoded |
| /// by the caller. We don't validate or preprocess it in any way. |
| /// |
| /// Currently, only PNG images are supported. |
| /// |
| /// Also, a font can contain both: images and outlines. So when this method returns `None` |
| /// you should also try `outline_glyph()` afterwards. |
| /// |
| /// There are multiple ways an image can be stored in a TrueType font |
| /// and this method supports only `sbix`, `CBLC`+`CBDT`. |
| #[inline] |
| pub fn glyph_raster_image(&self, glyph_id: GlyphId, pixels_per_em: u16) -> Option<RasterGlyphImage> { |
| if let Some(sbix_data) = self.sbix { |
| return sbix::parse(sbix_data, self.number_of_glyphs, glyph_id, pixels_per_em, 0); |
| } |
| |
| if let (Some(cblc_data), Some(cbdt_data)) = (self.cblc, self.cbdt) { |
| let location = cblc::find_location(cblc_data, glyph_id, pixels_per_em)?; |
| return cbdt::parse(cbdt_data, location); |
| } |
| |
| None |
| } |
| |
| /// Returns a reference to a glyph's SVG image. |
| /// |
| /// A font can define a glyph using a raster or a vector image instead of a simple outline. |
| /// Which is primarily used for emojis. This method should be used to access SVG images. |
| /// |
| /// Note that this method will return just an SVG data. It should be rendered |
| /// or even decompressed (in case of SVGZ) by the caller. |
| /// We don't validate or preprocess it in any way. |
| /// |
| /// Also, a font can contain both: images and outlines. So when this method returns `None` |
| /// you should also try `outline_glyph()` afterwards. |
| #[inline] |
| pub fn glyph_svg_image(&self, glyph_id: GlyphId) -> Option<&'a [u8]> { |
| self.svg_.and_then(|svg_data| svg::parse(svg_data, glyph_id)) |
| } |
| |
| /// Returns an iterator over variation axes. |
| #[inline] |
| pub fn variation_axes(&self) -> VariationAxes { |
| self.fvar.map(|fvar| fvar.axes()).unwrap_or_default() |
| } |
| |
| /// Sets a variation axis coordinate. |
| /// |
| /// This is the only mutable method in the library. |
| /// We can simplify the API a lot by storing the variable coordinates |
| /// in the face object itself. |
| /// |
| /// Since coordinates are stored on the stack, we allow only 32 of them. |
| /// |
| /// Returns `None` when face is not variable or doesn't have such axis. |
| pub fn set_variation(&mut self, axis: Tag, value: f32) -> Option<()> { |
| if !self.is_variable() { |
| return None; |
| } |
| |
| let v = self.variation_axes().enumerate().find(|(_, a)| a.tag == axis); |
| if let Some((idx, a)) = v { |
| if idx >= usize::from(MAX_VAR_COORDS) { |
| return None; |
| } |
| |
| self.coordinates.data[idx] = a.normalized_value(value); |
| } else { |
| return None; |
| } |
| |
| // TODO: optimize |
| if let Some(avar) = self.avar { |
| // Ignore error. |
| let _ = avar.map_coordinates(self.coordinates.as_mut_slice()); |
| } |
| |
| Some(()) |
| } |
| |
| /// Returns the current normalized variation coordinates. |
| #[inline] |
| pub fn variation_coordinates(&self) -> &[NormalizedCoordinate] { |
| self.coordinates.as_slice() |
| } |
| |
| /// Checks that face has non-default variation coordinates. |
| #[inline] |
| pub fn has_non_default_variation_coordinates(&self) -> bool { |
| self.coordinates.as_slice().iter().any(|c| c.0 != 0) |
| } |
| |
| #[inline] |
| fn metrics_var_offset(&self, tag: Tag) -> f32 { |
| self.mvar.and_then(|table| table.metrics_offset(tag, self.coords())).unwrap_or(0.0) |
| } |
| |
| #[inline] |
| fn apply_metrics_variation(&self, tag: Tag, mut value: i16) -> i16 { |
| self.apply_metrics_variation_to(tag, &mut value); |
| value |
| } |
| |
| #[inline] |
| fn apply_metrics_variation_to(&self, tag: Tag, value: &mut i16) { |
| if self.is_variable() { |
| let v = f32::from(*value) + self.metrics_var_offset(tag); |
| // TODO: Should probably round it, but f32::round is not available in core. |
| if let Some(v) = i16::try_num_from(v) { |
| *value = v; |
| } |
| } |
| } |
| |
| #[inline] |
| fn coords(&self) -> &[NormalizedCoordinate] { |
| self.coordinates.as_slice() |
| } |
| } |
| |
| impl fmt::Debug for Face<'_> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "Face()") |
| } |
| } |
| |
| /// Returns the number of fonts stored in a TrueType font collection. |
| /// |
| /// Returns `None` if a provided data is not a TrueType font collection. |
| #[inline] |
| pub fn fonts_in_collection(data: &[u8]) -> Option<u32> { |
| let mut s = Stream::new(data); |
| if s.read::<Magic>()? != Magic::FontCollection { |
| return None; |
| } |
| |
| s.skip::<u32>(); // version |
| s.read::<u32>() |
| } |
| |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn empty_font() { |
| assert_eq!(Face::from_slice(&[], 0).unwrap_err(), |
| FaceParsingError::UnknownMagic); |
| } |
| |
| #[test] |
| fn zero_tables() { |
| let data = &[ |
| 0x00, 0x01, 0x00, 0x00, // magic |
| 0x00, 0x00, // numTables: 0 |
| 0x00, 0x00, // searchRange: 0 |
| 0x00, 0x00, // entrySelector: 0 |
| 0x00, 0x00, // rangeShift: 0 |
| ]; |
| |
| assert_eq!(Face::from_slice(data, 0).unwrap_err(), |
| FaceParsingError::NoHeadTable); |
| } |
| |
| #[test] |
| fn tables_count_overflow() { |
| let data = &[ |
| 0x00, 0x01, 0x00, 0x00, // magic |
| 0xFF, 0xFF, // numTables: u16::MAX |
| 0x00, 0x00, // searchRange: 0 |
| 0x00, 0x00, // entrySelector: 0 |
| 0x00, 0x00, // rangeShift: 0 |
| ]; |
| |
| assert_eq!(Face::from_slice(data, 0).unwrap_err(), |
| FaceParsingError::MalformedFont); |
| } |
| |
| #[test] |
| fn empty_font_collection() { |
| let data = &[ |
| 0x74, 0x74, 0x63, 0x66, // magic |
| 0x00, 0x00, // majorVersion: 0 |
| 0x00, 0x00, // minorVersion: 0 |
| 0x00, 0x00, 0x00, 0x00, // numFonts: 0 |
| ]; |
| |
| assert_eq!(fonts_in_collection(data), Some(0)); |
| assert_eq!(Face::from_slice(data, 0).unwrap_err(), |
| FaceParsingError::FaceIndexOutOfBounds); |
| } |
| |
| #[test] |
| fn font_collection_num_fonts_overflow() { |
| let data = &[ |
| 0x74, 0x74, 0x63, 0x66, // magic |
| 0x00, 0x00, // majorVersion: 0 |
| 0x00, 0x00, // minorVersion: 0 |
| 0xFF, 0xFF, 0xFF, 0xFF, // numFonts: u32::MAX |
| ]; |
| |
| assert_eq!(fonts_in_collection(data), Some(std::u32::MAX)); |
| assert_eq!(Face::from_slice(data, 0).unwrap_err(), |
| FaceParsingError::MalformedFont); |
| } |
| |
| #[test] |
| fn font_index_overflow() { |
| let data = &[ |
| 0x74, 0x74, 0x63, 0x66, // magic |
| 0x00, 0x00, // majorVersion: 0 |
| 0x00, 0x00, // minorVersion: 0 |
| 0x00, 0x00, 0x00, 0x01, // numFonts: 1 |
| 0x00, 0x00, 0x00, 0x0C, // offset [0]: 12 |
| ]; |
| |
| assert_eq!(fonts_in_collection(data), Some(1)); |
| assert_eq!(Face::from_slice(data, std::u32::MAX).unwrap_err(), |
| FaceParsingError::FaceIndexOutOfBounds); |
| } |
| } |