use crate::parser::{read_i16, read_u16, read_u32};
use crate::Error;
pub const BASE_MAJOR_VERSION: u16 = 1;
pub const BASE_MINOR_VERSION_0: u16 = 0;
pub const BASE_MINOR_VERSION_1: u16 = 1;
const MAX_COUNT: usize = u16::MAX as usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BaseCoord {
Format1 {
coordinate: i16,
},
Format2 {
coordinate: i16,
reference_glyph: u16,
base_coord_point: u16,
},
Format3 {
coordinate: i16,
device_offset: Option<u16>,
},
}
impl BaseCoord {
pub fn coordinate(&self) -> i16 {
match self {
Self::Format1 { coordinate }
| Self::Format2 { coordinate, .. }
| Self::Format3 { coordinate, .. } => *coordinate,
}
}
pub fn format(&self) -> u16 {
match self {
Self::Format1 { .. } => 1,
Self::Format2 { .. } => 2,
Self::Format3 { .. } => 3,
}
}
fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let format = read_u16(bytes, 0)?;
let coordinate = read_i16(bytes, 2)?;
match format {
1 => Ok(Self::Format1 { coordinate }),
2 => {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let reference_glyph = read_u16(bytes, 4)?;
let base_coord_point = read_u16(bytes, 6)?;
Ok(Self::Format2 {
coordinate,
reference_glyph,
base_coord_point,
})
}
3 => {
if bytes.len() < 6 {
return Err(Error::UnexpectedEof);
}
let raw = read_u16(bytes, 4)?;
let device_offset = if raw == 0 { None } else { Some(raw) };
Ok(Self::Format3 {
coordinate,
device_offset,
})
}
_ => Err(Error::BadStructure("BASE: unknown BaseCoord format")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FeatMinMaxRecord {
pub feature_tag: [u8; 4],
pub min_coord: Option<BaseCoord>,
pub max_coord: Option<BaseCoord>,
}
#[derive(Debug, Clone)]
pub struct MinMaxTable {
pub min_coord: Option<BaseCoord>,
pub max_coord: Option<BaseCoord>,
pub feat_min_max_records: Vec<FeatMinMaxRecord>,
}
#[derive(Debug, Clone)]
pub struct BaseLangSysRecord {
pub lang_sys_tag: [u8; 4],
pub min_max: MinMaxTable,
}
#[derive(Debug, Clone)]
pub struct BaseValuesTable {
pub default_baseline_index: u16,
pub base_coords: Vec<BaseCoord>,
}
#[derive(Debug, Clone)]
pub struct BaseScriptTable {
pub base_values: Option<BaseValuesTable>,
pub default_min_max: Option<MinMaxTable>,
pub base_lang_sys_records: Vec<BaseLangSysRecord>,
}
impl BaseScriptTable {
pub fn min_max_for_lang_sys(&self, lang_sys_tag: [u8; 4]) -> Option<&MinMaxTable> {
self.base_lang_sys_records
.iter()
.find(|r| r.lang_sys_tag == lang_sys_tag)
.map(|r| &r.min_max)
}
}
#[derive(Debug, Clone)]
pub struct BaseScriptRecord {
pub script_tag: [u8; 4],
pub base_script: BaseScriptTable,
}
#[derive(Debug, Clone)]
pub struct AxisTable {
pub baseline_tags: Option<Vec<[u8; 4]>>,
pub base_scripts: Vec<BaseScriptRecord>,
}
impl AxisTable {
pub fn base_script_for_tag(&self, script_tag: [u8; 4]) -> Option<&BaseScriptTable> {
self.base_scripts
.iter()
.find(|r| r.script_tag == script_tag)
.map(|r| &r.base_script)
}
pub fn baseline_index_for_tag(&self, baseline_tag: [u8; 4]) -> Option<usize> {
self.baseline_tags
.as_ref()?
.iter()
.position(|t| t == &baseline_tag)
}
}
#[derive(Debug, Clone)]
pub struct BaseTable {
pub major_version: u16,
pub minor_version: u16,
pub horiz_axis: Option<AxisTable>,
pub vert_axis: Option<AxisTable>,
pub item_var_store_offset: Option<u32>,
item_var_store_bytes: Option<Vec<u8>>,
}
impl BaseTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let major_version = read_u16(bytes, 0)?;
let minor_version = read_u16(bytes, 2)?;
if major_version != BASE_MAJOR_VERSION {
return Err(Error::BadStructure("BASE: majorVersion != 1"));
}
if minor_version != BASE_MINOR_VERSION_0 && minor_version != BASE_MINOR_VERSION_1 {
return Err(Error::BadStructure("BASE: minorVersion neither 0 nor 1"));
}
let horiz_axis_offset = read_u16(bytes, 4)? as usize;
let vert_axis_offset = read_u16(bytes, 6)? as usize;
let (item_var_store_offset, item_var_store_bytes) = if minor_version == BASE_MINOR_VERSION_1
{
if bytes.len() < 12 {
return Err(Error::UnexpectedEof);
}
let raw = read_u32(bytes, 8)?;
if raw == 0 {
(None, None)
} else {
let off = raw as usize;
if off >= bytes.len() {
return Err(Error::BadStructure(
"BASE: itemVarStoreOffset past end of table",
));
}
let ivs_bytes = bytes[off..].to_vec();
(Some(raw), Some(ivs_bytes))
}
} else {
(None, None)
};
let horiz_axis = if horiz_axis_offset == 0 {
None
} else {
Some(parse_axis(bytes, horiz_axis_offset)?)
};
let vert_axis = if vert_axis_offset == 0 {
None
} else {
Some(parse_axis(bytes, vert_axis_offset)?)
};
Ok(Self {
major_version,
minor_version,
horiz_axis,
vert_axis,
item_var_store_offset,
item_var_store_bytes,
})
}
pub fn item_var_store_bytes(&self) -> Option<&[u8]> {
self.item_var_store_bytes.as_deref()
}
}
fn parse_axis(base_bytes: &[u8], axis_off: usize) -> Result<AxisTable, Error> {
if axis_off
.checked_add(4)
.ok_or(Error::BadStructure("BASE: Axis offset overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let axis_bytes = &base_bytes[axis_off..];
let base_tag_list_off = read_u16(axis_bytes, 0)? as usize;
let base_script_list_off = read_u16(axis_bytes, 2)? as usize;
let baseline_tags = if base_tag_list_off == 0 {
None
} else {
let abs = axis_off
.checked_add(base_tag_list_off)
.ok_or(Error::BadStructure("BASE: BaseTagList offset overflow"))?;
Some(parse_base_tag_list(base_bytes, abs)?)
};
if base_script_list_off == 0 {
return Err(Error::BadStructure(
"BASE: Axis missing baseScriptListOffset",
));
}
let bsl_abs = axis_off
.checked_add(base_script_list_off)
.ok_or(Error::BadStructure("BASE: BaseScriptList offset overflow"))?;
let base_scripts = parse_base_script_list(base_bytes, bsl_abs)?;
Ok(AxisTable {
baseline_tags,
base_scripts,
})
}
fn parse_base_tag_list(base_bytes: &[u8], off: usize) -> Result<Vec<[u8; 4]>, Error> {
if off
.checked_add(2)
.ok_or(Error::BadStructure("BASE: BaseTagList header overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let count = read_u16(base_bytes, off)? as usize;
if count > MAX_COUNT {
return Err(Error::BadStructure("BASE: baseTagCount cap"));
}
let body_start = off
.checked_add(2)
.ok_or(Error::BadStructure("BASE: BaseTagList body overflow"))?;
let body_end = body_start
.checked_add(
count
.checked_mul(4)
.ok_or(Error::BadStructure("BASE: baseTagCount * 4 overflow"))?,
)
.ok_or(Error::BadStructure("BASE: BaseTagList body overflow"))?;
if body_end > base_bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut tags = Vec::with_capacity(count);
for i in 0..count {
let p = body_start + i * 4;
tags.push([
base_bytes[p],
base_bytes[p + 1],
base_bytes[p + 2],
base_bytes[p + 3],
]);
}
Ok(tags)
}
fn parse_base_script_list(base_bytes: &[u8], off: usize) -> Result<Vec<BaseScriptRecord>, Error> {
if off
.checked_add(2)
.ok_or(Error::BadStructure("BASE: BaseScriptList header overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let count = read_u16(base_bytes, off)? as usize;
if count > MAX_COUNT {
return Err(Error::BadStructure("BASE: baseScriptCount cap"));
}
let body_start = off
.checked_add(2)
.ok_or(Error::BadStructure("BASE: BaseScriptList body overflow"))?;
let body_end = body_start
.checked_add(
count
.checked_mul(6)
.ok_or(Error::BadStructure("BASE: baseScriptCount * 6 overflow"))?,
)
.ok_or(Error::BadStructure("BASE: BaseScriptList body overflow"))?;
if body_end > base_bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut records = Vec::with_capacity(count);
for i in 0..count {
let p = body_start + i * 6;
let script_tag = [
base_bytes[p],
base_bytes[p + 1],
base_bytes[p + 2],
base_bytes[p + 3],
];
let bs_off_rel = read_u16(base_bytes, p + 4)? as usize;
let bs_abs = off
.checked_add(bs_off_rel)
.ok_or(Error::BadStructure("BASE: BaseScript offset overflow"))?;
let base_script = parse_base_script(base_bytes, bs_abs)?;
records.push(BaseScriptRecord {
script_tag,
base_script,
});
}
Ok(records)
}
fn parse_base_script(base_bytes: &[u8], off: usize) -> Result<BaseScriptTable, Error> {
if off
.checked_add(6)
.ok_or(Error::BadStructure("BASE: BaseScript header overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let bs_bytes = &base_bytes[off..];
let base_values_off = read_u16(bs_bytes, 0)? as usize;
let default_min_max_off = read_u16(bs_bytes, 2)? as usize;
let lang_sys_count = read_u16(bs_bytes, 4)? as usize;
let body_start = off
.checked_add(6)
.ok_or(Error::BadStructure("BASE: BaseScript body overflow"))?;
let body_end = body_start
.checked_add(
lang_sys_count
.checked_mul(6)
.ok_or(Error::BadStructure("BASE: baseLangSysCount * 6 overflow"))?,
)
.ok_or(Error::BadStructure("BASE: BaseScript body overflow"))?;
if body_end > base_bytes.len() {
return Err(Error::UnexpectedEof);
}
let base_values = if base_values_off == 0 {
None
} else {
let abs = off
.checked_add(base_values_off)
.ok_or(Error::BadStructure("BASE: BaseValues offset overflow"))?;
Some(parse_base_values(base_bytes, abs)?)
};
let default_min_max = if default_min_max_off == 0 {
None
} else {
let abs = off
.checked_add(default_min_max_off)
.ok_or(Error::BadStructure("BASE: defaultMinMax offset overflow"))?;
Some(parse_min_max(base_bytes, abs)?)
};
let mut base_lang_sys_records = Vec::with_capacity(lang_sys_count);
for i in 0..lang_sys_count {
let p = body_start + i * 6;
let lang_sys_tag = [
base_bytes[p],
base_bytes[p + 1],
base_bytes[p + 2],
base_bytes[p + 3],
];
let mm_off_rel = read_u16(base_bytes, p + 4)? as usize;
if mm_off_rel == 0 {
return Err(Error::BadStructure(
"BASE: BaseLangSysRecord minMaxOffset must not be NULL",
));
}
let mm_abs = off.checked_add(mm_off_rel).ok_or(Error::BadStructure(
"BASE: BaseLangSys MinMax offset overflow",
))?;
let min_max = parse_min_max(base_bytes, mm_abs)?;
base_lang_sys_records.push(BaseLangSysRecord {
lang_sys_tag,
min_max,
});
}
Ok(BaseScriptTable {
base_values,
default_min_max,
base_lang_sys_records,
})
}
fn parse_base_values(base_bytes: &[u8], off: usize) -> Result<BaseValuesTable, Error> {
if off
.checked_add(4)
.ok_or(Error::BadStructure("BASE: BaseValues header overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let bv_bytes = &base_bytes[off..];
let default_baseline_index = read_u16(bv_bytes, 0)?;
let count = read_u16(bv_bytes, 2)? as usize;
if count > MAX_COUNT {
return Err(Error::BadStructure("BASE: baseCoordCount cap"));
}
let body_start = off
.checked_add(4)
.ok_or(Error::BadStructure("BASE: BaseValues body overflow"))?;
let body_end = body_start
.checked_add(
count
.checked_mul(2)
.ok_or(Error::BadStructure("BASE: baseCoordCount * 2 overflow"))?,
)
.ok_or(Error::BadStructure("BASE: BaseValues body overflow"))?;
if body_end > base_bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut base_coords = Vec::with_capacity(count);
for i in 0..count {
let p = body_start + i * 2;
let bc_off_rel = read_u16(base_bytes, p)? as usize;
if bc_off_rel == 0 {
return Err(Error::BadStructure(
"BASE: BaseValues baseCoord offset must not be NULL",
));
}
let bc_abs = off
.checked_add(bc_off_rel)
.ok_or(Error::BadStructure("BASE: BaseCoord offset overflow"))?;
if bc_abs >= base_bytes.len() {
return Err(Error::UnexpectedEof);
}
base_coords.push(BaseCoord::parse(&base_bytes[bc_abs..])?);
}
Ok(BaseValuesTable {
default_baseline_index,
base_coords,
})
}
fn parse_min_max(base_bytes: &[u8], off: usize) -> Result<MinMaxTable, Error> {
if off
.checked_add(6)
.ok_or(Error::BadStructure("BASE: MinMax header overflow"))?
> base_bytes.len()
{
return Err(Error::UnexpectedEof);
}
let mm_bytes = &base_bytes[off..];
let min_off_rel = read_u16(mm_bytes, 0)? as usize;
let max_off_rel = read_u16(mm_bytes, 2)? as usize;
let feat_count = read_u16(mm_bytes, 4)? as usize;
if feat_count > MAX_COUNT {
return Err(Error::BadStructure("BASE: featMinMaxCount cap"));
}
let min_coord = if min_off_rel == 0 {
None
} else {
let abs = off
.checked_add(min_off_rel)
.ok_or(Error::BadStructure("BASE: MinMax minCoord offset overflow"))?;
if abs >= base_bytes.len() {
return Err(Error::UnexpectedEof);
}
Some(BaseCoord::parse(&base_bytes[abs..])?)
};
let max_coord = if max_off_rel == 0 {
None
} else {
let abs = off
.checked_add(max_off_rel)
.ok_or(Error::BadStructure("BASE: MinMax maxCoord offset overflow"))?;
if abs >= base_bytes.len() {
return Err(Error::UnexpectedEof);
}
Some(BaseCoord::parse(&base_bytes[abs..])?)
};
let body_start = off
.checked_add(6)
.ok_or(Error::BadStructure("BASE: MinMax body overflow"))?;
let body_end = body_start
.checked_add(
feat_count
.checked_mul(8)
.ok_or(Error::BadStructure("BASE: featMinMaxCount * 8 overflow"))?,
)
.ok_or(Error::BadStructure("BASE: MinMax body overflow"))?;
if body_end > base_bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut feat_min_max_records = Vec::with_capacity(feat_count);
for i in 0..feat_count {
let p = body_start + i * 8;
let feature_tag = [
base_bytes[p],
base_bytes[p + 1],
base_bytes[p + 2],
base_bytes[p + 3],
];
let f_min_off = read_u16(base_bytes, p + 4)? as usize;
let f_max_off = read_u16(base_bytes, p + 6)? as usize;
let feat_min = if f_min_off == 0 {
None
} else {
let abs = off.checked_add(f_min_off).ok_or(Error::BadStructure(
"BASE: FeatMinMax minCoord offset overflow",
))?;
if abs >= base_bytes.len() {
return Err(Error::UnexpectedEof);
}
Some(BaseCoord::parse(&base_bytes[abs..])?)
};
let feat_max = if f_max_off == 0 {
None
} else {
let abs = off.checked_add(f_max_off).ok_or(Error::BadStructure(
"BASE: FeatMinMax maxCoord offset overflow",
))?;
if abs >= base_bytes.len() {
return Err(Error::UnexpectedEof);
}
Some(BaseCoord::parse(&base_bytes[abs..])?)
};
feat_min_max_records.push(FeatMinMaxRecord {
feature_tag,
min_coord: feat_min,
max_coord: feat_max,
});
}
Ok(MinMaxTable {
min_coord,
max_coord,
feat_min_max_records,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn push_coord_f1(buf: &mut Vec<u8>, coord: i16) -> usize {
let off = buf.len();
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(&coord.to_be_bytes());
off
}
fn push_coord_f2(buf: &mut Vec<u8>, coord: i16, ref_glyph: u16, contour_pt: u16) -> usize {
let off = buf.len();
buf.extend_from_slice(&2u16.to_be_bytes());
buf.extend_from_slice(&coord.to_be_bytes());
buf.extend_from_slice(&ref_glyph.to_be_bytes());
buf.extend_from_slice(&contour_pt.to_be_bytes());
off
}
fn push_coord_f3(buf: &mut Vec<u8>, coord: i16, device_off: u16) -> usize {
let off = buf.len();
buf.extend_from_slice(&3u16.to_be_bytes());
buf.extend_from_slice(&coord.to_be_bytes());
buf.extend_from_slice(&device_off.to_be_bytes());
off
}
fn build_horiz_only_example() -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
let horiz_axis_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_axis_start = buf.len();
let off_he = (horiz_axis_start as u16).to_be_bytes();
buf[horiz_axis_off_pos..horiz_axis_off_pos + 2].copy_from_slice(&off_he);
let tag_list_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let script_list_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let tag_list_start = buf.len();
buf[tag_list_off_pos..tag_list_off_pos + 2]
.copy_from_slice(&((tag_list_start - horiz_axis_start) as u16).to_be_bytes());
buf.extend_from_slice(&2u16.to_be_bytes()); buf.extend_from_slice(b"ideo");
buf.extend_from_slice(b"romn");
let script_list_start = buf.len();
buf[script_list_off_pos..script_list_off_pos + 2]
.copy_from_slice(&((script_list_start - horiz_axis_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(b"latn");
let latn_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_start = buf.len();
buf[latn_off_pos..latn_off_pos + 2]
.copy_from_slice(&((latn_start - script_list_start) as u16).to_be_bytes());
let bv_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let dmm_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let bv_start = buf.len();
buf[bv_off_pos..bv_off_pos + 2]
.copy_from_slice(&((bv_start - latn_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(&2u16.to_be_bytes()); let bc0_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let bc1_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let bc0_start = buf.len();
buf[bc0_off_pos..bc0_off_pos + 2]
.copy_from_slice(&((bc0_start - bv_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, -120);
let bc1_start = buf.len();
buf[bc1_off_pos..bc1_off_pos + 2]
.copy_from_slice(&((bc1_start - bv_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, 0);
let dmm_start = buf.len();
buf[dmm_off_pos..dmm_off_pos + 2]
.copy_from_slice(&((dmm_start - latn_start) as u16).to_be_bytes());
let dmm_min_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let dmm_max_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let dmin_start = buf.len();
buf[dmm_min_off_pos..dmm_min_off_pos + 2]
.copy_from_slice(&((dmin_start - dmm_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, -432);
let dmax_start = buf.len();
buf[dmm_max_off_pos..dmm_max_off_pos + 2]
.copy_from_slice(&((dmax_start - dmm_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, 1750);
buf
}
#[test]
fn parses_horiz_only_worked_example() {
let bytes = build_horiz_only_example();
let base = BaseTable::parse(&bytes).expect("parse");
assert_eq!(base.major_version, BASE_MAJOR_VERSION);
assert_eq!(base.minor_version, BASE_MINOR_VERSION_0);
assert!(base.vert_axis.is_none());
let h = base.horiz_axis.as_ref().expect("HorizAxis present");
let tags = h.baseline_tags.as_ref().expect("BaseTagList present");
assert_eq!(tags, &vec![*b"ideo", *b"romn"]);
assert_eq!(h.base_scripts.len(), 1);
let latn = &h.base_scripts[0];
assert_eq!(latn.script_tag, *b"latn");
let bv = latn.base_script.base_values.as_ref().expect("BaseValues");
assert_eq!(bv.default_baseline_index, 1);
assert_eq!(bv.base_coords.len(), 2);
assert_eq!(bv.base_coords[0].coordinate(), -120);
assert_eq!(bv.base_coords[1].coordinate(), 0);
let dmm = latn
.base_script
.default_min_max
.as_ref()
.expect("default MinMax");
assert_eq!(dmm.min_coord.unwrap().coordinate(), -432);
assert_eq!(dmm.max_coord.unwrap().coordinate(), 1750);
assert_eq!(dmm.feat_min_max_records.len(), 0);
assert_eq!(latn.base_script.base_lang_sys_records.len(), 0);
assert_eq!(h.baseline_index_for_tag(*b"ideo"), Some(0));
assert_eq!(h.baseline_index_for_tag(*b"romn"), Some(1));
assert_eq!(h.baseline_index_for_tag(*b"hang"), None);
assert!(h.base_script_for_tag(*b"latn").is_some());
assert!(h.base_script_for_tag(*b"arab").is_none());
}
#[test]
fn rejects_short_header() {
assert!(matches!(
BaseTable::parse(&[0u8; 7]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_wrong_major_version() {
let mut b = build_horiz_only_example();
b[0..2].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(BaseTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_unknown_minor_version() {
let mut b = build_horiz_only_example();
b[2..4].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(BaseTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn parses_format2_and_format3_base_coords() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
let horiz_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_start = buf.len();
buf[horiz_off_pos..horiz_off_pos + 2].copy_from_slice(&(horiz_start as u16).to_be_bytes());
let tag_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let script_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let tag_start = buf.len();
buf[tag_off_pos..tag_off_pos + 2]
.copy_from_slice(&((tag_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"romn");
let script_start = buf.len();
buf[script_off_pos..script_off_pos + 2]
.copy_from_slice(&((script_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"latn");
let latn_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_start = buf.len();
buf[latn_off_pos..latn_off_pos + 2]
.copy_from_slice(&((latn_start - script_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); let dmm_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
let dmm_start = buf.len();
buf[dmm_off_pos..dmm_off_pos + 2]
.copy_from_slice(&((dmm_start - latn_start) as u16).to_be_bytes());
let dmm_min_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let dmm_max_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let f2_start = buf.len();
buf[dmm_min_pos..dmm_min_pos + 2]
.copy_from_slice(&((f2_start - dmm_start) as u16).to_be_bytes());
let _ = push_coord_f2(&mut buf, -500, 42, 7);
let f3_start = buf.len();
buf[dmm_max_pos..dmm_max_pos + 2]
.copy_from_slice(&((f3_start - dmm_start) as u16).to_be_bytes());
let _ = push_coord_f3(&mut buf, 1200, 10);
buf.extend_from_slice(&[0u8; 16]);
let base = BaseTable::parse(&buf).expect("parse");
let h = base.horiz_axis.unwrap();
let latn = &h.base_scripts[0].base_script;
let dmm = latn.default_min_max.as_ref().unwrap();
match dmm.min_coord.unwrap() {
BaseCoord::Format2 {
coordinate,
reference_glyph,
base_coord_point,
} => {
assert_eq!(coordinate, -500);
assert_eq!(reference_glyph, 42);
assert_eq!(base_coord_point, 7);
}
other => panic!("expected Format2, got {other:?}"),
}
match dmm.max_coord.unwrap() {
BaseCoord::Format3 {
coordinate,
device_offset,
} => {
assert_eq!(coordinate, 1200);
assert_eq!(device_offset, Some(10));
}
other => panic!("expected Format3, got {other:?}"),
}
assert_eq!(dmm.min_coord.unwrap().format(), 2);
assert_eq!(dmm.max_coord.unwrap().format(), 3);
}
#[test]
fn parses_lang_sys_and_feat_min_max() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
let horiz_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let horiz_start = buf.len();
buf[horiz_off_pos..horiz_off_pos + 2].copy_from_slice(&(horiz_start as u16).to_be_bytes());
let tag_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let script_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let tag_start = buf.len();
buf[tag_off_pos..tag_off_pos + 2]
.copy_from_slice(&((tag_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"romn");
let script_start = buf.len();
buf[script_off_pos..script_off_pos + 2]
.copy_from_slice(&((script_start - horiz_start) as u16).to_be_bytes());
buf.extend_from_slice(&1u16.to_be_bytes());
buf.extend_from_slice(b"latn");
let latn_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let latn_start = buf.len();
buf[latn_off_pos..latn_off_pos + 2]
.copy_from_slice(&((latn_start - script_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(b"URD "); let urd_mm_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let urd_mm_start = buf.len();
buf[urd_mm_off_pos..urd_mm_off_pos + 2]
.copy_from_slice(&((urd_mm_start - latn_start) as u16).to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); let urd_max_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(b"sups"); let sups_min_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes()); let sups_max_off_pos = buf.len();
buf.extend_from_slice(&0u16.to_be_bytes());
let urd_max_start = buf.len();
buf[urd_max_off_pos..urd_max_off_pos + 2]
.copy_from_slice(&((urd_max_start - urd_mm_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, 1800);
let sups_min_start = buf.len();
buf[sups_min_off_pos..sups_min_off_pos + 2]
.copy_from_slice(&((sups_min_start - urd_mm_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, 600);
let sups_max_start = buf.len();
buf[sups_max_off_pos..sups_max_off_pos + 2]
.copy_from_slice(&((sups_max_start - urd_mm_start) as u16).to_be_bytes());
let _ = push_coord_f1(&mut buf, 2100);
let base = BaseTable::parse(&buf).expect("parse");
let h = base.horiz_axis.unwrap();
let latn = &h.base_scripts[0].base_script;
assert!(latn.default_min_max.is_none());
assert_eq!(latn.base_lang_sys_records.len(), 1);
let lsr = &latn.base_lang_sys_records[0];
assert_eq!(lsr.lang_sys_tag, *b"URD ");
assert!(lsr.min_max.min_coord.is_none());
assert_eq!(lsr.min_max.max_coord.unwrap().coordinate(), 1800);
assert_eq!(lsr.min_max.feat_min_max_records.len(), 1);
let feat = &lsr.min_max.feat_min_max_records[0];
assert_eq!(feat.feature_tag, *b"sups");
assert_eq!(feat.min_coord.unwrap().coordinate(), 600);
assert_eq!(feat.max_coord.unwrap().coordinate(), 2100);
assert!(latn.min_max_for_lang_sys(*b"URD ").is_some());
assert!(latn.min_max_for_lang_sys(*b"DEU ").is_none());
}
#[test]
fn rejects_axis_missing_base_script_list() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
buf.extend_from_slice(&8u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
let res = BaseTable::parse(&buf);
assert!(matches!(res, Err(Error::BadStructure(_))));
}
#[test]
fn rejects_base_tag_list_running_past_end() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
buf.extend_from_slice(&8u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&4u16.to_be_bytes()); buf.extend_from_slice(&8u16.to_be_bytes()); buf.extend_from_slice(&5u16.to_be_bytes());
buf.extend_from_slice(b"romn");
let res = BaseTable::parse(&buf);
assert!(matches!(res, Err(Error::UnexpectedEof)));
}
#[test]
fn parses_v1_1_with_item_var_store() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_1.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&12u32.to_be_bytes()); buf.extend_from_slice(&[0xAB, 0xCD, 0xEF, 0x01]);
let base = BaseTable::parse(&buf).expect("parse");
assert_eq!(base.major_version, BASE_MAJOR_VERSION);
assert_eq!(base.minor_version, BASE_MINOR_VERSION_1);
assert!(base.horiz_axis.is_none());
assert!(base.vert_axis.is_none());
assert_eq!(base.item_var_store_offset, Some(12));
assert_eq!(
base.item_var_store_bytes(),
Some([0xAB, 0xCD, 0xEF, 0x01].as_slice())
);
}
#[test]
fn rejects_v1_1_item_var_store_offset_past_end() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_1.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&999u32.to_be_bytes());
assert!(matches!(
BaseTable::parse(&buf),
Err(Error::BadStructure(_))
));
}
#[test]
fn v1_0_table_has_no_item_var_store() {
let bytes = build_horiz_only_example();
let base = BaseTable::parse(&bytes).expect("parse");
assert!(base.item_var_store_offset.is_none());
assert!(base.item_var_store_bytes().is_none());
}
#[test]
fn base_coord_format1_rejects_unknown_format() {
let bytes = [0x00, 0x07, 0x00, 0x00];
assert!(matches!(
BaseCoord::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn base_coord_format2_rejects_short_slice() {
let bytes = [0x00, 0x02, 0x00, 0x00, 0x00, 0x00];
assert!(matches!(
BaseCoord::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn base_coord_format3_rejects_short_slice() {
let bytes = [0x00, 0x03, 0x00, 0x00, 0x00];
assert!(matches!(
BaseCoord::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_zero_min_max_offset_in_base_lang_sys_record() {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&BASE_MAJOR_VERSION.to_be_bytes());
buf.extend_from_slice(&BASE_MINOR_VERSION_0.to_be_bytes());
buf.extend_from_slice(&8u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&4u16.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(b"latn");
buf.extend_from_slice(&8u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(b"URD ");
buf.extend_from_slice(&0u16.to_be_bytes()); let res = BaseTable::parse(&buf);
assert!(matches!(res, Err(Error::BadStructure(_))));
}
}