use crate::parser::{read_i8, read_u16, read_u32, read_u8};
use crate::Error;
pub const EBSC_MAJOR_VERSION: u16 = 2;
pub const EBSC_MINOR_VERSION: u16 = 0;
const SBIT_LINE_METRICS_LEN: usize = 12;
const BITMAP_SCALE_LEN: usize = SBIT_LINE_METRICS_LEN * 2 + 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SbitLineMetrics {
pub ascender: i8,
pub descender: i8,
pub width_max: u8,
pub caret_slope_numerator: i8,
pub caret_slope_denominator: i8,
pub caret_offset: i8,
pub min_origin_sb: i8,
pub min_advance_sb: i8,
pub max_before_bl: i8,
pub min_after_bl: i8,
pub pad1: i8,
pub pad2: i8,
}
impl SbitLineMetrics {
fn parse(bytes: &[u8], off: usize) -> Result<Self, Error> {
Ok(Self {
ascender: read_i8(bytes, off)?,
descender: read_i8(bytes, off + 1)?,
width_max: read_u8(bytes, off + 2)?,
caret_slope_numerator: read_i8(bytes, off + 3)?,
caret_slope_denominator: read_i8(bytes, off + 4)?,
caret_offset: read_i8(bytes, off + 5)?,
min_origin_sb: read_i8(bytes, off + 6)?,
min_advance_sb: read_i8(bytes, off + 7)?,
max_before_bl: read_i8(bytes, off + 8)?,
min_after_bl: read_i8(bytes, off + 9)?,
pad1: read_i8(bytes, off + 10)?,
pad2: read_i8(bytes, off + 11)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BitmapScale {
pub hori: SbitLineMetrics,
pub vert: SbitLineMetrics,
pub ppem_x: u8,
pub ppem_y: u8,
pub substitute_ppem_x: u8,
pub substitute_ppem_y: u8,
}
impl BitmapScale {
fn parse(bytes: &[u8], off: usize) -> Result<Self, Error> {
let hori = SbitLineMetrics::parse(bytes, off)?;
let vert = SbitLineMetrics::parse(bytes, off + SBIT_LINE_METRICS_LEN)?;
let base = off + 2 * SBIT_LINE_METRICS_LEN;
Ok(Self {
hori,
vert,
ppem_x: read_u8(bytes, base)?,
ppem_y: read_u8(bytes, base + 1)?,
substitute_ppem_x: read_u8(bytes, base + 2)?,
substitute_ppem_y: read_u8(bytes, base + 3)?,
})
}
}
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct EbscTable {
minor_version: u16,
scales: Vec<BitmapScale>,
}
impl EbscTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
if major != EBSC_MAJOR_VERSION {
return Err(Error::BadStructure("EBSC: unknown major version"));
}
let minor_version = read_u16(bytes, 2)?;
let num_sizes = read_u32(bytes, 4)?;
if num_sizes > 256 {
return Err(Error::BadStructure("EBSC: implausible numSizes"));
}
let needed = 8usize
.checked_add(num_sizes as usize * BITMAP_SCALE_LEN)
.ok_or(Error::BadStructure("EBSC: numSizes overflow"))?;
if bytes.len() < needed {
return Err(Error::UnexpectedEof);
}
let mut scales = Vec::with_capacity(num_sizes as usize);
for i in 0..num_sizes as usize {
scales.push(BitmapScale::parse(bytes, 8 + i * BITMAP_SCALE_LEN)?);
}
Ok(Self {
minor_version,
scales,
})
}
pub fn minor_version(&self) -> u16 {
self.minor_version
}
pub fn scales(&self) -> &[BitmapScale] {
&self.scales
}
pub fn num_scales(&self) -> usize {
self.scales.len()
}
pub fn target_ppem_sizes(&self) -> impl Iterator<Item = (u8, u8)> + '_ {
self.scales.iter().map(|s| (s.ppem_x, s.ppem_y))
}
pub fn scale_for_target_ppem(&self, target_ppem: u8) -> Option<&BitmapScale> {
self.scales.iter().find(|s| s.ppem_y == target_ppem)
}
}
pub(crate) fn scale_metric(value: i32, target_ppem: u8, substitute_ppem: u8) -> i32 {
if substitute_ppem == 0 {
return value;
}
let num = value * target_ppem as i32;
let den = substitute_ppem as i32;
if num >= 0 {
(num + den / 2) / den
} else {
-((-num + den / 2) / den)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn line_metrics(asc: i8, desc: i8) -> [u8; 12] {
let mut b = [0u8; 12];
b[0] = asc as u8;
b[1] = desc as u8;
b
}
fn build_ebsc(scales: &[(u8, u8, u8, u8)]) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&EBSC_MAJOR_VERSION.to_be_bytes());
v.extend_from_slice(&EBSC_MINOR_VERSION.to_be_bytes());
v.extend_from_slice(&(scales.len() as u32).to_be_bytes());
for &(ppx, ppy, spx, spy) in scales {
v.extend_from_slice(&line_metrics(ppy as i8, -(ppy as i8) / 4));
v.extend_from_slice(&line_metrics(ppx as i8, -(ppx as i8) / 4));
v.push(ppx);
v.push(ppy);
v.push(spx);
v.push(spy);
}
v
}
#[test]
fn parses_header_and_records() {
let bytes = build_ebsc(&[(20, 20, 16, 16), (24, 24, 16, 16)]);
let t = EbscTable::parse(&bytes).unwrap();
assert_eq!(t.minor_version(), 0);
assert_eq!(t.num_scales(), 2);
let sizes: Vec<_> = t.target_ppem_sizes().collect();
assert_eq!(sizes, vec![(20, 20), (24, 24)]);
let s0 = &t.scales()[0];
assert_eq!(s0.ppem_x, 20);
assert_eq!(s0.substitute_ppem_x, 16);
assert_eq!(s0.hori.ascender, 20);
}
#[test]
fn line_metrics_round_trip() {
let bytes = build_ebsc(&[(20, 20, 16, 16)]);
let t = EbscTable::parse(&bytes).unwrap();
let s = &t.scales()[0];
assert_eq!(s.hori.ascender, 20);
assert_eq!(s.hori.descender, -5);
assert_eq!(s.vert.ascender, 20);
}
#[test]
fn lookup_by_target_ppem() {
let bytes = build_ebsc(&[(20, 20, 16, 16), (24, 24, 16, 16)]);
let t = EbscTable::parse(&bytes).unwrap();
let s = t.scale_for_target_ppem(24).unwrap();
assert_eq!(s.substitute_ppem_y, 16);
assert!(t.scale_for_target_ppem(99).is_none());
}
#[test]
fn rejects_wrong_major_version() {
let mut bytes = build_ebsc(&[(20, 20, 16, 16)]);
bytes[1] = 3; assert!(matches!(
EbscTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_truncated_record_array() {
let mut bytes = build_ebsc(&[(20, 20, 16, 16)]);
bytes.truncate(bytes.len() - 4); assert!(matches!(
EbscTable::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_implausible_num_sizes() {
let mut bytes = vec![0, 2, 0, 0]; bytes.extend_from_slice(&1000u32.to_be_bytes());
assert!(matches!(
EbscTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
#[test]
fn zero_scales_is_valid() {
let bytes = build_ebsc(&[]);
let t = EbscTable::parse(&bytes).unwrap();
assert_eq!(t.num_scales(), 0);
assert!(t.scale_for_target_ppem(20).is_none());
}
#[test]
fn scale_metric_nearest_integer() {
assert_eq!(scale_metric(16, 20, 16), 20);
assert_eq!(scale_metric(10, 24, 16), 15);
assert_eq!(scale_metric(7, 20, 16), 9);
assert_eq!(scale_metric(20, 16, 20), 16);
assert_eq!(scale_metric(-7, 20, 16), -9);
assert_eq!(scale_metric(5, 20, 0), 5);
}
}