mttf 0.1.4

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

use std::ops::Index;
use std::fmt::{ Debug, Formatter, Result as FmtResult };

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

// Glyph Names

const STANDARD_MACINTOSH_GLYPH_NAMES: [&str; 258] = [
	".notdef",
	".null",
	"nonmarkingreturn",
	"space",
	"exclam",
	"quotedbl",
	"numbersign",
	"dollar",
	"percent",
	"ampersand",
	"quotesingle",
	"parenleft",
	"parenright",
	"asterisk",
	"plus",
	"comma",
	"hyphen",
	"period",
	"slash",
	"zero",
	"one",
	"two",
	"three",
	"four",
	"five",
	"six",
	"seven",
	"eight",
	"nine",
	"colon",
	"semicolon",
	"less",
	"equal",
	"greater",
	"question",
	"at",
	"A",
	"B",
	"C",
	"D",
	"E",
	"F",
	"G",
	"H",
	"I",
	"J",
	"K",
	"L",
	"M",
	"N",
	"O",
	"P",
	"Q",
	"R",
	"S",
	"T",
	"U",
	"V",
	"W",
	"X",
	"Y",
	"Z",
	"bracketleft",
	"backslash",
	"bracketright",
	"asciicircum",
	"underscore",
	"grave",
	"a",
	"b",
	"c",
	"d",
	"e",
	"f",
	"g",
	"h",
	"i",
	"j",
	"k",
	"l",
	"m",
	"n",
	"o",
	"p",
	"q",
	"r",
	"s",
	"t",
	"u",
	"v",
	"w",
	"x",
	"y",
	"z",
	"braceleft",
	"bar",
	"braceright",
	"asciitilde",
	"Adieresis",
	"Aring",
	"Ccedilla",
	"Eacute",
	"Ntilde",
	"Odieresis",
	"Udieresis",
	"aacute",
	"agrave",
	"acircumflex",
	"adieresis",
	"atilde",
	"aring",
	"ccedilla",
	"eacute",
	"egrave",
	"ecircumflex",
	"edieresis",
	"iacute",
	"igrave",
	"icircumflex",
	"idieresis",
	"ntilde",
	"oacute",
	"ograve",
	"ocircumflex",
	"odieresis",
	"otilde",
	"uacute",
	"ugrave",
	"ucircumflex",
	"udieresis",
	"dagger",
	"degree",
	"cent",
	"sterling",
	"section",
	"bullet",
	"paragraph",
	"germandbls",
	"registered",
	"copyright",
	"trademark",
	"acute",
	"dieresis",
	"notequal",
	"AE",
	"Oslash",
	"infinity",
	"plusminus",
	"lessequal",
	"greaterequal",
	"yen",
	"mu",
	"partialdiff",
	"summation",
	"product",
	"pi",
	"integral",
	"ordfeminine",
	"ordmasculine",
	"Omega",
	"ae",
	"oslash",
	"questiondown",
	"exclamdown",
	"logicalnot",
	"radical",
	"florin",
	"approxequal",
	"Delta",
	"guillemotleft",
	"guillemotright",
	"ellipsis",
	"nonbreakingspace",
	"Agrave",
	"Atilde",
	"Otilde",
	"OE",
	"oe",
	"endash",
	"emdash",
	"quotedblleft",
	"quotedblright",
	"quoteleft",
	"quoteright",
	"divide",
	"lozenge",
	"ydieresis",
	"Ydieresis",
	"fraction",
	"currency",
	"guilsinglleft",
	"guilsinglright",
	"fi",
	"fl",
	"daggerdbl",
	"periodcentered",
	"quotesinglbase",
	"quotedblbase",
	"perthousand",
	"Acircumflex",
	"Ecircumflex",
	"Aacute",
	"Edieresis",
	"Egrave",
	"Iacute",
	"Icircumflex",
	"Idieresis",
	"Igrave",
	"Oacute",
	"Ocircumflex",
	"apple",
	"Ograve",
	"Uacute",
	"Ucircumflex",
	"Ugrave",
	"dotlessi",
	"circumflex",
	"tilde",
	"macron",
	"breve",
	"dotaccent",
	"ring",
	"cedilla",
	"hungarumlaut",
	"ogonek",
	"caron",
	"Lslash",
	"lslash",
	"Scaron",
	"scaron",
	"Zcaron",
	"zcaron",
	"brokenbar",
	"Eth",
	"eth",
	"Yacute",
	"yacute",
	"Thorn",
	"thorn",
	"minus",
	"multiply",
	"onesuperior",
	"twosuperior",
	"threesuperior",
	"onehalf",
	"onequarter",
	"threequarters",
	"franc",
	"Gbreve",
	"gbreve",
	"Idotaccent",
	"Scedilla",
	"scedilla",
	"Cacute",
	"cacute",
	"Ccaron",
	"ccaron",
	"dcroat",
];

#[derive(Clone, Copy)]
pub enum GlyphNames<'a> {
	Standard,
	Subset(&'a [u8]),
	Superset(&'a [u8]),
}

impl<'a> GlyphNames<'a> {
	pub fn len(&self) -> u16 {
		match self {
			Self::Standard => 258,
			Self::Subset(x) => x.uint16(0),
			Self::Superset(x) => x.uint16(0),
		}
	}
	pub fn is_empty(&self) -> bool {
		self.len() == 0
	}
	pub fn get(&self, index: u16) -> &'a str {
		match self {
			Self::Standard => STANDARD_MACINTOSH_GLYPH_NAMES[index as usize],
			Self::Subset(x) => {
				let offset = x.int8(2 + index as usize);
				let index = index as i32 + offset as i32;
				STANDARD_MACINTOSH_GLYPH_NAMES[index as usize]
			},
			Self::Superset(x) => {
				let index = x.uint16(2 + index as usize * 2);
				if index < 258 {
					return STANDARD_MACINTOSH_GLYPH_NAMES[index as usize];
				}
				let index = index - 258;
				let mut data = &x[2 + x.uint16(0) as usize * 2..];
				for _ in 0..index {
					let end = 1 + data[0] as usize;
					data = &data[end..];
				}
				let end = 1 + data[0] as usize;
				let data = &data[1..end];
				std::str::from_utf8(data).unwrap() // TODO this may fail, but the data should actually be ASCII
			},
		}
	}
	pub fn iter(&self) -> impl ExactSizeIterator<Item = &str> {
		(0..self.len()).map(|x| self.get(x))
	}
}

impl<'a> Index<u16> for GlyphNames<'a> {
	type Output = str;
	fn index(&self, index: u16) -> &Self::Output { self.get(index) }
}

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

// Table

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

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

impl<'a> PostScriptTable<'a> {
	pub fn version(&self) -> Version16Dot16 { self.version16dot16(0) }
	pub fn italic_angle(&self) -> Fixed { self.fixed(4) }
	pub fn underline_position(&self) -> i16 { self.int16(8) }
	pub fn underline_thickness(&self) -> i16 { self.int16(10) }
	pub fn is_fixed_pitch(&self) -> u32 { self.uint32(12) }
	pub fn min_mem_type42(&self) -> u32 { self.uint32(16) }
	pub fn max_mem_type42(&self) -> u32 { self.uint32(20) }
	pub fn min_mem_type1(&self) -> u32 { self.uint32(24) }
	pub fn max_mem_type1(&self) -> u32 { self.uint32(28) }
	pub fn glyph_names(&self) -> Option<GlyphNames<'a>> {
		match self.version() {
			Version16Dot16(0x0001, 0x0000) => Some(GlyphNames::Standard),
			Version16Dot16(0x0002, 0x0000) => Some(GlyphNames::Superset(&self.0[32..])),
			Version16Dot16(0x0002, 0x5000) => Some(GlyphNames::Subset(&self.0[32..])),
			Version16Dot16(0x0003, 0x0000) => None,
			_ => unreachable!(),
		}
	}
}

impl Debug for PostScriptTable<'_> {
	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
		f.debug_struct("PostScriptTable")
			.field("version", &self.version())
			.field("italic_angle", &self.italic_angle())
			.field("underline_position", &self.underline_position())
			.field("underline_thickness", &self.underline_thickness())
			.field("is_fixed_pitch", &self.is_fixed_pitch())
			.field("min_mem_type42", &self.min_mem_type42())
			.field("max_mem_type42", &self.max_mem_type42())
			.field("min_mem_type1", &self.min_mem_type1())
			.field("max_mem_type1", &self.max_mem_type1())
			.field("glyph_names", &self.glyph_names())
			.finish()
	}
}

impl<'a> TryFrom<&'a [u8]> for PostScriptTable<'a> {
	type Error = ReadError;
	fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
		if value.len() < 32 { return Err(ReadError::UnexpectedEof); }
		match value.version16dot16(0) {
			Version16Dot16(0x0001, 0x0000) => Ok(PostScriptTable(value)),
			Version16Dot16(0x0002, 0x0000) => {
				// TODO validate size
				Ok(PostScriptTable(value))
			},
			Version16Dot16(0x0002, 0x5000) => {
				// TODO validate size
				Ok(PostScriptTable(value))
			},
			Version16Dot16(0x0003, 0x0000) => Ok(PostScriptTable(value)),
			x => Err(ReadError::UnsupportedTableVersion16Dot16(x))
		}
	}
}