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, 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, 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 { 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, 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, 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, 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, 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, ReadError>> + '_ { (0..self.count()).map(|x| self.get(x)) } pub fn get_by_prefix(&self, prefix_glyph: u16) -> Result>, 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 { // 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, 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, 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, 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, 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, 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 { 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 { 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)) } } }