From 88aaffac51184993ce29ea03d1842d262682c4a6 Mon Sep 17 00:00:00 2001 From: Ruifeng Xie Date: Wed, 28 Sep 2022 00:12:56 +0800 Subject: [PATCH] Add `MATH` table. --- .gitignore | 1 + Cargo.toml | 2 +- README.md | 1 + src/lib.rs | 7 +- src/parser.rs | 6 + src/tables/math.rs | 904 +++++++++++++++++++++++++++++++++++++++++++++ src/tables/mod.rs | 1 + 7 files changed, 920 insertions(+), 2 deletions(-) create mode 100644 src/tables/math.rs diff --git a/.gitignore b/.gitignore index 0d961ad8..41189dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Cargo.lock .directory .DS_Store .vscode +.idea diff --git a/Cargo.toml b/Cargo.toml index 85c3b781..2bd7efba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ std = [] # Enables variable fonts support. Increases binary size almost twice. # Includes avar, CFF2, fvar, gvar, HVAR, MVAR and VVAR tables. variable-fonts = [] -# Enables GDEF, GPOS and GSUB tables. +# Enables GDEF, GPOS, GSUB and MATH tables. opentype-layout = [] # Enables ankr, feat, format1 subtable in kern, kerx, morx and trak tables. apple-layout = [] diff --git a/README.md b/README.md index 04bd76a4..b70792f8 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ There are roughly three types of TrueType tables: | `HVAR` table | ✓ | ✓ | | | `kern` table | ✓ | ~ (only 0) | ~ (only 0) | | `kerx` table | ✓ | | | +| `MATH` table | ✓ | | | | `maxp` table | ✓ | ✓ | ✓ | | `morx` table | ✓ | | | | `MVAR` table | ✓ | ✓ | | diff --git a/src/lib.rs b/src/lib.rs index 2ea24f4b..fa61f3b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ pub use os2::{Weight, Width, ScriptMetrics, Style}; pub use tables::CFFError; pub use tables::{cmap, kern, sbix, maxp, hmtx, name, os2, loca, svg, vorg, post, head, hhea, glyf}; pub use tables::{cff1 as cff, vhea, cbdt, cblc}; -#[cfg(feature = "opentype-layout")] pub use tables::{gdef, gpos, gsub}; +#[cfg(feature = "opentype-layout")] pub use tables::{gdef, gpos, gsub, math}; #[cfg(feature = "apple-layout")] pub use tables::{ankr, feat, kerx, morx, trak}; #[cfg(feature = "variable-fonts")] pub use tables::{cff2, avar, fvar, gvar, hvar, mvar}; @@ -680,6 +680,7 @@ pub struct RawFaceTables<'a> { #[cfg(feature = "opentype-layout")] pub gdef: Option<&'a [u8]>, #[cfg(feature = "opentype-layout")] pub gpos: Option<&'a [u8]>, #[cfg(feature = "opentype-layout")] pub gsub: Option<&'a [u8]>, + #[cfg(feature = "opentype-layout")] pub math: Option<&'a [u8]>, #[cfg(feature = "apple-layout")] pub ankr: Option<&'a [u8]>, #[cfg(feature = "apple-layout")] pub feat: Option<&'a [u8]>, @@ -730,6 +731,7 @@ pub struct FaceTables<'a> { #[cfg(feature = "opentype-layout")] pub gdef: Option>, #[cfg(feature = "opentype-layout")] pub gpos: Option>, #[cfg(feature = "opentype-layout")] pub gsub: Option>, + #[cfg(feature = "opentype-layout")] pub math: Option>, #[cfg(feature = "apple-layout")] pub ankr: Option>, #[cfg(feature = "apple-layout")] pub feat: Option>, @@ -841,6 +843,8 @@ impl<'a> Face<'a> { b"GPOS" => tables.gpos = table_data, #[cfg(feature = "opentype-layout")] b"GSUB" => tables.gsub = table_data, + #[cfg(feature = "opentype-layout")] + b"MATH" => tables.math = table_data, #[cfg(feature = "variable-fonts")] b"HVAR" => tables.hvar = table_data, #[cfg(feature = "variable-fonts")] @@ -963,6 +967,7 @@ impl<'a> Face<'a> { #[cfg(feature = "opentype-layout")] gdef: raw_tables.gdef.and_then(gdef::Table::parse), #[cfg(feature = "opentype-layout")] gpos: raw_tables.gpos.and_then(opentype_layout::LayoutTable::parse), #[cfg(feature = "opentype-layout")] gsub: raw_tables.gsub.and_then(opentype_layout::LayoutTable::parse), + #[cfg(feature = "opentype-layout")] math: raw_tables.math.and_then(math::Table::parse), #[cfg(feature = "apple-layout")] ankr: raw_tables.ankr .and_then(|data| ankr::Table::parse(maxp.number_of_glyphs, data)), diff --git a/src/parser.rs b/src/parser.rs index fdfef38c..57f53143 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -760,6 +760,12 @@ impl<'a> Stream<'a> { data.get(offset..) } + #[allow(dead_code)] + #[inline] + pub(crate) fn parse_at_offset16>(&mut self, data: &'a [u8]) -> Option { + self.read_at_offset16(data).and_then(T::parse) + } + #[allow(dead_code)] #[inline] pub(crate) fn read_at_offset32(&mut self, data: &'a [u8]) -> Option<&'a [u8]> { diff --git a/src/tables/math.rs b/src/tables/math.rs new file mode 100644 index 00000000..84aed247 --- /dev/null +++ b/src/tables/math.rs @@ -0,0 +1,904 @@ +//! A [Math Table](https://docs.microsoft.com/en-us/typography/opentype/spec/math) implementation. + +use crate::GlyphId; +use crate::gpos::Device; +use crate::opentype_layout::Coverage; +use crate::parser::{Offset16, Stream, FromSlice, FromData, Offset, LazyArray16, LazyOffsetArray16}; + +/// A [Math Value](https://docs.microsoft.com/en-us/typography/opentype/spec/math#mathvaluerecord) +/// with optional device corrections. +#[derive(Clone, Copy, Debug)] +pub struct MathValue<'a> { + /// The X or Y value in font design units. + pub value: i16, + /// Device corrections for this value. + pub device: Option>, +} + +impl<'a> MathValue<'a> { + fn parse(data: &'a [u8], parent: &'a [u8]) -> Option { + Some(MathValueRecord::parse(data)?.get(parent)) + } +} + + +/// A math value record with unresolved offset. +#[derive(Clone, Copy)] +struct MathValueRecord { + value: i16, + device_offset: Option, +} + +impl FromData for MathValueRecord { + const SIZE: usize = 4; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + let value = s.read::()?; + let device_offset = s.read::>()?; + Some(MathValueRecord { value, device_offset }) + } +} + +impl MathValueRecord { + fn get(self, data: &[u8]) -> MathValue { + let device = self.device_offset + .and_then(|offset| data.get(offset.to_usize()..)) + .and_then(Device::parse); + MathValue { value: self.value, device } + } +} + + +/// A mapping from glyphs to +/// [Math Values](https://docs.microsoft.com/en-us/typography/opentype/spec/math#mathvaluerecord). +#[derive(Clone, Copy)] +pub struct MathValues<'a> { + data: &'a [u8], + coverage: Coverage<'a>, + records: LazyArray16<'a, MathValueRecord>, +} + +impl<'a> FromSlice<'a> for MathValues<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let coverage = s.parse_at_offset16::(data)?; + let count = s.read::()?; + let records = s.read_array16::(count)?; + Some(MathValues {data, coverage, records }) + } +} + +impl<'a> MathValues<'a> { + /// Returns the value for the glyph or `None` if it is not covered. + #[inline] + pub fn get(&self, glyph: GlyphId) -> Option> { + let index = self.coverage.get(glyph)?; + Some(self.records.get(index)?.get(self.data)) + } +} + +impl core::fmt::Debug for MathValues<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "MathValues {{ ... }}") + } +} + + +/// A [Math Constants Table](https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathconstants-table). +#[derive(Clone, Copy)] +pub struct Constants<'a> { + data: &'a [u8], +} + +impl<'a> FromSlice<'a> for Constants<'a> { + fn parse(data: &'a [u8]) -> Option { + Some(Constants { data }) + } +} + +impl core::fmt::Debug for Constants<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Constants {{ ... }}") + } +} + + +const SCRIPT_PERCENT_SCALE_DOWN_OFFSET: usize = 0; +const SCRIPT_SCRIPT_PERCENT_SCALE_DOWN_OFFSET: usize = 2; +const DELIMITED_SUB_FORMULA_MIN_HEIGHT_OFFSET: usize = 4; +const DISPLAY_OPERATOR_MIN_HEIGHT_OFFSET: usize = 6; +const MATH_LEADING_OFFSET: usize = 8; +const AXIS_HEIGHT_OFFSET: usize = 12; +const ACCENT_BASE_HEIGHT_OFFSET: usize = 16; +const FLATTENED_ACCENT_BASE_HEIGHT_OFFSET: usize = 20; +const SUBSCRIPT_SHIFT_DOWN_OFFSET: usize = 24; +const SUBSCRIPT_TOP_MAX_OFFSET: usize = 28; +const SUBSCRIPT_BASELINE_DROP_MIN_OFFSET: usize = 32; +const SUPERSCRIPT_SHIFT_UP_OFFSET: usize = 36; +const SUPERSCRIPT_SHIFT_UP_CRAMPED_OFFSET: usize = 40; +const SUPERSCRIPT_BOTTOM_MIN_OFFSET: usize = 44; +const SUPERSCRIPT_BASELINE_DROP_MAX_OFFSET: usize = 48; +const SUB_SUPERSCRIPT_GAP_MIN_OFFSET: usize = 52; +const SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT_OFFSET: usize = 56; +const SPACE_AFTER_SCRIPT_OFFSET: usize = 60; +const UPPER_LIMIT_GAP_MIN_OFFSET: usize = 64; +const UPPER_LIMIT_BASELINE_RISE_MIN_OFFSET: usize = 68; +const LOWER_LIMIT_GAP_MIN_OFFSET: usize = 72; +const LOWER_LIMIT_BASELINE_DROP_MIN_OFFSET: usize = 76; +const STACK_TOP_SHIFT_UP_OFFSET: usize = 80; +const STACK_TOP_DISPLAY_STYLE_SHIFT_UP_OFFSET: usize = 84; +const STACK_BOTTOM_SHIFT_DOWN_OFFSET: usize = 88; +const STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN_OFFSET: usize = 92; +const STACK_GAP_MIN_OFFSET: usize = 96; +const STACK_DISPLAY_STYLE_GAP_MIN_OFFSET: usize = 100; +const STRETCH_STACK_TOP_SHIFT_UP_OFFSET: usize = 104; +const STRETCH_STACK_BOTTOM_SHIFT_DOWN_OFFSET: usize = 108; +const STRETCH_STACK_GAP_ABOVE_MIN_OFFSET: usize = 112; +const STRETCH_STACK_GAP_BELOW_MIN_OFFSET: usize = 116; +const FRACTION_NUMERATOR_SHIFT_UP_OFFSET: usize = 120; +const FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP_OFFSET: usize = 124; +const FRACTION_DENOMINATOR_SHIFT_DOWN_OFFSET: usize = 128; +const FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN_OFFSET: usize = 132; +const FRACTION_NUMERATOR_GAP_MIN_OFFSET: usize = 136; +const FRACTION_NUM_DISPLAY_STYLE_GAP_MIN_OFFSET: usize = 140; +const FRACTION_RULE_THICKNESS_OFFSET: usize = 144; +const FRACTION_DENOMINATOR_GAP_MIN_OFFSET: usize = 148; +const FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN_OFFSET: usize = 152; +const SKEWED_FRACTION_HORIZONTAL_GAP_OFFSET: usize = 156; +const SKEWED_FRACTION_VERTICAL_GAP_OFFSET: usize = 160; +const OVERBAR_VERTICAL_GAP_OFFSET: usize = 164; +const OVERBAR_RULE_THICKNESS_OFFSET: usize = 168; +const OVERBAR_EXTRA_ASCENDER_OFFSET: usize = 172; +const UNDERBAR_VERTICAL_GAP_OFFSET: usize = 176; +const UNDERBAR_RULE_THICKNESS_OFFSET: usize = 180; +const UNDERBAR_EXTRA_DESCENDER_OFFSET: usize = 184; +const RADICAL_VERTICAL_GAP_OFFSET: usize = 188; +const RADICAL_DISPLAY_STYLE_VERTICAL_GAP_OFFSET: usize = 192; +const RADICAL_RULE_THICKNESS_OFFSET: usize = 196; +const RADICAL_EXTRA_ASCENDER_OFFSET: usize = 200; +const RADICAL_KERN_BEFORE_DEGREE_OFFSET: usize = 204; +const RADICAL_KERN_AFTER_DEGREE_OFFSET: usize = 208; +const RADICAL_DEGREE_BOTTOM_RAISE_PERCENT_OFFSET: usize = 212; + + +impl<'a> Constants<'a> { + /// Percentage of scaling down for level 1 superscripts and subscripts. + #[inline] + pub fn script_percent_scale_down(&self) -> i16 { + self.read_i16(SCRIPT_PERCENT_SCALE_DOWN_OFFSET) + } + + /// Percentage of scaling down for level 2 (scriptScript) superscripts and subscripts. + #[inline] + pub fn script_script_percent_scale_down(&self) -> i16 { + self.read_i16(SCRIPT_SCRIPT_PERCENT_SCALE_DOWN_OFFSET) + } + + /// Minimum height required for a delimited expression (contained within parentheses, etc.) to + /// be treated as a sub-formula. + #[inline] + pub fn delimited_sub_formula_min_height(&self) -> u16 { + self.read_u16(DELIMITED_SUB_FORMULA_MIN_HEIGHT_OFFSET) + } + + /// Minimum height of n-ary operators (such as integral and summation) for formulas in display + /// mode (that is, appearing as standalone page elements, not embedded inline within text). + #[inline] + pub fn display_operator_min_height(&self) -> u16 { + self.read_u16(DISPLAY_OPERATOR_MIN_HEIGHT_OFFSET) + } + + /// White space to be left between math formulas to ensure proper line spacing. + #[inline] + pub fn math_leading(&self) -> MathValue<'a> { + self.read_record(MATH_LEADING_OFFSET) + } + + /// Axis height of the font. + #[inline] + pub fn axis_height(&self) -> MathValue<'a> { + self.read_record(AXIS_HEIGHT_OFFSET) + } + + /// Maximum (ink) height of accent base that does not require raising the accents. + #[inline] + pub fn accent_base_height(&self) -> MathValue<'a> { + self.read_record(ACCENT_BASE_HEIGHT_OFFSET) + } + + /// Maximum (ink) height of accent base that does not require flattening the accents. + #[inline] + pub fn flattened_accent_base_height(&self) -> MathValue<'a> { + self.read_record(FLATTENED_ACCENT_BASE_HEIGHT_OFFSET) + } + + /// The standard shift down applied to subscript elements. + #[inline] + pub fn subscript_shift_down(&self) -> MathValue<'a> { + self.read_record(SUBSCRIPT_SHIFT_DOWN_OFFSET) + } + + /// Maximum allowed height of the (ink) top of subscripts that does not require moving + /// subscripts further down. + #[inline] + pub fn subscript_top_max(&self) -> MathValue<'a> { + self.read_record(SUBSCRIPT_TOP_MAX_OFFSET) + } + + /// Minimum allowed drop of the baseline of subscripts relative to the (ink) bottom of the + /// base. + #[inline] + pub fn subscript_baseline_drop_min(&self) -> MathValue<'a> { + self.read_record(SUBSCRIPT_BASELINE_DROP_MIN_OFFSET) + } + + /// Standard shift up applied to superscript elements. + #[inline] + pub fn superscript_shift_up(&self) -> MathValue<'a> { + self.read_record(SUPERSCRIPT_SHIFT_UP_OFFSET) + } + + /// Standard shift of superscripts relative to the base, in cramped style. + #[inline] + pub fn superscript_shift_up_cramped(&self) -> MathValue<'a> { + self.read_record(SUPERSCRIPT_SHIFT_UP_CRAMPED_OFFSET) + } + + /// Minimum allowed height of the (ink) bottom of superscripts that does not require moving + /// subscripts further up. + #[inline] + pub fn superscript_bottom_min(&self) -> MathValue<'a> { + self.read_record(SUPERSCRIPT_BOTTOM_MIN_OFFSET) + } + + /// Maximum allowed drop of the baseline of superscripts relative to the (ink) top of the + /// base. + #[inline] + pub fn superscript_baseline_drop_max(&self) -> MathValue<'a> { + self.read_record(SUPERSCRIPT_BASELINE_DROP_MAX_OFFSET) + } + + /// Minimum gap between the superscript and subscript ink. + #[inline] + pub fn sub_superscript_gap_min(&self) -> MathValue<'a> { + self.read_record(SUB_SUPERSCRIPT_GAP_MIN_OFFSET) + } + + /// The maximum level to which the (ink) bottom of superscript can be pushed to increase the + /// gap between superscript and subscript, before subscript starts being moved down. + #[inline] + pub fn superscript_bottom_max_with_subscript(&self) -> MathValue<'a> { + self.read_record(SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT_OFFSET) + } + + /// Extra white space to be added after each subscript and superscript. + #[inline] + pub fn space_after_script(&self) -> MathValue<'a> { + self.read_record(SPACE_AFTER_SCRIPT_OFFSET) + } + + /// Minimum gap between the (ink) bottom of the upper limit, and the (ink) top of the base + /// operator. + #[inline] + pub fn upper_limit_gap_min(&self) -> MathValue<'a> { + self.read_record(UPPER_LIMIT_GAP_MIN_OFFSET) + } + + /// Minimum distance between baseline of upper limit and (ink) top of the base operator. + #[inline] + pub fn upper_limit_baseline_rise_min(&self) -> MathValue<'a> { + self.read_record(UPPER_LIMIT_BASELINE_RISE_MIN_OFFSET) + } + + /// Minimum gap between (ink) top of the lower limit, and (ink) bottom of the base operator. + #[inline] + pub fn lower_limit_gap_min(&self) -> MathValue<'a> { + self.read_record(LOWER_LIMIT_GAP_MIN_OFFSET) + } + + /// Minimum distance between baseline of the lower limit and (ink) bottom of the base operator. + #[inline] + pub fn lower_limit_baseline_drop_min(&self) -> MathValue<'a> { + self.read_record(LOWER_LIMIT_BASELINE_DROP_MIN_OFFSET) + } + + /// Standard shift up applied to the top element of a stack. + #[inline] + pub fn stack_top_shift_up(&self) -> MathValue<'a> { + self.read_record(STACK_TOP_SHIFT_UP_OFFSET) + } + + /// Standard shift up applied to the top element of a stack in display style. + #[inline] + pub fn stack_top_display_style_shift_up(&self) -> MathValue<'a> { + self.read_record(STACK_TOP_DISPLAY_STYLE_SHIFT_UP_OFFSET) + } + + /// Standard shift down applied to the bottom element of a stack. + #[inline] + pub fn stack_bottom_shift_down(&self) -> MathValue<'a> { + self.read_record(STACK_BOTTOM_SHIFT_DOWN_OFFSET) + } + + /// Standard shift down applied to the bottom element of a stack in display style. + #[inline] + pub fn stack_bottom_display_style_shift_down(&self) -> MathValue<'a> { + self.read_record(STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN_OFFSET) + } + + /// Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the + /// bottom element. + #[inline] + pub fn stack_gap_min(&self) -> MathValue<'a> { + self.read_record(STACK_GAP_MIN_OFFSET) + } + + /// Minimum gap between (ink) bottom of the top element of a stack, and the (ink) top of the + /// bottom element in display style. + #[inline] + pub fn stack_display_style_gap_min(&self) -> MathValue<'a> { + self.read_record(STACK_DISPLAY_STYLE_GAP_MIN_OFFSET) + } + + /// Standard shift up applied to the top element of the stretch stack. + #[inline] + pub fn stretch_stack_top_shift_up(&self) -> MathValue<'a> { + self.read_record(STRETCH_STACK_TOP_SHIFT_UP_OFFSET) + } + + /// Standard shift down applied to the bottom element of the stretch stack. + #[inline] + pub fn stretch_stack_bottom_shift_down(&self) -> MathValue<'a> { + self.read_record(STRETCH_STACK_BOTTOM_SHIFT_DOWN_OFFSET) + } + + /// Minimum gap between the ink of the stretched element, and the (ink) bottom of the element above. + #[inline] + pub fn stretch_stack_gap_above_min(&self) -> MathValue<'a> { + self.read_record(STRETCH_STACK_GAP_ABOVE_MIN_OFFSET) + } + + /// Minimum gap between the ink of the stretched element, and the (ink) top of the element below. + #[inline] + pub fn stretch_stack_gap_below_min(&self) -> MathValue<'a> { + self.read_record(STRETCH_STACK_GAP_BELOW_MIN_OFFSET) + } + + /// Standard shift up applied to the numerator. + #[inline] + pub fn fraction_numerator_shift_up(&self) -> MathValue<'a> { + self.read_record(FRACTION_NUMERATOR_SHIFT_UP_OFFSET) + } + + /// Standard shift up applied to the numerator in display style. + #[inline] + pub fn fraction_numerator_display_style_shift_up(&self) -> MathValue<'a> { + self.read_record(FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP_OFFSET) + } + + /// Standard shift down applied to the denominator. + #[inline] + pub fn fraction_denominator_shift_down(&self) -> MathValue<'a> { + self.read_record(FRACTION_DENOMINATOR_SHIFT_DOWN_OFFSET) + } + + /// Standard shift down applied to the denominator in display style. + #[inline] + pub fn fraction_denominator_display_style_shift_down(&self) -> MathValue<'a> { + self.read_record(FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN_OFFSET) + } + + /// Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the + /// fraction bar. + #[inline] + pub fn fraction_numerator_gap_min(&self) -> MathValue<'a> { + self.read_record(FRACTION_NUMERATOR_GAP_MIN_OFFSET) + } + + /// Minimum tolerated gap between the (ink) bottom of the numerator and the ink of the + /// fraction bar in display style. + #[inline] + pub fn fraction_num_display_style_gap_min(&self) -> MathValue<'a> { + self.read_record(FRACTION_NUM_DISPLAY_STYLE_GAP_MIN_OFFSET) + } + + /// Thickness of the fraction bar. + #[inline] + pub fn fraction_rule_thickness(&self) -> MathValue<'a> { + self.read_record(FRACTION_RULE_THICKNESS_OFFSET) + } + + /// Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction bar. + #[inline] + pub fn fraction_denominator_gap_min(&self) -> MathValue<'a> { + self.read_record(FRACTION_DENOMINATOR_GAP_MIN_OFFSET) + } + + /// Minimum tolerated gap between the (ink) top of the denominator and the ink of the fraction + /// bar in display style. + #[inline] + pub fn fraction_denom_display_style_gap_min(&self) -> MathValue<'a> { + self.read_record(FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN_OFFSET) + } + + /// Horizontal distance between the top and bottom elements of a skewed fraction. + #[inline] + pub fn skewed_fraction_horizontal_gap(&self) -> MathValue<'a> { + self.read_record(SKEWED_FRACTION_HORIZONTAL_GAP_OFFSET) + } + + /// Vertical distance between the ink of the top and bottom elements of a skewed fraction. + #[inline] + pub fn skewed_fraction_vertical_gap(&self) -> MathValue<'a> { + self.read_record(SKEWED_FRACTION_VERTICAL_GAP_OFFSET) + } + + /// Distance between the overbar and the (ink) top of he base. + #[inline] + pub fn overbar_vertical_gap(&self) -> MathValue<'a> { + self.read_record(OVERBAR_VERTICAL_GAP_OFFSET) + } + + /// Thickness of overbar. + #[inline] + pub fn overbar_rule_thickness(&self) -> MathValue<'a> { + self.read_record(OVERBAR_RULE_THICKNESS_OFFSET) + } + + /// Extra white space reserved above the overbar. + #[inline] + pub fn overbar_extra_ascender(&self) -> MathValue<'a> { + self.read_record(OVERBAR_EXTRA_ASCENDER_OFFSET) + } + + /// Distance between underbar and (ink) bottom of the base. + #[inline] + pub fn underbar_vertical_gap(&self) -> MathValue<'a> { + self.read_record(UNDERBAR_VERTICAL_GAP_OFFSET) + } + + /// Thickness of underbar. + #[inline] + pub fn underbar_rule_thickness(&self) -> MathValue<'a> { + self.read_record(UNDERBAR_RULE_THICKNESS_OFFSET) + } + + /// Extra white space reserved below the underbar. + #[inline] + pub fn underbar_extra_descender(&self) -> MathValue<'a> { + self.read_record(UNDERBAR_EXTRA_DESCENDER_OFFSET) + } + + /// Space between the (ink) top of the expression and the bar over it. + #[inline] + pub fn radical_vertical_gap(&self) -> MathValue<'a> { + self.read_record(RADICAL_VERTICAL_GAP_OFFSET) + } + + /// Space between the (ink) top of the expression and the bar over it. + #[inline] + pub fn radical_display_style_vertical_gap(&self) -> MathValue<'a> { + self.read_record(RADICAL_DISPLAY_STYLE_VERTICAL_GAP_OFFSET) + } + + /// Thickness of the radical rule. + #[inline] + pub fn radical_rule_thickness(&self) -> MathValue<'a> { + self.read_record(RADICAL_RULE_THICKNESS_OFFSET) + } + + /// Extra white space reserved above the radical. + #[inline] + pub fn radical_extra_ascender(&self) -> MathValue<'a> { + self.read_record(RADICAL_EXTRA_ASCENDER_OFFSET) + } + + /// Extra horizontal kern before the degree of a radical, if such is present. + #[inline] + pub fn radical_kern_before_degree(&self) -> MathValue<'a> { + self.read_record(RADICAL_KERN_BEFORE_DEGREE_OFFSET) + } + + /// Negative kern after the degree of a radical, if such is present. + #[inline] + pub fn radical_kern_after_degree(&self) -> MathValue<'a> { + self.read_record(RADICAL_KERN_AFTER_DEGREE_OFFSET) + } + + /// Height of the bottom of the radical degree, if such is present, in proportion to the + /// ascender of the radical sign. + #[inline] + pub fn radical_degree_bottom_raise_percent(&self) -> i16 { + self.read_i16(RADICAL_DEGREE_BOTTOM_RAISE_PERCENT_OFFSET) + } + + /// Read an `i16` at an offset into the table. + #[inline] + fn read_i16(&self, offset: usize) -> i16 { + Stream::read_at(self.data, offset).unwrap_or(0) + } + + /// Read an `u16` at an offset into the table. + #[inline] + fn read_u16(&self, offset: usize) -> u16 { + Stream::read_at(self.data, offset).unwrap_or(0) + } + + /// Read a `MathValueRecord` at an offset into the table. + #[inline] + fn read_record(&self, offset: usize) -> MathValue<'a> { + self.data.get(offset..) + .and_then(|data| MathValue::parse(data, self.data)) + .unwrap_or(MathValue { value: 0, device: None }) + } +} + + +/// A [Math Kern Table](https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathkern-table). +#[derive(Clone)] +pub struct Kern<'a> { + data: &'a [u8], + heights: LazyArray16<'a, MathValueRecord>, + kerns: LazyArray16<'a, MathValueRecord>, +} + +impl<'a> Kern<'a> { + /// Number of heights at which the kern value changes. + pub fn count(&self) -> u16 { + self.heights.len() + } + + /// The correction height at the given index. + /// + /// The index must be smaller than `count()`. + pub fn height(&self, index: u16) -> Option> { + Some(self.heights.get(index)?.get(self.data)) + } + + /// The kern value at the given index. + /// + /// The index must be smaller than or equal to `count()`. + pub fn kern(&self, index: u16) -> Option> { + Some(self.kerns.get(index)?.get(self.data)) + } +} + +impl<'a> FromSlice<'a> for Kern<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let count = s.read::()?; + let heights = s.read_array16::(count)?; + let kerns = s.read_array16::(count + 1)?; + Some(Kern { data, heights, kerns }) + } +} + +impl core::fmt::Debug for Kern<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Kern {{ ... }}") + } +} + + +#[derive(Clone, Copy)] +struct KernInfoRecord { + top_right: Option, + top_left: Option, + bottom_right: Option, + bottom_left: Option, +} + +impl FromData for KernInfoRecord { + const SIZE: usize = 8; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(KernInfoRecord { + top_right: s.read::>()?, + top_left: s.read::>()?, + bottom_right: s.read::>()?, + bottom_left: s.read::>()?, + }) + } +} + +impl KernInfoRecord { + fn get<'a>(&self, data: &'a [u8]) -> KernInfo<'a> { + let parse_field = |offset: Option| offset + .and_then(|offset| data.get(offset.to_usize()..)) + .and_then(Kern::parse); + KernInfo { + top_right: parse_field(self.top_right), + top_left: parse_field(self.top_left), + bottom_right: parse_field(self.bottom_right), + bottom_left: parse_field(self.bottom_left), + } + } +} + + +/// An [entry in a Math Kern Info Table]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathkerninforecord). +#[derive(Clone, Debug)] +pub struct KernInfo<'a> { + /// The kerning data for the top-right corner. + pub top_right: Option>, + /// The kerning data for the top-left corner. + pub top_left: Option>, + /// The kerning data for the bottom-right corner. + pub bottom_right: Option>, + /// The kerning data for the bottom-left corner. + pub bottom_left: Option>, +} + + +/// A [Math Kern Info Table](https://docs.microsoft.com/en-us/typography/opentype/spec/math#mathkerninfo-table). +#[derive(Clone, Copy)] +pub struct KernInfos<'a> { + data: &'a [u8], + coverage: Coverage<'a>, + records: LazyArray16<'a, KernInfoRecord>, +} + +impl<'a> FromSlice<'a> for KernInfos<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let coverage = s.parse_at_offset16::(data)?; + let count = s.read::()?; + let records = s.read_array16::(count)?; + Some(KernInfos { data, coverage, records }) + } +} + +impl<'a> KernInfos<'a> { + /// Returns the kerning info for the glyph or `None` if it is not covered. + #[inline] + pub fn get(&self, glyph: GlyphId) -> Option> { + let index = self.coverage.get(glyph)?; + Some(self.records.get(index)?.get(self.data)) + } +} + +impl core::fmt::Debug for KernInfos<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "KernInfos {{ ... }}") + } +} + + +/// A [Math Glyph Info Table](https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathglyphinfo-table). +#[derive(Clone, Copy, Debug)] +pub struct GlyphInfo<'a> { + /// Per-glyph italics correction values. + pub italic_corrections: Option>, + /// Per-glyph horizontal positions for attaching mathematical accents. + pub top_accent_attachments: Option>, + /// Glyphs which are _extended shapes_. + pub extended_shapes: Option>, + /// Per-glyph information for mathematical kerning. + pub kern_infos: Option>, +} + +impl<'a> FromSlice<'a> for GlyphInfo<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + Some(GlyphInfo { + italic_corrections: s.parse_at_offset16::(data), + top_accent_attachments: s.parse_at_offset16::(data), + extended_shapes: s.parse_at_offset16::(data), + kern_infos: s.parse_at_offset16::(data), + }) + } +} + + +/// Glyph part flags. +#[derive(Clone, Copy, Debug)] +pub struct PartFlags(pub u16); + +#[allow(missing_docs)] +impl PartFlags { + #[inline] pub fn extender(self) -> bool { self.0 & 0x0001 != 0 } +} + +impl FromData for PartFlags { + const SIZE: usize = 2; + + fn parse(data: &[u8]) -> Option { + u16::parse(data).map(PartFlags) + } +} + + +/// Details for a glyph part in an assembly. +#[derive(Clone, Copy, Debug)] +pub struct GlyphPart { + /// Glyph ID for the part. + pub glyph_id: GlyphId, + /// Lengths of the connectors on the start of the glyph, in font design units. + pub start_connector_length: u16, + /// Lengths of the connectors on the end of the glyph, in font design units. + pub end_connector_length: u16, + /// The full advance of the part, in font design units. + pub full_advance: u16, + /// Part flags. + pub part_flags: PartFlags, +} + +impl FromData for GlyphPart { + const SIZE: usize = 10; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(GlyphPart { + glyph_id: s.read::()?, + start_connector_length: s.read::()?, + end_connector_length: s.read::()?, + full_advance: s.read::()?, + part_flags: s.read::()?, + }) + } +} + + +/// A [Glyph Assembly Table](https://learn.microsoft.com/en-us/typography/opentype/spec/math#glyphassembly-table). +#[derive(Clone, Copy, Debug)] +pub struct GlyphAssembly<'a> { + /// The italics correction of the assembled glyph. + pub italics_correction: MathValue<'a>, + /// Parts the assembly is composed of. + pub parts: LazyArray16<'a, GlyphPart>, +} + +impl<'a> FromSlice<'a> for GlyphAssembly<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let italics_correction = s.read::()?.get(data); + let count = s.read::()?; + let parts = s.read_array16::(count)?; + Some(GlyphAssembly { italics_correction, parts }) + } +} + + +/// Description of math glyph variants. +#[derive(Clone, Copy, Debug)] +pub struct GlyphVariant { + /// The ID of the variant glyph. + pub variant_glyph: GlyphId, + /// Advance width/height, in design units, of the variant glyph. + pub advance_measurement: u16, +} + +impl FromData for GlyphVariant { + const SIZE: usize = 4; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(GlyphVariant { + variant_glyph: s.read::()?, + advance_measurement:s.read::()?, + }) + } +} + + +/// A [Math Glyph Construction Table]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathglyphconstruction-table). +#[derive(Clone, Copy, Debug)] +pub struct GlyphConstruction<'a> { + /// A general recipe on how to construct a variant with large advance width/height. + pub assembly: Option>, + /// Prepared variants of the glyph with varying advances. + pub variants: LazyArray16<'a, GlyphVariant>, +} + +impl<'a> FromSlice<'a> for GlyphConstruction<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let assembly = s.parse_at_offset16::(data); + let variant_count = s.read::()?; + let variants = s.read_array16::(variant_count)?; + Some(GlyphConstruction { assembly, variants }) + } +} + + +/// A mapping from glyphs to +/// [Math Glyph Construction Tables]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathglyphconstruction-table). +#[derive(Clone, Copy)] +pub struct GlyphConstructions<'a> { + coverage: Coverage<'a>, + constructions: LazyOffsetArray16<'a, GlyphConstruction<'a>>, +} + +impl<'a> GlyphConstructions<'a> { + fn new( + data: &'a [u8], + coverage: Option>, + offsets: LazyArray16<'a, Option>, + ) -> Self { + GlyphConstructions { + coverage: coverage.unwrap_or(Coverage::Format1 { + glyphs: LazyArray16::new(&[]) + }), + constructions: LazyOffsetArray16::new(data, offsets), + } + } + + /// Returns the construction for the glyph or `None` if it is not covered. + #[inline] + pub fn get(&self, glyph: GlyphId) -> Option> { + let index = self.coverage.get(glyph)?; + self.constructions.get(index) + } +} + +impl core::fmt::Debug for GlyphConstructions<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "GlyphConstructions {{ ... }}") + } +} + + +/// A [Math Variants Table]( +/// https://learn.microsoft.com/en-us/typography/opentype/spec/math#mathvariants-table). +#[derive(Clone, Copy, Debug)] +pub struct Variants<'a> { + /// Minimum overlap of connecting glyphs during glyph construction, in design units. + pub min_connector_overlap: u16, + /// Constructions for shapes growing in the vertical direction. + pub vertical_constructions: GlyphConstructions<'a>, + /// Constructions for shapes growing in the horizontal direction. + pub horizontal_constructions: GlyphConstructions<'a>, +} + +impl<'a> FromSlice<'a> for Variants<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let min_connector_overlap = s.read::()?; + let vertical_coverage = s.parse_at_offset16::(data); + let horizontal_coverage = s.parse_at_offset16::(data); + let vertical_count = s.read::()?; + let horizontal_count = s.read::()?; + let vertical_offsets = s.read_array16::>(vertical_count)?; + let horizontal_offsets = s.read_array16::>(horizontal_count)?; + Some(Variants { + min_connector_overlap, + vertical_constructions: GlyphConstructions::new(data, vertical_coverage, vertical_offsets), + horizontal_constructions: GlyphConstructions::new(data, horizontal_coverage, horizontal_offsets), + }) + } +} + + +/// A [Math Table](https://docs.microsoft.com/en-us/typography/opentype/spec/math). +#[derive(Clone, Copy, Debug)] +pub struct Table<'a> { + /// Math positioning constants. + pub constants: Option>, + /// Per-glyph positioning information. + pub glyph_info: Option>, + /// Variants and assembly recipes for growable glyphs. + pub variants: Option>, +} + +impl<'a> Table<'a> { + /// Parses a table from raw data. + pub fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let major_version = s.read::()? as u8; + s.skip::(); // minor version + if major_version != 1 { + return None; + } + + Some(Table { + constants: s.parse_at_offset16::(data), + glyph_info: s.parse_at_offset16::(data), + variants: s.parse_at_offset16::(data), + }) + } +} diff --git a/src/tables/mod.rs b/src/tables/mod.rs index 998da9e5..765e0622 100644 --- a/src/tables/mod.rs +++ b/src/tables/mod.rs @@ -20,6 +20,7 @@ pub mod vorg; #[cfg(feature = "opentype-layout")] pub mod gdef; #[cfg(feature = "opentype-layout")] pub mod gsub; #[cfg(feature = "opentype-layout")] pub mod gpos; +#[cfg(feature = "opentype-layout")] pub mod math; #[cfg(feature = "apple-layout")] pub mod ankr; #[cfg(feature = "apple-layout")] pub mod feat;