mttf 0.1.7

A library for working with TrueType fonts. Most parts are zero-allocation.
Documentation

use std::fmt::{ Debug, Formatter, Result as FmtResult };
use num_enum::{ FromPrimitive, IntoPrimitive };
//use bytemuck::{ Zeroable, Pod };

use super::ReadError;
use super::core::*;
use super::common::{ CoverageTable, ScriptList, FeatureList, LookupTable as GenericLookupTable };

// Single Substitution

#[derive(Clone, Copy, Debug)]
pub enum SingleSubstitution<'a> {
	DeltaGlyphId(i16),
	SubstituteGlyphArray(U16Array<'a>),
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct SingleSubstitutionTable<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for SingleSubstitutionTable<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> SingleSubstitutionTable<'a> {
	pub fn format(&self) -> u16 { self.uint16(0) }
	pub fn matches(&self) -> Result<CoverageTable<'a>, ReadError> {
		self.0[self.uint16(2) as usize..].try_into()
	}
	pub fn substitution(&self) -> SingleSubstitution<'a> {
		match self.format() {
			1 => SingleSubstitution::DeltaGlyphId(self.int16(4)),
			2 => SingleSubstitution::SubstituteGlyphArray(self.uint16_array(6, self.uint16(4) as usize)),
			_ => unreachable!(),
		}
	}
	pub fn map(&self, glyph: u16) -> Result<Option<u16>, ReadError> {
		match self.matches()?.map(glyph) {
			Some(index) => Ok(Some(match self.substitution() {
				SingleSubstitution::DeltaGlyphId(delta) => (glyph as i32 + delta as i32) as u16,
				SingleSubstitution::SubstituteGlyphArray(array) => array.get(index as usize),
			})),
			None => Ok(None),
		}
	}
}

impl Debug for SingleSubstitutionTable<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("SingleSubstitutionTable")
			.field("matches", &self.matches())
			.field("substitution", &self.substitution())
			.finish()
	}
}

impl<'a> TryFrom<&'a [u8]> for SingleSubstitutionTable<'a> {
	type Error = ReadError;
	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
		if value.len() < 6 { return Err(ReadError::UnexpectedEof); }
		match value.uint16(0) {
			1 => Ok(SingleSubstitutionTable(&value[0..])),
			2 => {
				let size = 6 + value.uint16(4) as usize * 2;
				if value.len() < size { return Err(ReadError::UnexpectedEof); }
				Ok(SingleSubstitutionTable(&value[0..]))
			},
			x => Err(ReadError::UnknownSingleSubstitutionFormat(x)),
		}
	}
}

// Ligature Substitution

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Ligature<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for Ligature<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> Ligature<'a> {
	pub fn ligature_glyph(&self) -> u16 { self.uint16(0) }
	pub fn component_glyphs(&self) -> U16Array<'a> { self.uint16_array(4, self.uint16(2) as usize - 1) }
}

impl Debug for Ligature<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("Ligature")
			.field("ligature_glyph", &self.ligature_glyph())
			.field("component_glyphs", &self.component_glyphs())
			.finish()
	}
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct LigatureSet<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for LigatureSet<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> LigatureSet<'a> {
	pub fn count(&self) -> u16 { self.uint16(0) }
	pub fn get(&self, index: u16) -> Result<Ligature<'a>, ReadError> {
		let start = self.uint16(2 + index as usize * 2) as usize;
		if start + 4 > self.0.len() { return Err(ReadError::UnexpectedEof); }
		let stop = start + 2 + self.uint16(start + 2) as usize * 2;
		if stop > self.0.len() { return Err(ReadError::UnexpectedEof); }
		Ok(Ligature(&self.0[start..stop]))
	}
	pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<Ligature<'a>, ReadError>> + '_ {
		(0..self.count()).map(|x| self.get(x))
	}
}

impl Debug for LigatureSet<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_list()
			.entries(self.iter())
			.finish()
	}
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct LigatureSubstitutionTable<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for LigatureSubstitutionTable<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> LigatureSubstitutionTable<'a> {
	pub fn format(&self) -> u16 { self.uint16(0) }
	pub fn prefixes(&self) -> Result<CoverageTable<'a>, ReadError> {
		self.0[self.uint16(2) as usize..].try_into()
	}
	pub fn count(&self) -> u16 { self.uint16(4) }
	pub fn get(&self, index: u16) -> Result<LigatureSet<'a>, ReadError> {
		let start = self.uint16(6 + index as usize * 2) as usize;
		if start + 2 > self.0.len() { return Err(ReadError::UnexpectedEof); }
		let stop = start + 2 + self.uint16(start) as usize * 2;
		if stop > self.0.len() { return Err(ReadError::UnexpectedEof); }
		Ok(LigatureSet(&self.0[start..]))
	}
	pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<LigatureSet<'a>, ReadError>> + '_ {
		(0..self.count()).map(|x| self.get(x))
	}
	pub fn get_by_prefix(&self, prefix_glyph: u16) -> Result<Option<LigatureSet<'a>>, ReadError> {
		if let Some(index) = self.prefixes()?.map(prefix_glyph) {
			Ok(Some(self.get(index)?))
		} else {
			Ok(None)
		}
	}
}

impl Debug for LigatureSubstitutionTable<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		struct Items<'a>(LigatureSubstitutionTable<'a>);
		
		impl Debug for Items<'_> {
			fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
				f.debug_list()
					.entries(self.0.iter())
					.finish()
			}
		}

		f.debug_struct("LigatureSubstitutionTable")
			.field("prefixes", &self.prefixes())
			.field("ligatures", &Items(*self))
			.finish()
	}
}

impl<'a> TryFrom<&'a [u8]> for LigatureSubstitutionTable<'a> {
	type Error = ReadError;
	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
		// TODO validate length
		Ok(Self(value))
	}
}

// Lookup List

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(FromPrimitive, IntoPrimitive)]
#[repr(u16)]
pub enum LookupType {
	Single = 1,
	Multiple = 2,
	Alternate = 3,
	Ligature = 4,
	ContextualSubstitution = 5,
	ChainedContextsSubstitution = 6,
	SubstitutionExtension = 7,
	ReverseChainingContextSingle = 8,

	#[num_enum(catch_all)]
	Unknown(u16),
}

#[derive(Debug, Clone, Copy)]
pub enum LookupTable<'a> {
	Single(GenericLookupTable<'a, LookupType, SingleSubstitutionTable<'a>>),
	Ligature(GenericLookupTable<'a, LookupType, LigatureSubstitutionTable<'a>>),
}

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct LookupList<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for LookupList<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> LookupList<'a> {
	pub fn count(&self) -> u16 { self.uint16(0) }
	pub fn get(&self, index: u16) -> Result<LookupTable<'a>, ReadError> {
		let offset = self.uint16(2 + index as usize * 2) as usize;
		if offset + 2 > self.0.len() { return Err(ReadError::UnexpectedEof); }
		let lookup_type = self.uint16(offset).into();
		match lookup_type {
			LookupType::Single => Ok(LookupTable::Single(self.0[offset..].try_into()?)),
			LookupType::Ligature => Ok(LookupTable::Ligature(self.0[offset..].try_into()?)),
			x => Err(ReadError::UnsupportedGsubLookupType(x)),
		}
	}
	pub fn iter(&self) -> impl ExactSizeIterator<Item = Result<LookupTable<'a>, ReadError>> + '_ {
		(0..self.count()).map(|x| self.get(x))
	}
}

impl Debug for LookupList<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_list()
			.entries(self.iter())
			.finish()
	}
}

// Table

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct GlyphSubstitutionTable<'a>(&'a [u8]);

impl<'a> RandomAccess<'a> for GlyphSubstitutionTable<'a> {
	fn bytes(&self) -> &'a [u8] { self.0 }
}

impl<'a> GlyphSubstitutionTable<'a> {
	pub fn version(&self) -> (u16, u16) { (self.uint16(0), self.uint16(2)) }
	pub fn script_list(&self) -> Result<ScriptList<'a>, ReadError> {
		let offset = self.uint16(4) as usize;
		if offset >= self.0.len() { return Err(ReadError::UnexpectedEof); }
		ScriptList::try_from(&self.0[offset..])
	}
	pub fn feature_list(&self) -> Result<FeatureList<'a>, ReadError> {
		let offset = self.uint16(6) as usize;
		if offset >= self.0.len() { return Err(ReadError::UnexpectedEof); }
		FeatureList::try_from(&self.0[offset..])
	}
	pub fn lookup_list(&self) -> Result<LookupList<'a>, ReadError> {
		let offset = self.uint16(8) as usize;
		if offset >= self.0.len() { return Err(ReadError::UnexpectedEof); }
		Ok(LookupList(&self.0[offset..]))
	}
	pub fn feature_variations_offset(&self) -> Option<u32> {
		match self.version() {
			(1, 0) => None,
			(1, 1) => Some(self.uint32(10)),
			_ => unreachable!(),
		}
	}
}

impl Debug for GlyphSubstitutionTable<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("GlyphSubstitutionTable")
			.field("version", &self.version())
			.field("script_list", &self.script_list())
			.field("feature_list", &self.feature_list())
			.field("lookup_list", &self.lookup_list())
			.field("feature_variations_offset", &self.feature_variations_offset())
			.finish()
	}
}

impl<'a> TryFrom<&'a [u8]> for GlyphSubstitutionTable<'a> {
	type Error = ReadError;
	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
		if value.len() < 10 { return Err(ReadError::UnexpectedEof); }
		match (value.uint16(0), value.uint16(2)) {
			(1, 0) => {
				Ok(GlyphSubstitutionTable(value))
			},
			(1, 1) => {
				if value.len() < 14 { return Err(ReadError::UnexpectedEof); }
				Ok(GlyphSubstitutionTable(value))
			},
			x => Err(ReadError::UnsupportedTableVersionPair(x.0, x.1))
		}
	}
}