//! A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2) //! implementation. use crate::LineMetrics; use crate::parser::Stream; const WEIGHT_CLASS_OFFSET: usize = 4; const WIDTH_CLASS_OFFSET: usize = 6; const Y_SUBSCRIPT_X_SIZE_OFFSET: usize = 10; const Y_SUPERSCRIPT_X_SIZE_OFFSET: usize = 18; const Y_STRIKEOUT_SIZE_OFFSET: usize = 26; const Y_STRIKEOUT_POSITION_OFFSET: usize = 28; const FS_SELECTION_OFFSET: usize = 62; const TYPO_ASCENDER_OFFSET: usize = 68; const TYPO_DESCENDER_OFFSET: usize = 70; const TYPO_LINE_GAP_OFFSET: usize = 72; const WIN_ASCENT: usize = 74; const WIN_DESCENT: usize = 76; const X_HEIGHT_OFFSET: usize = 86; const CAP_HEIGHT_OFFSET: usize = 88; /// A face [weight](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass). #[allow(missing_docs)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] pub enum Weight { Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black, Other(u16), } impl Weight { /// Returns a numeric representation of a weight. #[inline] pub fn to_number(self) -> u16 { match self { Weight::Thin => 100, Weight::ExtraLight => 200, Weight::Light => 300, Weight::Normal => 400, Weight::Medium => 500, Weight::SemiBold => 600, Weight::Bold => 700, Weight::ExtraBold => 800, Weight::Black => 900, Weight::Other(n) => n, } } } impl From for Weight { #[inline] fn from(value: u16) -> Self { match value { 100 => Weight::Thin, 200 => Weight::ExtraLight, 300 => Weight::Light, 400 => Weight::Normal, 500 => Weight::Medium, 600 => Weight::SemiBold, 700 => Weight::Bold, 800 => Weight::ExtraBold, 900 => Weight::Black, _ => Weight::Other(value), } } } impl Default for Weight { #[inline] fn default() -> Self { Weight::Normal } } /// A face [width](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass). #[allow(missing_docs)] #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] pub enum Width { UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, Normal, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded, } impl Width { /// Returns a numeric representation of a width. #[inline] pub fn to_number(self) -> u16 { match self { Width::UltraCondensed => 1, Width::ExtraCondensed => 2, Width::Condensed => 3, Width::SemiCondensed => 4, Width::Normal => 5, Width::SemiExpanded => 6, Width::Expanded => 7, Width::ExtraExpanded => 8, Width::UltraExpanded => 9, } } } impl Default for Width { #[inline] fn default() -> Self { Width::Normal } } /// A face style. #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum Style { /// A face that is neither italic not obliqued. Normal, /// A form that is generally cursive in nature. Italic, /// A typically-sloped version of the regular face. Oblique, } impl Default for Style { #[inline] fn default() -> Style { Style::Normal } } /// A script metrics used by subscript and superscript. #[repr(C)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] pub struct ScriptMetrics { /// Horizontal face size. pub x_size: i16, /// Vertical face size. pub y_size: i16, /// X offset. pub x_offset: i16, /// Y offset. pub y_offset: i16, } // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection #[derive(Clone, Copy)] struct SelectionFlags(u16); impl SelectionFlags { #[inline] fn italic(self) -> bool { self.0 & (1 << 0) != 0 } #[inline] fn bold(self) -> bool { self.0 & (1 << 5) != 0 } // #[inline] fn regular(self) -> bool { self.0 & (1 << 6) != 0 } #[inline] fn use_typo_metrics(self) -> bool { self.0 & (1 << 7) != 0 } #[inline] fn oblique(self) -> bool { self.0 & (1 << 9) != 0 } } /// A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2). #[derive(Clone, Copy)] pub struct Table<'a> { /// Table version. pub version: u8, data: &'a [u8], } impl<'a> Table<'a> { /// Parses a table from raw data. pub fn parse(data: &'a [u8]) -> Option { let mut s = Stream::new(data); let version = s.read::()?; let table_len = match version { 0 => 78, 1 => 86, 2 => 96, 3 => 96, 4 => 96, 5 => 100, _ => return None, }; if data.len() != table_len { return None; } Some(Table { version: version as u8, data, }) } /// Returns weight class. #[inline] pub fn weight(&self) -> Weight { Weight::from(Stream::read_at::(self.data, WEIGHT_CLASS_OFFSET).unwrap_or(0)) } /// Returns face width. #[inline] pub fn width(&self) -> Width { match Stream::read_at::(self.data, WIDTH_CLASS_OFFSET).unwrap_or(0) { 1 => Width::UltraCondensed, 2 => Width::ExtraCondensed, 3 => Width::Condensed, 4 => Width::SemiCondensed, 5 => Width::Normal, 6 => Width::SemiExpanded, 7 => Width::Expanded, 8 => Width::ExtraExpanded, 9 => Width::UltraExpanded, _ => Width::Normal, } } /// Returns subscript metrics. #[inline] pub fn subscript_metrics(&self) -> ScriptMetrics { let mut s = Stream::new_at(self.data, Y_SUBSCRIPT_X_SIZE_OFFSET).unwrap_or_default(); ScriptMetrics { x_size: s.read::().unwrap_or(0), y_size: s.read::().unwrap_or(0), x_offset: s.read::().unwrap_or(0), y_offset: s.read::().unwrap_or(0), } } /// Returns superscript metrics. #[inline] pub fn superscript_metrics(&self) -> ScriptMetrics { let mut s = Stream::new_at(self.data, Y_SUPERSCRIPT_X_SIZE_OFFSET).unwrap_or_default(); ScriptMetrics { x_size: s.read::().unwrap_or(0), y_size: s.read::().unwrap_or(0), x_offset: s.read::().unwrap_or(0), y_offset: s.read::().unwrap_or(0), } } /// Returns strikeout metrics. #[inline] pub fn strikeout_metrics(&self) -> LineMetrics { LineMetrics { thickness: Stream::read_at::(self.data, Y_STRIKEOUT_SIZE_OFFSET).unwrap_or(0), position: Stream::read_at::(self.data, Y_STRIKEOUT_POSITION_OFFSET).unwrap_or(0), } } #[inline] fn fs_selection(&self) -> u16 { Stream::read_at::(self.data, FS_SELECTION_OFFSET).unwrap_or(0) } /// Returns style. pub fn style(&self) -> Style { let flags = SelectionFlags(self.fs_selection()); if flags.italic() { Style::Italic } else if self.version >= 4 && flags.oblique() { Style::Oblique } else { Style::Normal } } /// Checks if face is bold. /// /// Do not confuse with [`Weight::Bold`]. #[inline] pub fn is_bold(&self) -> bool { SelectionFlags(self.fs_selection()).bold() } /// Checks if typographic metrics should be used. #[inline] pub fn use_typographic_metrics(&self) -> bool { if self.version < 4 { false } else { SelectionFlags(self.fs_selection()).use_typo_metrics() } } /// Returns typographic ascender. #[inline] pub fn typographic_ascender(&self) -> i16 { Stream::read_at::(self.data, TYPO_ASCENDER_OFFSET).unwrap_or(0) } /// Returns typographic descender. #[inline] pub fn typographic_descender(&self) -> i16 { Stream::read_at::(self.data, TYPO_DESCENDER_OFFSET).unwrap_or(0) } /// Returns typographic line gap. #[inline] pub fn typographic_line_gap(&self) -> i16 { Stream::read_at::(self.data, TYPO_LINE_GAP_OFFSET).unwrap_or(0) } /// Returns Windows ascender. #[inline] pub fn windows_ascender(&self) -> i16 { Stream::read_at::(self.data, WIN_ASCENT).unwrap_or(0) } /// Returns Windows descender. #[inline] pub fn windows_descender(&self) -> i16 { // Should be negated. -Stream::read_at::(self.data, WIN_DESCENT).unwrap_or(0) } /// Returns x height. /// /// Returns `None` version is < 2. #[inline] pub fn x_height(&self) -> Option { if self.version < 2 { None } else { Stream::read_at::(self.data, X_HEIGHT_OFFSET) } } /// Returns capital height. /// /// Returns `None` version is < 2. #[inline] pub fn capital_height(&self) -> Option { if self.version < 2 { None } else { Stream::read_at::(self.data, CAP_HEIGHT_OFFSET) } } } impl core::fmt::Debug for Table<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Table {{ ... }}") } }