use crate::parser::{read_i16, read_u16};
use crate::Error;
pub const VORG_MAJOR_VERSION: u16 = 1;
pub const VORG_MINOR_VERSION: u16 = 0;
const MAX_METRICS_ENTRIES: usize = u16::MAX as usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VertOriginEntry {
pub glyph_index: u16,
pub vert_origin_y: i16,
}
#[derive(Debug, Clone)]
pub struct VorgTable {
pub major_version: u16,
pub minor_version: u16,
pub default_vert_origin_y: i16,
metrics: Vec<VertOriginEntry>,
}
impl VorgTable {
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 != VORG_MAJOR_VERSION {
return Err(Error::BadStructure("VORG: majorVersion != 1"));
}
if minor_version != VORG_MINOR_VERSION {
return Err(Error::BadStructure("VORG: minorVersion != 0"));
}
let default_vert_origin_y = read_i16(bytes, 4)?;
let count = read_u16(bytes, 6)? as usize;
if count > MAX_METRICS_ENTRIES {
return Err(Error::BadStructure("VORG: numVertOriginYMetrics cap"));
}
let body_end = 8usize
.checked_add(
count
.checked_mul(4)
.ok_or(Error::BadStructure("VORG: numVertOriginYMetrics overflow"))?,
)
.ok_or(Error::BadStructure("VORG: numVertOriginYMetrics overflow"))?;
if bytes.len() < body_end {
return Err(Error::UnexpectedEof);
}
let mut metrics: Vec<VertOriginEntry> = Vec::with_capacity(count);
let mut last: Option<u16> = None;
for i in 0..count {
let off = 8 + i * 4;
let glyph_index = read_u16(bytes, off)?;
let vert_origin_y = read_i16(bytes, off + 2)?;
if let Some(prev) = last {
if glyph_index <= prev {
return Err(Error::BadStructure(
"VORG: vertOriginYMetrics not strictly increasing",
));
}
}
last = Some(glyph_index);
metrics.push(VertOriginEntry {
glyph_index,
vert_origin_y,
});
}
Ok(Self {
major_version,
minor_version,
default_vert_origin_y,
metrics,
})
}
pub fn metrics_len(&self) -> usize {
self.metrics.len()
}
pub fn metrics(&self) -> &[VertOriginEntry] {
&self.metrics
}
pub fn vert_origin_y(&self, glyph_id: u16) -> i16 {
match self
.metrics
.binary_search_by_key(&glyph_id, |e| e.glyph_index)
{
Ok(idx) => self.metrics[idx].vert_origin_y,
Err(_) => self.default_vert_origin_y,
}
}
pub fn vert_origin_y_override(&self, glyph_id: u16) -> Option<i16> {
self.metrics
.binary_search_by_key(&glyph_id, |e| e.glyph_index)
.ok()
.map(|i| self.metrics[i].vert_origin_y)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build(default_y: i16, entries: &[(u16, i16)]) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(&VORG_MAJOR_VERSION.to_be_bytes());
b.extend_from_slice(&VORG_MINOR_VERSION.to_be_bytes());
b.extend_from_slice(&default_y.to_be_bytes());
b.extend_from_slice(&(entries.len() as u16).to_be_bytes());
for (gid, y) in entries {
b.extend_from_slice(&gid.to_be_bytes());
b.extend_from_slice(&y.to_be_bytes());
}
b
}
#[test]
fn empty_metrics_table_uses_default_for_all_glyphs() {
let bytes = build(880, &[]);
assert_eq!(bytes.len(), 8);
let vorg = VorgTable::parse(&bytes).expect("parse");
assert_eq!(vorg.default_vert_origin_y, 880);
assert_eq!(vorg.metrics_len(), 0);
assert_eq!(vorg.vert_origin_y(0), 880);
assert_eq!(vorg.vert_origin_y(12345), 880);
assert!(vorg.vert_origin_y_override(0).is_none());
}
#[test]
fn worked_example_from_spec() {
let bytes = build(880, &[(10, 889), (12, 861), (13, 849)]);
let vorg = VorgTable::parse(&bytes).expect("parse");
assert_eq!(vorg.default_vert_origin_y, 880);
assert_eq!(vorg.metrics_len(), 3);
assert_eq!(vorg.vert_origin_y(0), 880);
assert_eq!(vorg.vert_origin_y(9), 880);
assert_eq!(vorg.vert_origin_y(11), 880);
assert_eq!(vorg.vert_origin_y(14), 880);
assert_eq!(vorg.vert_origin_y(10), 889);
assert_eq!(vorg.vert_origin_y(12), 861);
assert_eq!(vorg.vert_origin_y(13), 849);
assert_eq!(vorg.vert_origin_y_override(10), Some(889));
assert_eq!(vorg.vert_origin_y_override(11), None);
}
#[test]
fn rejects_short_header() {
let b = vec![0u8; 7];
assert!(matches!(VorgTable::parse(&b), Err(Error::UnexpectedEof)));
}
#[test]
fn rejects_wrong_major_version() {
let mut b = build(0, &[]);
b[0..2].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(VorgTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_nonzero_minor_version() {
let mut b = build(0, &[]);
b[2..4].copy_from_slice(&1u16.to_be_bytes());
assert!(matches!(VorgTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_truncated_metrics_array() {
let mut b = build(100, &[(5, 200)]);
b[6..8].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(VorgTable::parse(&b), Err(Error::UnexpectedEof)));
}
#[test]
fn rejects_unsorted_metrics_array() {
let b = build(0, &[(10, 100), (8, 200)]);
assert!(matches!(VorgTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_duplicate_glyph_index() {
let b = build(0, &[(10, 100), (10, 200)]);
assert!(matches!(VorgTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn binary_search_handles_large_arrays() {
let entries: Vec<(u16, i16)> = (0..512u16).map(|i| (i * 2, i as i16)).collect();
let bytes = build(-1, &entries);
let vorg = VorgTable::parse(&bytes).expect("parse");
for i in 0..256u16 {
assert_eq!(vorg.vert_origin_y(i * 2), i as i16);
assert_eq!(vorg.vert_origin_y(i * 2 + 1), -1);
}
assert_eq!(vorg.vert_origin_y(60_000), -1);
}
#[test]
fn metrics_accessor_returns_sorted_array_in_full() {
let bytes = build(0, &[(1, 10), (2, 20), (3, 30)]);
let vorg = VorgTable::parse(&bytes).expect("parse");
let arr = vorg.metrics();
assert_eq!(arr.len(), 3);
assert_eq!(arr[0].glyph_index, 1);
assert_eq!(arr[0].vert_origin_y, 10);
assert_eq!(arr[2].vert_origin_y, 30);
}
}