mttf 0.1.7

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

use thiserror::Error;
use num_enum::{ FromPrimitive, IntoPrimitive };
use std::ops::Range;
use std::fmt::{ Debug, Formatter, Result as FmtResult };
use bytemuck::{ Zeroable, Pod };

use mchr::Pullable;
use mchr::str::StdDecoder;

pub type BytesIter<'a> = std::iter::Copied<std::slice::Iter<'a, u8>>;

use super::ReadError;
use super::core::*;
use super::common::*;

// Name Record

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(FromPrimitive, IntoPrimitive)]
#[repr(u16)]
pub enum NameKind {
	CopyrightNotice = 0,
	FamilyName = 1,
	FontSubfamily = 2,
	UniqueSubfamilyId = 3,
	FullName = 4,
	Version = 5,
	PostScriptName = 6,
	TrademarkNotice = 7,
	Manufacturer = 8,
	Designer = 9,
	Description = 10,
	VendorUrl = 11,
	DesignerUrl = 12,
	LicenseDescription = 13,
	LicenseUrl = 14,
	PreferredFamily = 16,
	PreferredSubfamily = 17,
	CompatibleFull = 18,
	SampleText = 19,
	// TODO 20-24
	VariationsPostScriptNamePrefix = 25,

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

#[derive(Clone, Copy, Zeroable, Pod)]
#[repr(transparent)]
pub struct NameRecord([u8; 12]);

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

impl NameRecord {
	pub fn encoding_and_language(&self) -> PlatformEncodingAndLanguage { (self.uint16(0), self.uint16(2), self.uint16(4)).into() }
	pub fn kind(&self) -> NameKind { NameKind::from(self.uint16(6)) }
	pub fn text(&self) -> Range<u16> {
		let start = self.uint16(10);
		let stop = start + self.uint16(8);
		start..stop
	}
}

impl Debug for NameRecord {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("NameRecord")
			.field("encoding_and_language", &self.encoding_and_language())
			.field("kind", &self.kind())
			.field("text", &self.text())
			.finish()
	}
}

// Language Tag Record

#[derive(Clone, Copy, Zeroable, Pod)]
#[repr(transparent)]
pub struct LangTagRecord([u8; 4]);

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

impl LangTagRecord {
	pub fn text(&self) -> Range<u16> {
		let start = self.uint16(2);
		let stop = start + self.uint16(0);
		start..stop
	}
}

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

// Naming Table

#[derive(Error, Debug)]
pub enum StringError {

	#[error("invalid string range: {0:?}")]
	InvalidStringRange(Range<u16>),

	#[error("unsupported encoding: {0:?}")]
	UnsupportedPlatformEncoding(PlatformEncodingAndLanguage),
}

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

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

impl<'a> NamingTable<'a> {
	pub fn version(&self) -> u16 { self.uint16(0) }
	pub fn name_count(&self) -> u16 { self.uint16(2) }
	pub fn string_offset(&self) -> u16 { self.uint16(4) }
	pub fn name_records(&self) -> &'a [NameRecord] { self.array(6, self.name_count() as usize) }

	pub fn string_data(&self, range: Range<u16>) -> Result<&'a [u8], Range<u16>> {
		let offset = self.string_offset() as usize;
		let start = offset + range.start as usize;
		let stop = offset + range.end as usize;
		let data = self.bytes();
		if stop <= data.len() {
			Ok(&data[start..stop])
		} else {
			Err(range)
		}
	}

	pub fn string(&self, record: &NameRecord) -> Result<StdDecoder<BytesIter<'a>>, StringError> {
		let data = self.string_data(record.text()).map_err(StringError::InvalidStringRange)?;
		let encoding = record.encoding_and_language();
		let encoding = encoding.character_encoding().ok_or(StringError::UnsupportedPlatformEncoding(encoding))?;
		Ok(data.iter().copied().transcode(encoding))
	}
}

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

impl<'a> TryFrom<&'a [u8]> for NamingTable<'a> {
	type Error = ReadError;
	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
		if value.len() < 6 {
			return Err(ReadError::UnexpectedEof);
		}

		let value = NamingTable(value);

		let version = value.version();
		if !matches!(version, 0 | 1) {
			return Err(ReadError::UnsupportedTableVersionSingle(version));
		}

		if value.bytes().len() < 6 + value.name_count() as usize * 12 {
			return Err(ReadError::UnexpectedEof);
		}

		Ok(value)
	}
}

// Iterator

#[derive(Clone, Copy)]
pub struct Name<'a> {
	table: NamingTable<'a>,
	record: &'a NameRecord,
}

impl<'a> Name<'a> {
	pub fn encoding_and_language(&self) -> PlatformEncodingAndLanguage { self.record.encoding_and_language() }
	pub fn kind(&self) -> NameKind { self.record.kind() }
	pub fn text(&self) -> Result<StdDecoder<BytesIter<'a>>, StringError> { self.table.string(self.record) }
}

impl<'a> NamingTable<'a> {
	pub fn iter(&self) -> impl ExactSizeIterator<Item = Name<'a>> + '_ {
		self.name_records().iter().map(|record| Name { table: *self, record })
	}
}

impl<'a> Debug for Name<'a> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("Name")
			.field("encoding_and_language", &self.encoding_and_language())
			.field("kind", &self.kind())
			.field("text", &self.text().map(|x| x.buffered().collect::<String>()))
			.finish()
	}
}