blob: d0b5bbb50859755255b60933936569332cf3c05c [file] [log] [blame]
/*!
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);
}
}