#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![allow(clippy::get_first)] #![allow(clippy::identity_op)] #![allow(clippy::too_many_arguments)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::upper_case_acronyms)]
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
macro_rules! try_opt_or {
($value:expr, $ret:expr) => {
match $value {
Some(v) => v,
None => return $ret,
}
};
}
#[cfg(feature = "apple-layout")]
mod aat;
#[cfg(feature = "opentype-layout")]
mod ggg;
mod language;
mod parser;
mod tables;
#[cfg(feature = "variable-fonts")]
mod var_store;
use head::IndexToLocationFormat;
pub use parser::{Fixed, FromData, LazyArray16, LazyArray32, LazyArrayIter16, LazyArrayIter32};
use parser::{NumFrom, Offset, Offset32, Stream, TryNumFrom};
#[cfg(feature = "variable-fonts")]
pub use fvar::VariationAxis;
pub use language::Language;
pub use name::{name_id, PlatformId};
pub use os2::{Permissions, ScriptMetrics, Style, UnicodeRanges, Weight, Width};
pub use tables::CFFError;
#[cfg(feature = "apple-layout")]
pub use tables::{ankr, feat, kerx, morx, trak};
#[cfg(feature = "variable-fonts")]
pub use tables::{avar, cff2, fvar, gvar, hvar, mvar};
pub use tables::{cbdt, cblc, cff1 as cff, vhea};
pub use tables::{
cmap, glyf, head, hhea, hmtx, kern, loca, maxp, name, os2, post, sbix, svg, vorg,
};
#[cfg(feature = "opentype-layout")]
pub use tables::{gdef, gpos, gsub, math};
#[cfg(feature = "opentype-layout")]
pub mod opentype_layout {
pub use crate::ggg::*;
}
#[cfg(feature = "apple-layout")]
pub mod apple_layout {
pub use crate::aat::*;
}
#[repr(transparent)]
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default, Debug, Hash)]
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)
}
}
#[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,
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub struct NormalizedCoordinate(i16);
impl From<i16> for NormalizedCoordinate {
#[inline]
fn from(n: i16) -> Self {
NormalizedCoordinate(parser::i16_bound(-16384, n, 16384))
}
}
impl From<f32> for NormalizedCoordinate {
#[inline]
fn from(n: f32) -> Self {
NormalizedCoordinate((parser::f32_bound(-1.0, n, 1.0) * 16384.0) as i16)
}
}
impl NormalizedCoordinate {
#[inline]
pub fn get(self) -> i16 {
self.0
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Variation {
pub axis: Tag,
pub value: f32,
}
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(pub u32);
impl Tag {
#[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))
}
#[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(),
])
}
#[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,
]
}
#[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,
]
}
#[inline]
pub const fn is_null(&self) -> bool {
self.0 == 0
}
#[inline]
pub const fn as_u32(&self) -> u32 {
self.0
}
}
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)
}
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct LineMetrics {
pub position: i16,
pub thickness: i16,
}
#[repr(C)]
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Rect {
pub x_min: i16,
pub y_min: i16,
pub x_max: i16,
pub y_max: i16,
}
impl Rect {
#[inline]
pub fn width(&self) -> i16 {
self.x_max - self.x_min
}
#[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)?,
})
}
}
pub trait OutlineBuilder {
fn move_to(&mut self, x: f32, y: f32);
fn line_to(&mut self, x: f32, y: f32);
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32);
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32);
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) {}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RasterImageFormat {
PNG,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct RasterGlyphImage<'a> {
pub x: i16,
pub y: i16,
pub width: u16,
pub height: u16,
pub pixels_per_em: u16,
pub format: RasterImageFormat,
pub data: &'a [u8],
}
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct TableRecord {
pub tag: Tag,
#[allow(dead_code)]
pub check_sum: u32,
pub offset: u32,
pub 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 {
tag: s.read::<Tag>()?,
check_sum: s.read::<u32>()?,
offset: s.read::<u32>()?,
length: s.read::<u32>()?,
})
}
}
#[cfg(feature = "variable-fonts")]
const MAX_VAR_COORDS: usize = 32;
#[cfg(feature = "variable-fonts")]
#[derive(Clone, Default)]
struct VarCoords {
data: [NormalizedCoordinate; MAX_VAR_COORDS],
len: u8,
}
#[cfg(feature = "variable-fonts")]
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]
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FaceParsingError {
MalformedFont,
UnknownMagic,
FaceIndexOutOfBounds,
NoHeadTable,
NoHheaTable,
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 {}
#[derive(Clone, Copy)]
pub struct RawFace<'a> {
pub data: &'a [u8],
pub table_records: LazyArray16<'a, TableRecord>,
}
impl<'a> RawFace<'a> {
#[deprecated(since = "0.16.0", note = "use `parse` instead")]
pub fn from_slice(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> {
Self::parse(data, index)
}
pub fn parse(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> {
let mut s = Stream::new(data);
let magic = s.read::<Magic>().ok_or(FaceParsingError::UnknownMagic)?;
if magic == Magic::FontCollection {
s.skip::<u32>(); let number_of_faces = s.read::<u32>().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)?;
let face_offset = face_offset
.to_usize()
.checked_sub(s.offset())
.ok_or(FaceParsingError::MalformedFont)?;
s.advance_checked(face_offset)
.ok_or(FaceParsingError::MalformedFont)?;
let magic = s.read::<Magic>().ok_or(FaceParsingError::UnknownMagic)?;
if magic == Magic::FontCollection {
return Err(FaceParsingError::UnknownMagic);
}
} else {
if index != 0 {
return Err(FaceParsingError::FaceIndexOutOfBounds);
}
}
let num_tables = s.read::<u16>().ok_or(FaceParsingError::MalformedFont)?;
s.advance(6); let table_records = s
.read_array16::<TableRecord>(num_tables)
.ok_or(FaceParsingError::MalformedFont)?;
Ok(RawFace {
data,
table_records,
})
}
pub fn table(&self, tag: Tag) -> Option<&'a [u8]> {
let (_, table) = self
.table_records
.binary_search_by(|record| record.tag.cmp(&tag))?;
let offset = usize::num_from(table.offset);
let length = usize::num_from(table.length);
let end = offset.checked_add(length)?;
self.data.get(offset..end)
}
}
impl core::fmt::Debug for RawFace<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "RawFace {{ ... }}")
}
}
#[allow(missing_docs)]
#[allow(missing_debug_implementations)]
#[derive(Clone, Default)]
pub struct RawFaceTables<'a> {
pub head: &'a [u8],
pub hhea: &'a [u8],
pub maxp: &'a [u8],
pub cbdt: Option<&'a [u8]>,
pub cblc: Option<&'a [u8]>,
pub cff: Option<&'a [u8]>,
pub cmap: Option<&'a [u8]>,
pub glyf: Option<&'a [u8]>,
pub hmtx: Option<&'a [u8]>,
pub kern: Option<&'a [u8]>,
pub loca: Option<&'a [u8]>,
pub name: Option<&'a [u8]>,
pub os2: Option<&'a [u8]>,
pub post: Option<&'a [u8]>,
pub sbix: Option<&'a [u8]>,
pub svg: Option<&'a [u8]>,
pub vhea: Option<&'a [u8]>,
pub vmtx: Option<&'a [u8]>,
pub vorg: Option<&'a [u8]>,
#[cfg(feature = "opentype-layout")]
pub gdef: Option<&'a [u8]>,
#[cfg(feature = "opentype-layout")]
pub gpos: Option<&'a [u8]>,
#[cfg(feature = "opentype-layout")]
pub gsub: Option<&'a [u8]>,
#[cfg(feature = "opentype-layout")]
pub math: Option<&'a [u8]>,
#[cfg(feature = "apple-layout")]
pub ankr: Option<&'a [u8]>,
#[cfg(feature = "apple-layout")]
pub feat: Option<&'a [u8]>,
#[cfg(feature = "apple-layout")]
pub kerx: Option<&'a [u8]>,
#[cfg(feature = "apple-layout")]
pub morx: Option<&'a [u8]>,
#[cfg(feature = "apple-layout")]
pub trak: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub avar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub cff2: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub fvar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub gvar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub hvar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub mvar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")]
pub vvar: Option<&'a [u8]>,
}
#[allow(missing_docs)]
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct FaceTables<'a> {
pub head: head::Table,
pub hhea: hhea::Table,
pub maxp: maxp::Table,
pub cbdt: Option<cbdt::Table<'a>>,
pub cff: Option<cff::Table<'a>>,
pub cmap: Option<cmap::Table<'a>>,
pub glyf: Option<glyf::Table<'a>>,
pub hmtx: Option<hmtx::Table<'a>>,
pub kern: Option<kern::Table<'a>>,
pub name: Option<name::Table<'a>>,
pub os2: Option<os2::Table<'a>>,
pub post: Option<post::Table<'a>>,
pub sbix: Option<sbix::Table<'a>>,
pub svg: Option<svg::Table<'a>>,
pub vhea: Option<vhea::Table>,
pub vmtx: Option<hmtx::Table<'a>>,
pub vorg: Option<vorg::Table<'a>>,
#[cfg(feature = "opentype-layout")]
pub gdef: Option<gdef::Table<'a>>,
#[cfg(feature = "opentype-layout")]
pub gpos: Option<opentype_layout::LayoutTable<'a>>,
#[cfg(feature = "opentype-layout")]
pub gsub: Option<opentype_layout::LayoutTable<'a>>,
#[cfg(feature = "opentype-layout")]
pub math: Option<math::Table<'a>>,
#[cfg(feature = "apple-layout")]
pub ankr: Option<ankr::Table<'a>>,
#[cfg(feature = "apple-layout")]
pub feat: Option<feat::Table<'a>>,
#[cfg(feature = "apple-layout")]
pub kerx: Option<kerx::Table<'a>>,
#[cfg(feature = "apple-layout")]
pub morx: Option<morx::Table<'a>>,
#[cfg(feature = "apple-layout")]
pub trak: Option<trak::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub avar: Option<avar::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub cff2: Option<cff2::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub fvar: Option<fvar::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub gvar: Option<gvar::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub hvar: Option<hvar::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub mvar: Option<mvar::Table<'a>>,
#[cfg(feature = "variable-fonts")]
pub vvar: Option<hvar::Table<'a>>,
}
#[derive(Clone)]
pub struct Face<'a> {
raw_face: RawFace<'a>,
tables: FaceTables<'a>, #[cfg(feature = "variable-fonts")]
coordinates: VarCoords,
}
impl<'a> Face<'a> {
#[deprecated(since = "0.16.0", note = "use `parse` instead")]
pub fn from_slice(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> {
Self::parse(data, index)
}
pub fn parse(data: &'a [u8], index: u32) -> Result<Self, FaceParsingError> {
let raw_face = RawFace::parse(data, index)?;
let raw_tables = Self::collect_tables(raw_face);
#[allow(unused_mut)]
let mut face = Face {
raw_face,
#[cfg(feature = "variable-fonts")]
coordinates: VarCoords::default(),
tables: Self::parse_tables(raw_tables)?,
};
#[cfg(feature = "variable-fonts")]
{
if let Some(ref fvar) = face.tables.fvar {
face.coordinates.len = fvar.axes.len().min(MAX_VAR_COORDS as u16) as u8;
}
}
Ok(face)
}
fn collect_tables(raw_face: RawFace<'a>) -> RawFaceTables<'a> {
let mut tables = RawFaceTables::default();
for record in raw_face.table_records {
let start = usize::num_from(record.offset);
let end = match start.checked_add(usize::num_from(record.length)) {
Some(v) => v,
None => continue,
};
let table_data = raw_face.data.get(start..end);
match &record.tag.to_bytes() {
b"CBDT" => tables.cbdt = table_data,
b"CBLC" => tables.cblc = table_data,
b"CFF " => tables.cff = table_data,
#[cfg(feature = "variable-fonts")]
b"CFF2" => tables.cff2 = table_data,
#[cfg(feature = "opentype-layout")]
b"GDEF" => tables.gdef = table_data,
#[cfg(feature = "opentype-layout")]
b"GPOS" => tables.gpos = table_data,
#[cfg(feature = "opentype-layout")]
b"GSUB" => tables.gsub = table_data,
#[cfg(feature = "opentype-layout")]
b"MATH" => tables.math = table_data,
#[cfg(feature = "variable-fonts")]
b"HVAR" => tables.hvar = table_data,
#[cfg(feature = "variable-fonts")]
b"MVAR" => tables.mvar = table_data,
b"OS/2" => tables.os2 = table_data,
b"SVG " => tables.svg = table_data,
b"VORG" => tables.vorg = table_data,
#[cfg(feature = "variable-fonts")]
b"VVAR" => tables.vvar = table_data,
#[cfg(feature = "apple-layout")]
b"ankr" => tables.ankr = table_data,
#[cfg(feature = "variable-fonts")]
b"avar" => tables.avar = table_data,
b"cmap" => tables.cmap = table_data,
#[cfg(feature = "apple-layout")]
b"feat" => tables.feat = table_data,
#[cfg(feature = "variable-fonts")]
b"fvar" => tables.fvar = table_data,
b"glyf" => tables.glyf = table_data,
#[cfg(feature = "variable-fonts")]
b"gvar" => tables.gvar = table_data,
b"head" => tables.head = table_data.unwrap_or_default(),
b"hhea" => tables.hhea = table_data.unwrap_or_default(),
b"hmtx" => tables.hmtx = table_data,
b"kern" => tables.kern = table_data,
#[cfg(feature = "apple-layout")]
b"kerx" => tables.kerx = table_data,
b"loca" => tables.loca = table_data,
b"maxp" => tables.maxp = table_data.unwrap_or_default(),
#[cfg(feature = "apple-layout")]
b"morx" => tables.morx = table_data,
b"name" => tables.name = table_data,
b"post" => tables.post = table_data,
b"sbix" => tables.sbix = table_data,
#[cfg(feature = "apple-layout")]
b"trak" => tables.trak = table_data,
b"vhea" => tables.vhea = table_data,
b"vmtx" => tables.vmtx = table_data,
_ => {}
}
}
tables
}
pub fn from_raw_tables(raw_tables: RawFaceTables<'a>) -> Result<Self, FaceParsingError> {
#[allow(unused_mut)]
let mut face = Face {
raw_face: RawFace {
data: &[],
table_records: LazyArray16::default(),
},
#[cfg(feature = "variable-fonts")]
coordinates: VarCoords::default(),
tables: Self::parse_tables(raw_tables)?,
};
#[cfg(feature = "variable-fonts")]
{
if let Some(ref fvar) = face.tables.fvar {
face.coordinates.len = fvar.axes.len().min(MAX_VAR_COORDS as u16) as u8;
}
}
Ok(face)
}
fn parse_tables(raw_tables: RawFaceTables<'a>) -> Result<FaceTables<'a>, FaceParsingError> {
let head = head::Table::parse(raw_tables.head).ok_or(FaceParsingError::NoHeadTable)?;
let hhea = hhea::Table::parse(raw_tables.hhea).ok_or(FaceParsingError::NoHheaTable)?;
let maxp = maxp::Table::parse(raw_tables.maxp).ok_or(FaceParsingError::NoMaxpTable)?;
let hmtx = raw_tables.hmtx.and_then(|data| {
hmtx::Table::parse(hhea.number_of_metrics, maxp.number_of_glyphs, data)
});
let vhea = raw_tables.vhea.and_then(vhea::Table::parse);
let vmtx = if let Some(vhea) = vhea {
raw_tables.vmtx.and_then(|data| {
hmtx::Table::parse(vhea.number_of_metrics, maxp.number_of_glyphs, data)
})
} else {
None
};
let loca = raw_tables.loca.and_then(|data| {
loca::Table::parse(maxp.number_of_glyphs, head.index_to_location_format, data)
});
let glyf = if let Some(loca) = loca {
raw_tables
.glyf
.and_then(|data| glyf::Table::parse(loca, data))
} else {
None
};
let cbdt = if let Some(cblc) = raw_tables.cblc.and_then(cblc::Table::parse) {
raw_tables
.cbdt
.and_then(|data| cbdt::Table::parse(cblc, data))
} else {
None
};
Ok(FaceTables {
head,
hhea,
maxp,
cbdt,
cff: raw_tables.cff.and_then(cff::Table::parse),
cmap: raw_tables.cmap.and_then(cmap::Table::parse),
glyf,
hmtx,
kern: raw_tables.kern.and_then(kern::Table::parse),
name: raw_tables.name.and_then(name::Table::parse),
os2: raw_tables.os2.and_then(os2::Table::parse),
post: raw_tables.post.and_then(post::Table::parse),
sbix: raw_tables
.sbix
.and_then(|data| sbix::Table::parse(maxp.number_of_glyphs, data)),
svg: raw_tables.svg.and_then(svg::Table::parse),
vhea: raw_tables.vhea.and_then(vhea::Table::parse),
vmtx,
vorg: raw_tables.vorg.and_then(vorg::Table::parse),
#[cfg(feature = "opentype-layout")]
gdef: raw_tables.gdef.and_then(gdef::Table::parse),
#[cfg(feature = "opentype-layout")]
gpos: raw_tables
.gpos
.and_then(opentype_layout::LayoutTable::parse),
#[cfg(feature = "opentype-layout")]
gsub: raw_tables
.gsub
.and_then(opentype_layout::LayoutTable::parse),
#[cfg(feature = "opentype-layout")]
math: raw_tables.math.and_then(math::Table::parse),
#[cfg(feature = "apple-layout")]
ankr: raw_tables
.ankr
.and_then(|data| ankr::Table::parse(maxp.number_of_glyphs, data)),
#[cfg(feature = "apple-layout")]
feat: raw_tables.feat.and_then(feat::Table::parse),
#[cfg(feature = "apple-layout")]
kerx: raw_tables
.kerx
.and_then(|data| kerx::Table::parse(maxp.number_of_glyphs, data)),
#[cfg(feature = "apple-layout")]
morx: raw_tables
.morx
.and_then(|data| morx::Table::parse(maxp.number_of_glyphs, data)),
#[cfg(feature = "apple-layout")]
trak: raw_tables.trak.and_then(trak::Table::parse),
#[cfg(feature = "variable-fonts")]
avar: raw_tables.avar.and_then(avar::Table::parse),
#[cfg(feature = "variable-fonts")]
cff2: raw_tables.cff2.and_then(cff2::Table::parse),
#[cfg(feature = "variable-fonts")]
fvar: raw_tables.fvar.and_then(fvar::Table::parse),
#[cfg(feature = "variable-fonts")]
gvar: raw_tables.gvar.and_then(gvar::Table::parse),
#[cfg(feature = "variable-fonts")]
hvar: raw_tables.hvar.and_then(hvar::Table::parse),
#[cfg(feature = "variable-fonts")]
mvar: raw_tables.mvar.and_then(mvar::Table::parse),
#[cfg(feature = "variable-fonts")]
vvar: raw_tables.vvar.and_then(hvar::Table::parse),
})
}
#[inline]
pub fn tables(&self) -> &FaceTables<'a> {
&self.tables
}
#[inline]
pub fn raw_face(&self) -> &RawFace<'a> {
&self.raw_face
}
#[deprecated(since = "0.16.0", note = "use `self.raw_face().table()` instead")]
#[inline]
pub fn table_data(&self, tag: Tag) -> Option<&'a [u8]> {
self.raw_face.table(tag)
}
#[inline]
pub fn names(&self) -> name::Names<'a> {
self.tables.name.unwrap_or_default().names
}
#[inline]
pub fn is_regular(&self) -> bool {
self.tables
.os2
.map(|s| s.style() == Style::Normal)
.unwrap_or(false)
}
#[inline]
pub fn is_italic(&self) -> bool {
self.tables
.os2
.map(|s| s.style() == Style::Italic)
.unwrap_or(false)
}
#[inline]
pub fn is_bold(&self) -> bool {
try_opt_or!(self.tables.os2, false).is_bold()
}
#[inline]
pub fn is_oblique(&self) -> bool {
self.tables
.os2
.map(|s| s.style() == Style::Oblique)
.unwrap_or(false)
}
#[inline]
pub fn style(&self) -> Style {
try_opt_or!(self.tables.os2, Style::Normal).style()
}
#[inline]
pub fn is_monospaced(&self) -> bool {
try_opt_or!(self.tables.post, false).is_monospaced
}
#[inline]
pub fn is_variable(&self) -> bool {
#[cfg(feature = "variable-fonts")]
{
self.tables.fvar.is_some()
}
#[cfg(not(feature = "variable-fonts"))]
{
false
}
}
#[inline]
pub fn weight(&self) -> Weight {
try_opt_or!(self.tables.os2, Weight::default()).weight()
}
#[inline]
pub fn width(&self) -> Width {
try_opt_or!(self.tables.os2, Width::default()).width()
}
#[inline]
pub fn italic_angle(&self) -> Option<f32> {
self.tables.post.map(|table| table.italic_angle)
}
#[inline]
pub fn ascender(&self) -> i16 {
if let Some(os_2) = self.tables.os2 {
if os_2.use_typographic_metrics() {
let value = os_2.typographic_ascender();
return self.apply_metrics_variation(Tag::from_bytes(b"hasc"), value);
}
}
let mut value = self.tables.hhea.ascender;
if value == 0 {
if let Some(os_2) = self.tables.os2 {
value = os_2.typographic_ascender();
if value == 0 {
value = os_2.windows_ascender();
value = self.apply_metrics_variation(Tag::from_bytes(b"hcla"), value);
} else {
value = self.apply_metrics_variation(Tag::from_bytes(b"hasc"), value);
}
}
}
value
}
#[inline]
pub fn descender(&self) -> i16 {
if let Some(os_2) = self.tables.os2 {
if os_2.use_typographic_metrics() {
let value = os_2.typographic_descender();
return self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), value);
}
}
let mut value = self.tables.hhea.descender;
if value == 0 {
if let Some(os_2) = self.tables.os2 {
value = os_2.typographic_descender();
if value == 0 {
value = os_2.windows_descender();
value = self.apply_metrics_variation(Tag::from_bytes(b"hcld"), value);
} else {
value = self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), value);
}
}
}
value
}
#[inline]
pub fn height(&self) -> i16 {
self.ascender() - self.descender()
}
#[inline]
pub fn line_gap(&self) -> i16 {
if let Some(os_2) = self.tables.os2 {
if os_2.use_typographic_metrics() {
let value = os_2.typographic_line_gap();
return self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), value);
}
}
let mut value = self.tables.hhea.line_gap;
if self.tables.hhea.ascender == 0 || self.tables.hhea.descender == 0 {
if let Some(os_2) = self.tables.os2 {
if os_2.typographic_ascender() != 0 || os_2.typographic_descender() != 0 {
value = os_2.typographic_line_gap();
value = self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), value);
} else {
value = 0;
}
}
}
value
}
#[inline]
pub fn typographic_ascender(&self) -> Option<i16> {
self.tables.os2.map(|table| {
let v = table.typographic_ascender();
self.apply_metrics_variation(Tag::from_bytes(b"hasc"), v)
})
}
#[inline]
pub fn typographic_descender(&self) -> Option<i16> {
self.tables.os2.map(|table| {
let v = table.typographic_descender();
self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), v)
})
}
#[inline]
pub fn typographic_line_gap(&self) -> Option<i16> {
self.tables.os2.map(|table| {
let v = table.typographic_line_gap();
self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), v)
})
}
#[inline]
pub fn vertical_ascender(&self) -> Option<i16> {
self.tables
.vhea
.map(|vhea| vhea.ascender)
.map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vasc"), v))
}
#[inline]
pub fn vertical_descender(&self) -> Option<i16> {
self.tables
.vhea
.map(|vhea| vhea.descender)
.map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vdsc"), v))
}
#[inline]
pub fn vertical_height(&self) -> Option<i16> {
Some(self.vertical_ascender()? - self.vertical_descender()?)
}
#[inline]
pub fn vertical_line_gap(&self) -> Option<i16> {
self.tables
.vhea
.map(|vhea| vhea.line_gap)
.map(|v| self.apply_metrics_variation(Tag::from_bytes(b"vlgp"), v))
}
#[inline]
pub fn units_per_em(&self) -> u16 {
self.tables.head.units_per_em
}
#[inline]
pub fn x_height(&self) -> Option<i16> {
self.tables
.os2
.and_then(|os_2| os_2.x_height())
.map(|v| self.apply_metrics_variation(Tag::from_bytes(b"xhgt"), v))
}
#[inline]
pub fn capital_height(&self) -> Option<i16> {
self.tables
.os2
.and_then(|os_2| os_2.capital_height())
.map(|v| self.apply_metrics_variation(Tag::from_bytes(b"cpht"), v))
}
#[inline]
pub fn underline_metrics(&self) -> Option<LineMetrics> {
let mut metrics = self.tables.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)
}
#[inline]
pub fn strikeout_metrics(&self) -> Option<LineMetrics> {
let mut metrics = self.tables.os2?.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)
}
#[inline]
pub fn subscript_metrics(&self) -> Option<ScriptMetrics> {
let mut metrics = self.tables.os2?.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)
}
#[inline]
pub fn superscript_metrics(&self) -> Option<ScriptMetrics> {
let mut metrics = self.tables.os2?.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)
}
#[inline]
pub fn permissions(&self) -> Option<Permissions> {
self.tables.os2?.permissions()
}
#[inline]
pub fn is_subsetting_allowed(&self) -> bool {
self.tables
.os2
.map(|t| t.is_subsetting_allowed())
.unwrap_or(false)
}
#[inline]
pub fn is_bitmap_embedding_allowed(&self) -> bool {
self.tables
.os2
.map(|t| t.is_bitmap_embedding_allowed())
.unwrap_or(false)
}
#[inline]
pub fn unicode_ranges(&self) -> UnicodeRanges {
self.tables
.os2
.map(|t| t.unicode_ranges())
.unwrap_or_default()
}
#[inline]
pub fn number_of_glyphs(&self) -> u16 {
self.tables.maxp.number_of_glyphs.get()
}
#[inline]
pub fn glyph_index(&self, code_point: char) -> Option<GlyphId> {
for subtable in self.tables.cmap?.subtables {
if !subtable.is_unicode() {
continue;
}
if let Some(id) = subtable.glyph_index(u32::from(code_point)) {
return Some(id);
}
}
None
}
#[cfg(feature = "glyph-names")]
#[inline]
pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> {
if let Some(name) = self
.tables
.post
.and_then(|post| post.glyph_index_by_name(name))
{
return Some(name);
}
if let Some(name) = self
.tables
.cff
.as_ref()
.and_then(|cff| cff.glyph_index_by_name(name))
{
return Some(name);
}
None
}
#[inline]
pub fn glyph_variation_index(&self, code_point: char, variation: char) -> Option<GlyphId> {
for subtable in self.tables.cmap?.subtables {
if let cmap::Format::UnicodeVariationSequences(ref table) = subtable.format {
return match table.glyph_index(u32::from(code_point), u32::from(variation))? {
cmap::GlyphVariationResult::Found(v) => Some(v),
cmap::GlyphVariationResult::UseDefault => self.glyph_index(code_point),
};
}
}
None
}
#[inline]
pub fn glyph_hor_advance(&self, glyph_id: GlyphId) -> Option<u16> {
#[cfg(feature = "variable-fonts")]
{
let mut advance = self.tables.hmtx?.advance(glyph_id)? as f32;
if self.is_variable() {
if let Some(hvar) = self.tables.hvar {
if let Some(offset) = hvar.advance_offset(glyph_id, self.coords()) {
advance += offset + 0.5;
}
}
}
u16::try_num_from(advance)
}
#[cfg(not(feature = "variable-fonts"))]
{
self.tables.hmtx?.advance(glyph_id)
}
}
#[inline]
pub fn glyph_ver_advance(&self, glyph_id: GlyphId) -> Option<u16> {
#[cfg(feature = "variable-fonts")]
{
let mut advance = self.tables.vmtx?.advance(glyph_id)? as f32;
if self.is_variable() {
if let Some(vvar) = self.tables.vvar {
if let Some(offset) = vvar.advance_offset(glyph_id, self.coords()) {
advance += offset + 0.5;
}
}
}
u16::try_num_from(advance)
}
#[cfg(not(feature = "variable-fonts"))]
{
self.tables.vmtx?.advance(glyph_id)
}
}
#[inline]
pub fn glyph_hor_side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
#[cfg(feature = "variable-fonts")]
{
let mut bearing = self.tables.hmtx?.side_bearing(glyph_id)? as f32;
if self.is_variable() {
if let Some(hvar) = self.tables.hvar {
if let Some(offset) = hvar.side_bearing_offset(glyph_id, self.coords()) {
bearing += offset + 0.5;
}
}
}
i16::try_num_from(bearing)
}
#[cfg(not(feature = "variable-fonts"))]
{
self.tables.hmtx?.side_bearing(glyph_id)
}
}
#[inline]
pub fn glyph_ver_side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
#[cfg(feature = "variable-fonts")]
{
let mut bearing = self.tables.vmtx?.side_bearing(glyph_id)? as f32;
if self.is_variable() {
if let Some(vvar) = self.tables.vvar {
if let Some(offset) = vvar.side_bearing_offset(glyph_id, self.coords()) {
bearing += offset + 0.5;
}
}
}
i16::try_num_from(bearing)
}
#[cfg(not(feature = "variable-fonts"))]
{
self.tables.vmtx?.side_bearing(glyph_id)
}
}
pub fn glyph_y_origin(&self, glyph_id: GlyphId) -> Option<i16> {
self.tables.vorg.map(|vorg| vorg.glyph_y_origin(glyph_id))
}
#[cfg(feature = "glyph-names")]
#[inline]
pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&str> {
if let Some(name) = self.tables.post.and_then(|post| post.glyph_name(glyph_id)) {
return Some(name);
}
if let Some(name) = self
.tables
.cff
.as_ref()
.and_then(|cff1| cff1.glyph_name(glyph_id))
{
return Some(name);
}
None
}
#[inline]
pub fn outline_glyph(
&self,
glyph_id: GlyphId,
builder: &mut dyn OutlineBuilder,
) -> Option<Rect> {
#[cfg(feature = "variable-fonts")]
{
if let Some(ref gvar) = self.tables.gvar {
return gvar.outline(self.tables.glyf?, self.coords(), glyph_id, builder);
}
}
if let Some(table) = self.tables.glyf {
return table.outline(glyph_id, builder);
}
if let Some(ref cff) = self.tables.cff {
return cff.outline(glyph_id, builder).ok();
}
#[cfg(feature = "variable-fonts")]
{
if let Some(ref cff2) = self.tables.cff2 {
return cff2.outline(self.coords(), glyph_id, builder).ok();
}
}
None
}
#[inline]
pub fn glyph_bounding_box(&self, glyph_id: GlyphId) -> Option<Rect> {
self.outline_glyph(glyph_id, &mut DummyOutline)
}
#[inline]
pub fn global_bounding_box(&self) -> Rect {
self.tables.head.global_bbox
}
#[inline]
pub fn glyph_raster_image(
&self,
glyph_id: GlyphId,
pixels_per_em: u16,
) -> Option<RasterGlyphImage> {
if let Some(table) = self.tables.sbix {
if let Some(strike) = table.best_strike(pixels_per_em) {
return strike.get(glyph_id);
}
}
if let Some(cbdt) = self.tables.cbdt {
return cbdt.get(glyph_id, pixels_per_em);
}
None
}
#[inline]
pub fn glyph_svg_image(&self, glyph_id: GlyphId) -> Option<&'a [u8]> {
self.tables.svg.and_then(|svg| svg.documents.find(glyph_id))
}
#[cfg(feature = "variable-fonts")]
#[inline]
pub fn variation_axes(&self) -> LazyArray16<'a, VariationAxis> {
self.tables.fvar.map(|fvar| fvar.axes).unwrap_or_default()
}
#[cfg(feature = "variable-fonts")]
pub fn set_variation(&mut self, axis: Tag, value: f32) -> Option<()> {
if !self.is_variable() {
return None;
}
let v = self
.variation_axes()
.into_iter()
.enumerate()
.find(|(_, a)| a.tag == axis);
if let Some((idx, a)) = v {
if idx >= MAX_VAR_COORDS {
return None;
}
self.coordinates.data[idx] = a.normalized_value(value);
} else {
return None;
}
if let Some(avar) = self.tables.avar {
let _ = avar.map_coordinates(self.coordinates.as_mut_slice());
}
Some(())
}
#[cfg(feature = "variable-fonts")]
#[inline]
pub fn variation_coordinates(&self) -> &[NormalizedCoordinate] {
self.coordinates.as_slice()
}
#[cfg(feature = "variable-fonts")]
#[inline]
pub fn has_non_default_variation_coordinates(&self) -> bool {
self.coordinates.as_slice().iter().any(|c| c.0 != 0)
}
#[cfg(feature = "variable-fonts")]
#[inline]
fn metrics_var_offset(&self, tag: Tag) -> f32 {
self.tables
.mvar
.and_then(|table| table.metric_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
}
#[cfg(feature = "variable-fonts")]
#[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);
if let Some(v) = i16::try_num_from(v) {
*value = v;
}
}
}
#[cfg(not(feature = "variable-fonts"))]
#[inline]
fn apply_metrics_variation_to(&self, _: Tag, _: &mut i16) {}
#[cfg(feature = "variable-fonts")]
#[inline]
fn coords(&self) -> &[NormalizedCoordinate] {
self.coordinates.as_slice()
}
}
struct DefaultTableProvider<'a> {
data: &'a [u8],
tables: LazyArrayIter16<'a, TableRecord>,
}
impl<'a> Iterator for DefaultTableProvider<'a> {
type Item = Result<(Tag, Option<&'a [u8]>), FaceParsingError>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.tables.next().map(|table| {
Ok((table.tag, {
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;
self.data.get(range)
}))
})
}
}
impl core::fmt::Debug for Face<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Face()")
}
}
#[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>(); s.read::<u32>()
}