use crate::parser::{read_i16, read_u16};
use crate::tables::device::resolve_device_delta;
use crate::tables::gdef::coverage_lookup;
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
pub const MATH_TABLE_TAG: [u8; 4] = *b"MATH";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct MathValueRecord {
pub value: i16,
pub device_offset: u16,
}
impl MathValueRecord {
const LEN: usize = 4;
fn read(bytes: &[u8], at: usize) -> Result<Self, Error> {
Ok(Self {
value: read_i16(bytes, at)?,
device_offset: read_u16(bytes, at + 2)?,
})
}
pub fn resolved_value(
&self,
parent_bytes: &[u8],
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> f32 {
let delta = resolve_device_delta(parent_bytes, self.device_offset, ivs, coords);
self.value as f32 + delta
}
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct MathTable<'a> {
data: &'a [u8],
constants_off: usize,
glyph_info_off: usize,
variants_off: usize,
}
impl<'a> MathTable<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let major = read_u16(data, 0)?;
if major != 1 {
return Err(Error::BadStructure("MATH major version not 1"));
}
let constants_off = read_u16(data, 4)? as usize;
let glyph_info_off = read_u16(data, 6)? as usize;
let variants_off = read_u16(data, 8)? as usize;
for &o in &[constants_off, glyph_info_off, variants_off] {
if o != 0 && o >= data.len() {
return Err(Error::BadStructure("MATH sub-table offset OOB"));
}
}
Ok(Self {
data,
constants_off,
glyph_info_off,
variants_off,
})
}
pub fn constants(&self) -> Option<MathConstants<'a>> {
if self.constants_off == 0 {
return None;
}
Some(MathConstants {
data: self.data,
base: self.constants_off,
})
}
pub fn glyph_info(&self) -> Option<MathGlyphInfo<'a>> {
if self.glyph_info_off == 0 {
return None;
}
let base = self.glyph_info_off;
Some(MathGlyphInfo {
data: self.data,
base,
})
}
pub fn variants(&self) -> Option<MathVariants<'a>> {
if self.variants_off == 0 {
return None;
}
Some(MathVariants {
data: self.data,
base: self.variants_off,
})
}
}
#[derive(Debug, Clone, Copy)]
#[doc(hidden)]
pub struct MathConstants<'a> {
data: &'a [u8],
base: usize,
}
pub mod constant {
pub const MATH_LEADING: usize = 0;
pub const AXIS_HEIGHT: usize = 1;
pub const ACCENT_BASE_HEIGHT: usize = 2;
pub const FLATTENED_ACCENT_BASE_HEIGHT: usize = 3;
pub const SUBSCRIPT_SHIFT_DOWN: usize = 4;
pub const SUBSCRIPT_TOP_MAX: usize = 5;
pub const SUBSCRIPT_BASELINE_DROP_MIN: usize = 6;
pub const SUPERSCRIPT_SHIFT_UP: usize = 7;
pub const SUPERSCRIPT_SHIFT_UP_CRAMPED: usize = 8;
pub const SUPERSCRIPT_BOTTOM_MIN: usize = 9;
pub const SUPERSCRIPT_BASELINE_DROP_MAX: usize = 10;
pub const SUB_SUPERSCRIPT_GAP_MIN: usize = 11;
pub const SUPERSCRIPT_BOTTOM_MAX_WITH_SUBSCRIPT: usize = 12;
pub const SPACE_AFTER_SCRIPT: usize = 13;
pub const UPPER_LIMIT_GAP_MIN: usize = 14;
pub const UPPER_LIMIT_BASELINE_RISE_MIN: usize = 15;
pub const LOWER_LIMIT_GAP_MIN: usize = 16;
pub const LOWER_LIMIT_BASELINE_DROP_MIN: usize = 17;
pub const STACK_TOP_SHIFT_UP: usize = 18;
pub const STACK_TOP_DISPLAY_STYLE_SHIFT_UP: usize = 19;
pub const STACK_BOTTOM_SHIFT_DOWN: usize = 20;
pub const STACK_BOTTOM_DISPLAY_STYLE_SHIFT_DOWN: usize = 21;
pub const STACK_GAP_MIN: usize = 22;
pub const STACK_DISPLAY_STYLE_GAP_MIN: usize = 23;
pub const STRETCH_STACK_TOP_SHIFT_UP: usize = 24;
pub const STRETCH_STACK_BOTTOM_SHIFT_DOWN: usize = 25;
pub const STRETCH_STACK_GAP_ABOVE_MIN: usize = 26;
pub const STRETCH_STACK_GAP_BELOW_MIN: usize = 27;
pub const FRACTION_NUMERATOR_SHIFT_UP: usize = 28;
pub const FRACTION_NUMERATOR_DISPLAY_STYLE_SHIFT_UP: usize = 29;
pub const FRACTION_DENOMINATOR_SHIFT_DOWN: usize = 30;
pub const FRACTION_DENOMINATOR_DISPLAY_STYLE_SHIFT_DOWN: usize = 31;
pub const FRACTION_NUMERATOR_GAP_MIN: usize = 32;
pub const FRACTION_NUM_DISPLAY_STYLE_GAP_MIN: usize = 33;
pub const FRACTION_RULE_THICKNESS: usize = 34;
pub const FRACTION_DENOMINATOR_GAP_MIN: usize = 35;
pub const FRACTION_DENOM_DISPLAY_STYLE_GAP_MIN: usize = 36;
pub const SKEWED_FRACTION_HORIZONTAL_GAP: usize = 37;
pub const SKEWED_FRACTION_VERTICAL_GAP: usize = 38;
pub const OVERBAR_VERTICAL_GAP: usize = 39;
pub const OVERBAR_RULE_THICKNESS: usize = 40;
pub const OVERBAR_EXTRA_ASCENDER: usize = 41;
pub const UNDERBAR_VERTICAL_GAP: usize = 42;
pub const UNDERBAR_RULE_THICKNESS: usize = 43;
pub const UNDERBAR_EXTRA_DESCENDER: usize = 44;
pub const RADICAL_VERTICAL_GAP: usize = 45;
pub const RADICAL_DISPLAY_STYLE_VERTICAL_GAP: usize = 46;
pub const RADICAL_RULE_THICKNESS: usize = 47;
pub const RADICAL_EXTRA_ASCENDER: usize = 48;
pub const RADICAL_KERN_BEFORE_DEGREE: usize = 49;
pub const RADICAL_KERN_AFTER_DEGREE: usize = 50;
}
impl<'a> MathConstants<'a> {
pub fn script_percent_scale_down(&self) -> i16 {
read_i16(self.data, self.base).unwrap_or(0)
}
pub fn script_script_percent_scale_down(&self) -> i16 {
read_i16(self.data, self.base + 2).unwrap_or(0)
}
pub fn delimited_sub_formula_min_height(&self) -> u16 {
read_u16(self.data, self.base + 4).unwrap_or(0)
}
pub fn display_operator_min_height(&self) -> u16 {
read_u16(self.data, self.base + 6).unwrap_or(0)
}
pub fn radical_degree_bottom_raise_percent(&self) -> i16 {
let at = self.base + 8 + 51 * MathValueRecord::LEN;
read_i16(self.data, at).unwrap_or(0)
}
pub fn value(&self, index: usize) -> Option<MathValueRecord> {
if index > constant::RADICAL_KERN_AFTER_DEGREE {
return None;
}
let at = self.base + 8 + index * MathValueRecord::LEN;
MathValueRecord::read(self.data, at).ok()
}
pub fn value_i16(&self, index: usize) -> i16 {
self.value(index).map(|r| r.value).unwrap_or(0)
}
pub fn value_resolved(
&self,
index: usize,
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> f32 {
match self.value(index) {
Some(r) => r.resolved_value(&self.data[self.base..], ivs, coords),
None => 0.0,
}
}
}
#[derive(Debug, Clone, Copy)]
#[doc(hidden)]
pub struct MathGlyphInfo<'a> {
data: &'a [u8],
base: usize,
}
impl<'a> MathGlyphInfo<'a> {
fn sub_off(&self, idx: usize) -> Option<usize> {
let o = read_u16(self.data, self.base + idx * 2).ok()? as usize;
if o == 0 {
None
} else {
Some(self.base + o)
}
}
pub fn italics_correction(&self, gid: u16) -> Option<i16> {
let base = self.sub_off(0)?; let cov_off = read_u16(self.data, base).ok()? as usize;
if cov_off == 0 {
return None;
}
let idx = coverage_lookup(self.data.get(base + cov_off..)?, gid)? as usize;
let count = read_u16(self.data, base + 2).ok()? as usize;
if idx >= count {
return None;
}
let at = base + 4 + idx * MathValueRecord::LEN;
Some(MathValueRecord::read(self.data, at).ok()?.value)
}
fn value_record_for(&self, value_sub: usize, gid: u16) -> Option<(MathValueRecord, usize)> {
let base = self.sub_off(value_sub)?;
let cov_off = read_u16(self.data, base).ok()? as usize;
if cov_off == 0 {
return None;
}
let idx = coverage_lookup(self.data.get(base + cov_off..)?, gid)? as usize;
let count = read_u16(self.data, base + 2).ok()? as usize;
if idx >= count {
return None;
}
let at = base + 4 + idx * MathValueRecord::LEN;
Some((MathValueRecord::read(self.data, at).ok()?, base))
}
pub fn italics_correction_resolved(
&self,
gid: u16,
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> Option<f32> {
let (rec, base) = self.value_record_for(0, gid)?;
Some(rec.resolved_value(&self.data[base..], ivs, coords))
}
pub fn top_accent_attachment(&self, gid: u16) -> Option<i16> {
let base = self.sub_off(1)?; let cov_off = read_u16(self.data, base).ok()? as usize;
if cov_off == 0 {
return None;
}
let idx = coverage_lookup(self.data.get(base + cov_off..)?, gid)? as usize;
let count = read_u16(self.data, base + 2).ok()? as usize;
if idx >= count {
return None;
}
let at = base + 4 + idx * MathValueRecord::LEN;
Some(MathValueRecord::read(self.data, at).ok()?.value)
}
pub fn top_accent_attachment_resolved(
&self,
gid: u16,
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> Option<f32> {
let (rec, base) = self.value_record_for(1, gid)?;
Some(rec.resolved_value(&self.data[base..], ivs, coords))
}
pub fn is_extended_shape(&self, gid: u16) -> bool {
match self.sub_off(2) {
Some(cov) => self
.data
.get(cov..)
.and_then(|b| coverage_lookup(b, gid))
.is_some(),
None => false,
}
}
pub fn math_kern(&self, gid: u16, corner: MathKernCorner, height: i16) -> Option<i16> {
let base = self.sub_off(3)?; let cov_off = read_u16(self.data, base).ok()? as usize;
if cov_off == 0 {
return None;
}
let idx = coverage_lookup(self.data.get(base + cov_off..)?, gid)? as usize;
let count = read_u16(self.data, base + 2).ok()? as usize;
if idx >= count {
return None;
}
let rec_at = base + 4 + idx * 8;
let kern_off = read_u16(self.data, rec_at + corner as usize * 2).ok()? as usize;
if kern_off == 0 {
return None;
}
math_kern_value(self.data, base + kern_off, height).map(|r| r.value)
}
pub fn math_kern_resolved(
&self,
gid: u16,
corner: MathKernCorner,
height: i16,
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> Option<f32> {
let base = self.sub_off(3)?; let cov_off = read_u16(self.data, base).ok()? as usize;
if cov_off == 0 {
return None;
}
let idx = coverage_lookup(self.data.get(base + cov_off..)?, gid)? as usize;
let count = read_u16(self.data, base + 2).ok()? as usize;
if idx >= count {
return None;
}
let rec_at = base + 4 + idx * 8;
let kern_off = read_u16(self.data, rec_at + corner as usize * 2).ok()? as usize;
if kern_off == 0 {
return None;
}
let kern_base = base + kern_off;
let rec = math_kern_value(self.data, kern_base, height)?;
Some(rec.resolved_value(&self.data[kern_base..], ivs, coords))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MathKernCorner {
TopRight = 0,
TopLeft = 1,
BottomRight = 2,
BottomLeft = 3,
}
fn math_kern_value(data: &[u8], base: usize, height: i16) -> Option<MathValueRecord> {
let n = read_u16(data, base).ok()? as usize;
let heights_at = base + 2;
let kerns_at = heights_at + n * MathValueRecord::LEN;
let mut sel = n; for i in 0..n {
let h = MathValueRecord::read(data, heights_at + i * MathValueRecord::LEN)
.ok()?
.value;
if height < h {
sel = i;
break;
}
}
let at = kerns_at + sel * MathValueRecord::LEN;
MathValueRecord::read(data, at).ok()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GrowDirection {
Vertical,
Horizontal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct GlyphVariant {
pub glyph: u16,
pub advance: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[doc(hidden)]
pub struct GlyphPart {
pub glyph: u16,
pub start_connector_length: u16,
pub end_connector_length: u16,
pub full_advance: u16,
pub part_flags: u16,
}
impl GlyphPart {
pub fn is_extender(&self) -> bool {
self.part_flags & 0x0001 != 0
}
}
#[derive(Debug, Clone, Copy)]
#[doc(hidden)]
pub struct MathVariants<'a> {
data: &'a [u8],
base: usize,
}
impl<'a> MathVariants<'a> {
pub fn min_connector_overlap(&self) -> u16 {
read_u16(self.data, self.base).unwrap_or(0)
}
fn coverage_off(&self, dir: GrowDirection) -> Option<usize> {
let field = match dir {
GrowDirection::Vertical => 2,
GrowDirection::Horizontal => 4,
};
let o = read_u16(self.data, self.base + field).ok()? as usize;
if o == 0 {
None
} else {
Some(self.base + o)
}
}
fn glyph_count(&self, dir: GrowDirection) -> u16 {
let field = match dir {
GrowDirection::Vertical => 6,
GrowDirection::Horizontal => 8,
};
read_u16(self.data, self.base + field).unwrap_or(0)
}
fn construction_off(&self, gid: u16, dir: GrowDirection) -> Option<usize> {
let cov = self.coverage_off(dir)?;
let idx = coverage_lookup(self.data.get(cov..)?, gid)? as usize;
let count = self.glyph_count(dir) as usize;
if idx >= count {
return None;
}
let vert_count = self.glyph_count(GrowDirection::Vertical) as usize;
let array_base = match dir {
GrowDirection::Vertical => self.base + 10,
GrowDirection::Horizontal => self.base + 10 + vert_count * 2,
};
let o = read_u16(self.data, array_base + idx * 2).ok()? as usize;
if o == 0 {
None
} else {
Some(self.base + o)
}
}
pub fn variants(&self, gid: u16, dir: GrowDirection) -> Vec<GlyphVariant> {
let mut out = Vec::new();
let Some(ctor) = self.construction_off(gid, dir) else {
return out;
};
let Ok(count) = read_u16(self.data, ctor + 2) else {
return out;
};
for i in 0..count as usize {
let at = ctor + 4 + i * 4;
let (Ok(glyph), Ok(advance)) = (read_u16(self.data, at), read_u16(self.data, at + 2))
else {
break;
};
out.push(GlyphVariant { glyph, advance });
}
out
}
pub fn assembly(&self, gid: u16, dir: GrowDirection) -> Option<(i16, Vec<GlyphPart>)> {
let ctor = self.construction_off(gid, dir)?;
let asm_off = read_u16(self.data, ctor).ok()? as usize;
if asm_off == 0 {
return None;
}
let asm = ctor + asm_off;
let italics = MathValueRecord::read(self.data, asm).ok()?.value;
let part_count = read_u16(self.data, asm + MathValueRecord::LEN).ok()? as usize;
let parts_at = asm + MathValueRecord::LEN + 2;
let mut parts = Vec::with_capacity(part_count);
for i in 0..part_count {
let at = parts_at + i * 10; parts.push(GlyphPart {
glyph: read_u16(self.data, at).ok()?,
start_connector_length: read_u16(self.data, at + 2).ok()?,
end_connector_length: read_u16(self.data, at + 4).ok()?,
full_advance: read_u16(self.data, at + 6).ok()?,
part_flags: read_u16(self.data, at + 8).ok()?,
});
}
Some((italics, parts))
}
pub fn assembly_italics_correction_resolved(
&self,
gid: u16,
dir: GrowDirection,
ivs: Option<&ItemVariationStore>,
coords: &[f32],
) -> Option<f32> {
let ctor = self.construction_off(gid, dir)?;
let asm_off = read_u16(self.data, ctor).ok()? as usize;
if asm_off == 0 {
return None;
}
let asm = ctor + asm_off;
let rec = MathValueRecord::read(self.data, asm).ok()?;
Some(rec.resolved_value(&self.data[asm..], ivs, coords))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_math_constants_only() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&1u16.to_be_bytes());
data.extend_from_slice(&0u16.to_be_bytes());
let const_off_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes());
let const_off = data.len();
data.extend_from_slice(&80i16.to_be_bytes()); data.extend_from_slice(&60i16.to_be_bytes()); data.extend_from_slice(&300u16.to_be_bytes()); data.extend_from_slice(&1500u16.to_be_bytes()); for i in 0..51 {
let v: i16 = if i == constant::AXIS_HEIGHT as i32 as usize {
250
} else if i == constant::FRACTION_RULE_THICKNESS {
40
} else {
0
};
data.extend_from_slice(&v.to_be_bytes());
data.extend_from_slice(&0u16.to_be_bytes()); }
data.extend_from_slice(&60i16.to_be_bytes());
let off = const_off as u16;
data[const_off_pos..const_off_pos + 2].copy_from_slice(&off.to_be_bytes());
data
}
#[test]
fn math_constants_scalars_and_records() {
let data = build_math_constants_only();
let m = MathTable::parse(&data).expect("parse");
let c = m.constants().expect("constants");
assert_eq!(c.script_percent_scale_down(), 80);
assert_eq!(c.script_script_percent_scale_down(), 60);
assert_eq!(c.delimited_sub_formula_min_height(), 300);
assert_eq!(c.display_operator_min_height(), 1500);
assert_eq!(c.value_i16(constant::AXIS_HEIGHT), 250);
assert_eq!(c.value_i16(constant::FRACTION_RULE_THICKNESS), 40);
assert_eq!(c.value_i16(constant::MATH_LEADING), 0);
assert_eq!(c.radical_degree_bottom_raise_percent(), 60);
assert!(c.value(100).is_none());
assert!(m.glyph_info().is_none());
assert!(m.variants().is_none());
}
#[test]
fn rejects_wrong_version() {
let mut data = vec![0u8; 10];
data[1] = 2; assert!(MathTable::parse(&data).is_err());
}
fn build_single_region_ivs(delta: i16) -> Vec<u8> {
let mut b = vec![0u8; 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[2..6].copy_from_slice(&12u32.to_be_bytes()); b[6..8].copy_from_slice(&1u16.to_be_bytes()); b[8..12].copy_from_slice(&22u32.to_be_bytes()); b[12..14].copy_from_slice(&1u16.to_be_bytes()); b[14..16].copy_from_slice(&1u16.to_be_bytes()); b[16..18].copy_from_slice(&0i16.to_be_bytes()); b[18..20].copy_from_slice(&16384i16.to_be_bytes()); b[20..22].copy_from_slice(&16384i16.to_be_bytes()); b[22..24].copy_from_slice(&1u16.to_be_bytes()); b[24..26].copy_from_slice(&1u16.to_be_bytes()); b[26..28].copy_from_slice(&1u16.to_be_bytes()); b[28..30].copy_from_slice(&0u16.to_be_bytes()); b[30..32].copy_from_slice(&delta.to_be_bytes()); b
}
fn build_math_constants_with_var_axis_height(base: i16) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); let const_off_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes());
let const_off = data.len();
data.extend_from_slice(&80i16.to_be_bytes()); data.extend_from_slice(&60i16.to_be_bytes()); data.extend_from_slice(&300u16.to_be_bytes()); data.extend_from_slice(&1500u16.to_be_bytes()); let records_at = data.len();
for i in 0..51usize {
if i == constant::AXIS_HEIGHT {
data.extend_from_slice(&base.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); } else {
data.extend_from_slice(&0i16.to_be_bytes());
data.extend_from_slice(&0u16.to_be_bytes());
}
}
data.extend_from_slice(&60i16.to_be_bytes());
let var_idx_at = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0x8000u16.to_be_bytes());
let dev_off_pos = records_at + constant::AXIS_HEIGHT * MathValueRecord::LEN + 2;
let dev_off = (var_idx_at - const_off) as u16;
data[dev_off_pos..dev_off_pos + 2].copy_from_slice(&dev_off.to_be_bytes());
data[const_off_pos..const_off_pos + 2].copy_from_slice(&(const_off as u16).to_be_bytes());
data
}
#[test]
fn math_constants_value_resolves_variation_index_delta() {
let data = build_math_constants_with_var_axis_height(250);
let m = MathTable::parse(&data).expect("parse");
let c = m.constants().expect("constants");
let ivs_bytes = build_single_region_ivs(-40);
let ivs = ItemVariationStore::parse(&ivs_bytes).expect("ivs");
assert_eq!(c.value_i16(constant::AXIS_HEIGHT), 250);
assert_eq!(c.value_resolved(constant::AXIS_HEIGHT, None, &[0.0]), 250.0);
assert_eq!(
c.value_resolved(constant::AXIS_HEIGHT, Some(&ivs), &[0.0]),
250.0
);
assert_eq!(
c.value_resolved(constant::AXIS_HEIGHT, Some(&ivs), &[1.0]),
210.0
);
assert_eq!(
c.value_resolved(constant::AXIS_HEIGHT, Some(&ivs), &[0.5]),
230.0
);
assert_eq!(
c.value_resolved(constant::MATH_LEADING, Some(&ivs), &[1.0]),
0.0
);
}
fn build_math_variants() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); let var_off_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes());
let var_base = data.len();
data.extend_from_slice(&20u16.to_be_bytes()); let vcov_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); let vctor_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes());
let cov_at = data.len();
data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&5u16.to_be_bytes()); data[vcov_pos..vcov_pos + 2].copy_from_slice(&((cov_at - var_base) as u16).to_be_bytes());
let ctor_at = data.len();
let asm_off_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&2u16.to_be_bytes()); data.extend_from_slice(&10u16.to_be_bytes());
data.extend_from_slice(&1000u16.to_be_bytes());
data.extend_from_slice(&11u16.to_be_bytes());
data.extend_from_slice(&2000u16.to_be_bytes());
data[vctor_pos..vctor_pos + 2]
.copy_from_slice(&((ctor_at - var_base) as u16).to_be_bytes());
let asm_at = data.len();
data.extend_from_slice(&0i16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&2u16.to_be_bytes()); data.extend_from_slice(&20u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&50u16.to_be_bytes()); data.extend_from_slice(&300u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&21u16.to_be_bytes());
data.extend_from_slice(&50u16.to_be_bytes());
data.extend_from_slice(&50u16.to_be_bytes());
data.extend_from_slice(&200u16.to_be_bytes());
data.extend_from_slice(&1u16.to_be_bytes()); data[asm_off_pos..asm_off_pos + 2]
.copy_from_slice(&((asm_at - ctor_at) as u16).to_be_bytes());
data[var_off_pos..var_off_pos + 2].copy_from_slice(&(var_base as u16).to_be_bytes());
data
}
#[test]
fn math_variants_and_assembly() {
let data = build_math_variants();
let m = MathTable::parse(&data).expect("parse");
let v = m.variants().expect("variants");
assert_eq!(v.min_connector_overlap(), 20);
let vars = v.variants(5, GrowDirection::Vertical);
assert_eq!(vars.len(), 2);
assert_eq!(
vars[0],
GlyphVariant {
glyph: 10,
advance: 1000
}
);
assert_eq!(
vars[1],
GlyphVariant {
glyph: 11,
advance: 2000
}
);
assert!(v.variants(99, GrowDirection::Vertical).is_empty());
assert!(v.variants(5, GrowDirection::Horizontal).is_empty());
let (italics, parts) = v.assembly(5, GrowDirection::Vertical).expect("assembly");
assert_eq!(italics, 0);
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].glyph, 20);
assert!(!parts[0].is_extender());
assert_eq!(parts[1].glyph, 21);
assert!(parts[1].is_extender());
assert_eq!(parts[1].full_advance, 200);
}
fn build_math_glyph_info(ic: i16, tac: i16, kern_hi: i16) -> Vec<u8> {
let mut gi = Vec::new();
let header_len = 8usize;
let mut ic_sub = Vec::new();
ic_sub.extend_from_slice(&8u16.to_be_bytes()); ic_sub.extend_from_slice(&1u16.to_be_bytes()); ic_sub.extend_from_slice(&ic.to_be_bytes()); let ic_dev_pos = ic_sub.len();
ic_sub.extend_from_slice(&0u16.to_be_bytes()); ic_sub.extend_from_slice(&1u16.to_be_bytes());
ic_sub.extend_from_slice(&1u16.to_be_bytes());
ic_sub.extend_from_slice(&7u16.to_be_bytes());
let ic_var_at = ic_sub.len();
ic_sub.extend_from_slice(&0u16.to_be_bytes());
ic_sub.extend_from_slice(&0u16.to_be_bytes());
ic_sub.extend_from_slice(&0x8000u16.to_be_bytes());
ic_sub[ic_dev_pos..ic_dev_pos + 2].copy_from_slice(&(ic_var_at as u16).to_be_bytes());
let mut tac_sub = Vec::new();
tac_sub.extend_from_slice(&8u16.to_be_bytes()); tac_sub.extend_from_slice(&1u16.to_be_bytes()); tac_sub.extend_from_slice(&tac.to_be_bytes());
tac_sub.extend_from_slice(&0u16.to_be_bytes()); tac_sub.extend_from_slice(&1u16.to_be_bytes()); tac_sub.extend_from_slice(&1u16.to_be_bytes());
tac_sub.extend_from_slice(&7u16.to_be_bytes());
let mut kern_sub = Vec::new();
let kcov_pos = kern_sub.len();
kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&1u16.to_be_bytes()); let krec_pos = kern_sub.len();
kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); let kcov_at = kern_sub.len();
kern_sub.extend_from_slice(&1u16.to_be_bytes());
kern_sub.extend_from_slice(&1u16.to_be_bytes());
kern_sub.extend_from_slice(&7u16.to_be_bytes());
kern_sub[kcov_pos..kcov_pos + 2].copy_from_slice(&(kcov_at as u16).to_be_bytes());
let mkern_at = kern_sub.len();
kern_sub.extend_from_slice(&1u16.to_be_bytes()); kern_sub.extend_from_slice(&100i16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&10i16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&kern_hi.to_be_bytes()); let khi_dev_pos = kern_sub.len();
kern_sub.extend_from_slice(&0u16.to_be_bytes()); let kvar_at = kern_sub.len();
kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&0u16.to_be_bytes()); kern_sub.extend_from_slice(&0x8000u16.to_be_bytes()); kern_sub[khi_dev_pos..khi_dev_pos + 2]
.copy_from_slice(&((kvar_at - mkern_at) as u16).to_be_bytes());
kern_sub[krec_pos..krec_pos + 2].copy_from_slice(&(mkern_at as u16).to_be_bytes());
let ic_at = header_len;
let tac_at = ic_at + ic_sub.len();
let kern_at = tac_at + tac_sub.len();
gi.extend_from_slice(&(ic_at as u16).to_be_bytes()); gi.extend_from_slice(&(tac_at as u16).to_be_bytes()); gi.extend_from_slice(&0u16.to_be_bytes()); gi.extend_from_slice(&(kern_at as u16).to_be_bytes()); gi.extend_from_slice(&ic_sub);
gi.extend_from_slice(&tac_sub);
gi.extend_from_slice(&kern_sub);
let mut data = Vec::new();
data.extend_from_slice(&1u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); let gi_off_pos = data.len();
data.extend_from_slice(&0u16.to_be_bytes()); data.extend_from_slice(&0u16.to_be_bytes()); let gi_off = data.len();
data.extend_from_slice(&gi);
data[gi_off_pos..gi_off_pos + 2].copy_from_slice(&(gi_off as u16).to_be_bytes());
data
}
#[test]
fn glyph_info_values_resolve_variation_deltas() {
let data = build_math_glyph_info(120, 300, 25);
let m = MathTable::parse(&data).expect("parse");
let gi = m.glyph_info().expect("glyph info");
let ivs_bytes = build_single_region_ivs(-15);
let ivs = ItemVariationStore::parse(&ivs_bytes).expect("ivs");
assert_eq!(gi.italics_correction(7), Some(120));
assert_eq!(gi.top_accent_attachment(7), Some(300));
assert_eq!(gi.math_kern(7, MathKernCorner::TopRight, 50), Some(10));
assert_eq!(gi.math_kern(7, MathKernCorner::TopRight, 150), Some(25));
assert_eq!(
gi.italics_correction_resolved(7, Some(&ivs), &[0.0]),
Some(120.0)
);
assert_eq!(
gi.italics_correction_resolved(7, Some(&ivs), &[1.0]),
Some(105.0)
); assert_eq!(
gi.top_accent_attachment_resolved(7, Some(&ivs), &[1.0]),
Some(300.0)
);
assert_eq!(
gi.math_kern_resolved(7, MathKernCorner::TopRight, 50, Some(&ivs), &[1.0]),
Some(10.0)
);
assert_eq!(
gi.math_kern_resolved(7, MathKernCorner::TopRight, 150, Some(&ivs), &[0.0]),
Some(25.0)
);
assert_eq!(
gi.math_kern_resolved(7, MathKernCorner::TopRight, 150, Some(&ivs), &[1.0]),
Some(10.0)
);
assert!(gi
.italics_correction_resolved(99, Some(&ivs), &[1.0])
.is_none());
assert!(gi
.math_kern_resolved(99, MathKernCorner::TopRight, 0, Some(&ivs), &[1.0])
.is_none());
}
}