#![doc(html_root_url = "https://docs.rs/ttf-parser/0.5.0")]
#![no_std]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
#[cfg(feature = "std")]
use std::string::String;
use core::fmt;
use core::num::NonZeroU16;
macro_rules! try_opt_or {
($value:expr, $ret:expr) => {
match $value {
Some(v) => v,
None => return $ret,
}
};
}
#[cfg(feature = "logging")]
macro_rules! warn {
($($arg:tt)+) => (
log::log!(log::Level::Warn, $($arg)+);
)
}
#[cfg(not(feature = "logging"))]
macro_rules! warn {
($($arg:tt)+) => ()
}
mod ggg;
mod parser;
mod raw;
mod tables;
mod var_store;
#[cfg(feature = "std")]
mod writer;
use tables::*;
use parser::{Stream, SafeStream, FromData, Offset, NumConv, TryNumConv, i16_bound, f32_bound};
pub use fvar::{VariationAxes, VariationAxis};
pub use gdef::GlyphClass;
pub use ggg::*;
pub use name::*;
pub use os2::*;
#[repr(transparent)]
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default, Debug)]
pub struct GlyphId(pub u16);
impl FromData for GlyphId {
#[inline]
fn parse(data: &[u8]) -> Self {
let mut s = SafeStream::new(data);
GlyphId(s.read())
}
}
#[derive(Clone, Copy, PartialEq, Default, Debug)]
struct NormalizedCoord(i16);
impl From<i16> for NormalizedCoord {
#[inline]
fn from(n: i16) -> Self {
NormalizedCoord(i16_bound(-16384, n, 16384))
}
}
impl From<f32> for NormalizedCoord {
#[inline]
fn from(n: f32) -> Self {
NormalizedCoord((f32_bound(-1.0, n, 1.0) * 16384.0) as i16)
}
}
impl NormalizedCoord {
#[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
}
#[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(),
])
}
#[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 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Tag({})", self)
}
}
impl core::fmt::Display for Tag {
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 {
#[inline]
fn parse(data: &[u8]) -> Self {
Tag(u32::parse(data))
}
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct LineMetrics {
pub position: i16,
pub thickness: i16,
}
#[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 {
#[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, Debug)]
pub(crate) enum IndexToLocationFormat {
Short,
Long,
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum TableName {
AxisVariations = 0,
CharacterToGlyphIndexMapping,
CompactFontFormat,
CompactFontFormat2,
FontVariations,
GlyphData,
GlyphDefinition,
GlyphVariations,
Header,
HorizontalHeader,
HorizontalMetrics,
HorizontalMetricsVariations,
IndexToLocation,
Kerning,
MaximumProfile,
MetricsVariations,
Naming,
PostScript,
VerticalHeader,
VerticalMetrics,
VerticalMetricsVariations,
VerticalOrigin,
WindowsMetrics,
}
const MAX_VAR_COORDS: u8 = 32;
#[derive(Clone, Default)]
struct VarCoords {
data: [NormalizedCoord; MAX_VAR_COORDS as usize],
len: u8,
}
impl VarCoords {
#[inline]
fn as_slice(&self) -> &[NormalizedCoord] {
&self.data[0..usize::from(self.len)]
}
#[inline]
fn as_mut_slice(&mut self) -> &mut [NormalizedCoord] {
let end = usize::from(self.len);
&mut self.data[0..end]
}
}
#[derive(Clone)]
pub struct Font<'a> {
avar: Option<avar::Table<'a>>,
cff_: Option<cff::Metadata<'a>>,
cff2: Option<cff2::Metadata<'a>>,
cmap: Option<cmap::Table<'a>>,
fvar: Option<fvar::Table<'a>>,
gdef: Option<gdef::Table<'a>>,
glyf: Option<&'a [u8]>,
gvar: Option<gvar::Table<'a>>,
head: raw::head::Table<'a>,
hhea: raw::hhea::Table<'a>,
hmtx: Option<hmtx::Table<'a>>,
hvar: Option<hvar::Table<'a>>,
kern: Option<&'a [u8]>,
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<raw::vhea::Table<'a>>,
vmtx: Option<hmtx::Table<'a>>,
vorg: Option<vorg::Table<'a>>,
vvar: Option<hvar::Table<'a>>,
number_of_glyphs: NonZeroU16,
coordinates: VarCoords,
}
impl<'a> Font<'a> {
pub fn from_data(data: &'a [u8], index: u32) -> Option<Self> {
let table_data = if let Some(n) = fonts_in_collection(data) {
if index < n {
const OFFSET_32_SIZE: usize = 4;
let offset = raw::TTCHeader::SIZE + OFFSET_32_SIZE * usize::num_from(index);
let font_offset: u32 = Stream::read_at(data, offset)?;
data.get(usize::num_from(font_offset) .. data.len())?
} else {
return None;
}
} else {
data
};
const OFFSET_TABLE_SIZE: usize = 12;
if data.len() < OFFSET_TABLE_SIZE {
return None;
}
const SFNT_VERSION_TRUE_TYPE: u32 = 0x00010000;
const SFNT_VERSION_OPEN_TYPE: u32 = 0x4F54544F;
let mut s = Stream::new(table_data);
let sfnt_version: u32 = s.read()?;
if sfnt_version != SFNT_VERSION_TRUE_TYPE && sfnt_version != SFNT_VERSION_OPEN_TYPE {
return None;
}
let num_tables: u16 = s.read()?;
s.advance(6);
let tables = s.read_array16::<raw::TableRecord>(num_tables)?;
let mut cff_ = None;
let mut cff2 = None;
let mut gdef = None;
let mut hvar = None;
let mut gvar = None;
let mut mvar = None;
let mut os_2 = None;
let mut vorg = None;
let mut vvar = None;
let mut avar = None;
let mut cmap = None;
let mut fvar = None;
let mut glyf = None;
let mut head = None;
let mut hhea = None;
let mut hmtx = None;
let mut kern = None;
let mut loca = None;
let mut maxp = None;
let mut name = None;
let mut post = None;
let mut vhea = None;
let mut vmtx = None;
for table in tables {
let offset = table.offset().to_usize();
let length = usize::num_from(table.length());
let range = offset..(offset + length);
match &table.table_tag().to_bytes() {
b"CFF " => cff_ = data.get(range).and_then(|data| cff::parse_metadata(data)),
b"CFF2" => cff2 = data.get(range).and_then(|data| cff2::parse_metadata(data)),
b"GDEF" => gdef = data.get(range).and_then(|data| gdef::Table::parse(data)),
b"HVAR" => hvar = data.get(range).and_then(|data| hvar::Table::parse(data)),
b"MVAR" => mvar = data.get(range).and_then(|data| mvar::Table::parse(data)),
b"OS/2" => os_2 = data.get(range).and_then(|data| os2::Table::parse(data)),
b"VORG" => vorg = data.get(range).and_then(|data| vorg::Table::parse(data)),
b"VVAR" => vvar = data.get(range).and_then(|data| hvar::Table::parse(data)),
b"avar" => avar = data.get(range).and_then(|data| avar::Table::parse(data)),
b"cmap" => cmap = data.get(range).and_then(|data| cmap::Table::parse(data)),
b"fvar" => fvar = data.get(range).and_then(|data| fvar::Table::parse(data)),
b"glyf" => glyf = data.get(range),
b"gvar" => gvar = data.get(range).and_then(|data| gvar::Table::parse(data)),
b"head" => head = data.get(range).and_then(|data| raw::head::Table::parse(data)),
b"hhea" => hhea = data.get(range).and_then(|data| raw::hhea::Table::parse(data)),
b"hmtx" => hmtx = data.get(range),
b"kern" => kern = data.get(range),
b"loca" => loca = data.get(range),
b"maxp" => maxp = data.get(range).and_then(|data| maxp::parse(data)),
b"name" => name = data.get(range).and_then(|data| name::parse(data)),
b"post" => post = data.get(range).and_then(|data| post::Table::parse(data)),
b"vhea" => vhea = data.get(range).and_then(|data| raw::vhea::Table::parse(data)),
b"vmtx" => vmtx = data.get(range),
_ => {}
}
}
let head = head?;
let hhea = hhea?;
let maxp = maxp?;
let number_of_glyphs = maxp.number_of_glyphs;
let mut coordinates = VarCoords::default();
if let Some(ref fvar) = fvar {
coordinates.len = fvar.axes().count().min(MAX_VAR_COORDS as usize) as u8;
}
let mut font = Font {
avar,
cff_,
cff2,
cmap,
fvar,
gdef,
glyf,
gvar,
head,
hhea,
hmtx: None,
hvar,
kern,
loca: None,
mvar,
name,
os_2,
post,
vhea,
vmtx: None,
vorg,
vvar,
number_of_glyphs,
coordinates,
};
if let Some(data) = hmtx {
if let Some(number_of_h_metrics) = font.hhea.number_of_h_metrics() {
font.hmtx = hmtx::Table::parse(data, number_of_h_metrics, font.number_of_glyphs);
}
}
if let (Some(vhea), Some(data)) = (font.vhea, vmtx) {
if let Some(number_of_v_metrics) = vhea.num_of_long_ver_metrics() {
font.vmtx = hmtx::Table::parse(data, number_of_v_metrics, font.number_of_glyphs);
}
}
if let Some(data) = loca {
let format = match font.head.index_to_loc_format() {
0 => Some(IndexToLocationFormat::Short),
1 => Some(IndexToLocationFormat::Long),
_ => None,
};
if let Some(format) = format {
font.loca = loca::Table::parse(data, font.number_of_glyphs, format);
}
}
Some(font)
}
#[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::CompactFontFormat => self.cff_.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::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(),
}
}
#[inline]
pub fn names(&self) -> Names {
self.name.unwrap_or_default()
}
#[cfg(feature = "std")]
#[inline]
pub fn family_name(&self) -> Option<String> {
let mut idx = None;
let mut iter = self.names();
for (i, name) in iter.enumerate() {
if name.name_id() == name_id::TYPOGRAPHIC_FAMILY && name.is_unicode() {
idx = Some(i);
break;
} else if name.name_id() == name_id::FAMILY && name.is_unicode() {
idx = Some(i);
}
}
iter.nth(idx?).and_then(|name| name.name_from_utf16_be())
}
#[cfg(feature = "std")]
#[inline]
pub fn post_script_name(&self) -> Option<String> {
self.names()
.find(|name| name.name_id() == name_id::POST_SCRIPT_NAME && name.is_unicode())
.and_then(|name| name.name_from_utf16_be())
}
#[inline]
pub fn is_regular(&self) -> bool {
try_opt_or!(self.os_2, false).is_regular()
}
#[inline]
pub fn is_italic(&self) -> bool {
try_opt_or!(self.os_2, false).is_italic()
}
#[inline]
pub fn is_bold(&self) -> bool {
try_opt_or!(self.os_2, false).is_bold()
}
#[inline]
pub fn is_oblique(&self) -> bool {
try_opt_or!(self.os_2, false).is_oblique()
}
#[inline]
pub fn is_variable(&self) -> bool {
self.fvar.is_some()
}
#[inline]
pub fn weight(&self) -> Weight {
try_opt_or!(self.os_2, Weight::default()).weight()
}
#[inline]
pub fn width(&self) -> Width {
try_opt_or!(self.os_2, Width::default()).width()
}
#[inline]
fn use_typo_metrics(&self) -> Option<os2::Table> {
self.os_2.filter(|table| table.is_use_typo_metrics())
}
#[inline]
pub fn ascender(&self) -> i16 {
if let Some(os_2) = self.use_typo_metrics() {
let v = os_2.s_typo_ascender();
self.apply_metrics_variation(Tag::from_bytes(b"hasc"), v)
} else {
self.hhea.ascender()
}
}
#[inline]
pub fn descender(&self) -> i16 {
if let Some(os_2) = self.use_typo_metrics() {
let v = os_2.s_typo_descender();
self.apply_metrics_variation(Tag::from_bytes(b"hdsc"), v)
} else {
self.hhea.descender()
}
}
#[inline]
pub fn height(&self) -> i16 {
self.ascender() - self.descender()
}
#[inline]
pub fn line_gap(&self) -> i16 {
if let Some(os_2) = self.use_typo_metrics() {
let v = os_2.s_typo_line_gap();
self.apply_metrics_variation(Tag::from_bytes(b"hlgp"), v)
} else {
self.hhea.line_gap()
}
}
#[inline]
pub fn vertical_ascender(&self) -> Option<i16> {
self.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.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.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) -> Option<u16> {
let num = self.head.units_per_em();
if num >= 16 && num <= 16384 {
Some(num)
} else {
None
}
}
#[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))
}
#[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)
}
#[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)
}
#[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)
}
#[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)
}
#[inline]
pub fn number_of_glyphs(&self) -> u16 {
self.number_of_glyphs.get()
}
#[inline]
pub fn glyph_index(&self, c: char) -> Option<GlyphId> {
cmap::glyph_index(self.cmap.as_ref()?, c)
}
#[inline]
pub fn glyph_variation_index(&self, c: char, variation: char) -> Option<GlyphId> {
cmap::glyph_variation_index(self.cmap.as_ref()?, c, variation)
}
#[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() {
advance += hvar::glyph_advance_offset(self.hvar?, glyph_id, self.coords())? + 0.5;
}
u16::try_num_from(advance)
}
#[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() {
advance += hvar::glyph_advance_offset(self.vvar?, glyph_id, self.coords())? + 0.5;
}
u16::try_num_from(advance)
}
#[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() {
bearing += hvar::glyph_side_bearing_offset(self.hvar?, glyph_id, self.coords())? + 0.5;
}
i16::try_num_from(bearing)
}
#[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() {
bearing += hvar::glyph_side_bearing_offset(self.vvar?, glyph_id, self.coords())? + 0.5;
}
i16::try_num_from(bearing)
}
pub fn glyph_y_origin(&self, glyph_id: GlyphId) -> Option<i16> {
self.vorg.map(|vorg| vorg.glyph_y_origin(glyph_id))
}
#[inline]
pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&str> {
self.post.and_then(|post| post.glyph_name(glyph_id))
}
pub fn has_glyph_classes(&self) -> bool {
try_opt_or!(self.gdef, false).has_glyph_classes()
}
pub fn glyph_class(&self, glyph_id: GlyphId) -> Option<GlyphClass> {
self.gdef.and_then(|gdef| gdef.glyph_class(glyph_id))
}
pub fn glyph_mark_attachment_class(&self, glyph_id: GlyphId) -> Class {
try_opt_or!(self.gdef, Class(0)).glyph_mark_attachment_class(glyph_id)
}
#[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)
}
pub fn glyphs_kerning(&self, glyph_id1: GlyphId, glyph_id2: GlyphId) -> Option<i16> {
kern::glyphs_kerning(self.kern?, glyph_id1, glyph_id2)
}
#[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.cff_ {
return cff::outline(metadata, glyph_id, builder);
}
if let Some(ref metadata) = self.cff2 {
return cff2::outline(metadata, self.coords(), glyph_id, builder);
}
None
}
#[inline]
pub fn glyph_bounding_box(&self, glyph_id: GlyphId) -> Option<Rect> {
if let Some(glyf_table) = self.glyf {
return glyf::glyph_bbox(self.loca?, glyf_table, glyph_id);
}
self.outline_glyph(glyph_id, &mut DummyOutline)
}
#[inline]
pub fn variation_axes(&self) -> VariationAxes {
self.fvar.map(|fvar| fvar.axes()).unwrap_or_default()
}
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 {
warn!("Font doesn't have a '{}' axis.", axis);
return None;
}
if let Some(avar) = self.avar {
let _ = avar.map_coordinates(self.coordinates.as_mut_slice());
}
Some(())
}
#[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);
if let Some(v) = i16::try_num_from(v) {
*value = v;
}
}
}
#[inline]
fn coords(&self) -> &[NormalizedCoord] {
self.coordinates.as_slice()
}
}
impl fmt::Debug for Font<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Font()")
}
}
#[inline]
pub fn fonts_in_collection(data: &[u8]) -> Option<u32> {
let table = raw::TTCHeader::new(data.get(0..raw::TTCHeader::SIZE)?);
if &table.ttc_tag().to_bytes() != b"ttcf" {
return None;
}
Some(table.num_fonts())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::writer;
use writer::TtfType::*;
#[test]
fn empty_font() {
assert!(Font::from_data(&[], 0).is_none());
}
#[test]
fn incomplete_header() {
let data = writer::convert(&[
TrueTypeMagic,
UInt16(0),
UInt16(0),
UInt16(0),
UInt16(0),
]);
for i in 0..data.len() {
assert!(Font::from_data(&data[0..i], 0).is_none());
}
}
#[test]
fn zero_tables() {
let data = writer::convert(&[
TrueTypeMagic,
UInt16(0),
UInt16(0),
UInt16(0),
UInt16(0),
]);
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn tables_count_overflow() {
let data = writer::convert(&[
TrueTypeMagic,
UInt16(std::u16::MAX),
UInt16(0),
UInt16(0),
UInt16(0),
]);
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn open_type_magic() {
let data = writer::convert(&[
OpenTypeMagic,
UInt16(0),
UInt16(0),
UInt16(0),
UInt16(0),
]);
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn unknown_magic() {
let data = writer::convert(&[
Raw(&[0xFF, 0xFF, 0xFF, 0xFF]),
UInt16(0),
UInt16(0),
UInt16(0),
UInt16(0),
]);
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn empty_font_collection() {
let data = writer::convert(&[
FontCollectionMagic,
UInt16(1),
UInt16(0),
UInt32(0),
]);
assert_eq!(fonts_in_collection(&data), Some(0));
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn font_collection_num_fonts_overflow() {
let data = writer::convert(&[
FontCollectionMagic,
UInt16(1),
UInt16(0),
UInt32(std::u32::MAX),
]);
assert_eq!(fonts_in_collection(&data), Some(std::u32::MAX));
assert!(Font::from_data(&data, 0).is_none());
}
#[test]
fn font_index_overflow_1() {
let data = writer::convert(&[
FontCollectionMagic,
UInt16(1),
UInt16(0),
UInt32(1),
]);
assert_eq!(fonts_in_collection(&data), Some(1));
assert!(Font::from_data(&data, std::u32::MAX).is_none());
}
#[test]
fn font_index_overflow_2() {
let data = writer::convert(&[
FontCollectionMagic,
UInt16(1),
UInt16(0),
UInt32(std::u32::MAX),
]);
assert_eq!(fonts_in_collection(&data), Some(std::u32::MAX));
assert!(Font::from_data(&data, std::u32::MAX - 1).is_none());
}
}