use crate::parser::{read_i16, read_i8, read_u16, read_u32, read_u8};
use crate::Error;
const MAX_TUPLES_PER_GLYPH: u16 = 4096;
const MAX_POINTS_PER_GLYPH: usize = 0xFFFF;
const FLAG_LONG_OFFSETS: u16 = 0x0001;
const TI_EMBEDDED_PEAK: u16 = 0x8000;
const TI_INTERMEDIATE: u16 = 0x4000;
const TI_PRIVATE_POINTS: u16 = 0x2000;
const TI_TUPLE_INDEX_MASK: u16 = 0x0FFF;
#[derive(Debug, Clone)]
pub struct GvarTable<'a> {
bytes: &'a [u8],
axis_count: u16,
shared_tuple_count: u16,
shared_tuples_offset: usize,
glyph_count: u16,
offsets: Vec<u32>,
glyph_data_array_offset: usize,
}
impl<'a> GvarTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 20 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
if major != 1 {
return Err(Error::BadStructure("gvar version not 1.x"));
}
let axis_count = read_u16(bytes, 4)?;
let shared_tuple_count = read_u16(bytes, 6)?;
let shared_tuples_offset = read_u32(bytes, 8)? as usize;
let glyph_count = read_u16(bytes, 12)?;
let flags = read_u16(bytes, 14)?;
let long_offsets = flags & FLAG_LONG_OFFSETS != 0;
let glyph_data_array_offset = read_u32(bytes, 16)? as usize;
let entry = if long_offsets { 4 } else { 2 };
let off_array_start = 20usize;
let off_array_end = off_array_start
.checked_add(entry * (glyph_count as usize + 1))
.ok_or(Error::BadOffset)?;
if bytes.len() < off_array_end {
return Err(Error::UnexpectedEof);
}
let mut offsets = Vec::with_capacity(glyph_count as usize + 1);
for i in 0..=glyph_count as usize {
let off = off_array_start + i * entry;
let v = if long_offsets {
read_u32(bytes, off)?
} else {
read_u16(bytes, off)? as u32 * 2
};
offsets.push(v);
}
Ok(Self {
bytes,
axis_count,
shared_tuple_count,
shared_tuples_offset,
glyph_count,
offsets,
glyph_data_array_offset,
})
}
pub fn axis_count(&self) -> u16 {
self.axis_count
}
pub fn glyph_count(&self) -> u16 {
self.glyph_count
}
fn shared_tuple(&self, i: u16) -> Result<Vec<f32>, Error> {
if i >= self.shared_tuple_count {
return Err(Error::BadStructure("gvar shared tuple index out of range"));
}
let stride = self.axis_count as usize * 2;
let off = self
.shared_tuples_offset
.checked_add(i as usize * stride)
.ok_or(Error::BadOffset)?;
if off + stride > self.bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut t = Vec::with_capacity(self.axis_count as usize);
for ai in 0..self.axis_count as usize {
t.push(f2dot14(read_i16(self.bytes, off + ai * 2)?));
}
Ok(t)
}
pub fn glyph_deltas(
&self,
glyph_id: u16,
num_points: u16,
coords: &[f32],
) -> Result<Vec<(i32, i32)>, Error> {
let np = num_points as usize;
let total = np.checked_add(4).ok_or(Error::BadOffset)?;
let mut full = self.decode_deltas(glyph_id, total, coords, None)?;
full.truncate(np);
Ok(full)
}
pub fn glyph_deltas_iup(
&self,
glyph_id: u16,
outline: &SimpleOutlineInfo,
coords: &[f32],
) -> Result<Vec<(i32, i32)>, Error> {
let np = outline.points.len();
let total = np.checked_add(4).ok_or(Error::BadOffset)?;
let mut full = self.decode_deltas(glyph_id, total, coords, Some(outline))?;
full.truncate(np);
Ok(full)
}
pub fn glyph_component_deltas(
&self,
glyph_id: u16,
num_components: u16,
coords: &[f32],
) -> Result<Vec<(i32, i32)>, Error> {
let nc = num_components as usize;
let total = nc.checked_add(4).ok_or(Error::BadOffset)?;
let mut full = self.decode_deltas(glyph_id, total, coords, None)?;
full.truncate(nc);
Ok(full)
}
fn decode_deltas(
&self,
glyph_id: u16,
total_points: usize,
coords: &[f32],
outline: Option<&SimpleOutlineInfo>,
) -> Result<Vec<(i32, i32)>, Error> {
if total_points > MAX_POINTS_PER_GLYPH {
return Err(Error::BadStructure("gvar point count exceeds cap"));
}
let mut out = vec![(0i32, 0i32); total_points];
if glyph_id >= self.glyph_count {
return Err(Error::GlyphOutOfRange(glyph_id));
}
if coords.len() != self.axis_count as usize {
return Err(Error::BadStructure(
"gvar coord vector length != fvar axis count",
));
}
let start = self.offsets[glyph_id as usize] as usize;
let end = self.offsets[glyph_id as usize + 1] as usize;
if end <= start {
return Ok(out);
}
let block_off = self
.glyph_data_array_offset
.checked_add(start)
.ok_or(Error::BadOffset)?;
let block_len = end - start;
if block_off + block_len > self.bytes.len() {
return Err(Error::UnexpectedEof);
}
let block = &self.bytes[block_off..block_off + block_len];
if block.len() < 4 {
return Ok(out);
}
let tuple_count = read_u16(block, 0)?;
let n_tuples = tuple_count & 0x0FFF; if n_tuples > MAX_TUPLES_PER_GLYPH {
return Err(Error::BadStructure("gvar tupleVariationCount > cap"));
}
let data_offset = read_u16(block, 2)? as usize;
if data_offset > block.len() {
return Err(Error::BadOffset);
}
let mut hdr_off = 4usize;
let mut data_cursor = data_offset;
let shared_points: Option<Vec<u16>> = if data_offset < block.len() {
let shared_slice = &block[data_offset..];
let (pts, used) = decode_packed_points(shared_slice, total_points as u16)?;
data_cursor = data_offset + used;
Some(pts)
} else {
None
};
for _ in 0..n_tuples {
if hdr_off + 4 > block.len() {
return Err(Error::BadStructure("gvar tuple header truncated"));
}
let var_data_size = read_u16(block, hdr_off)? as usize;
let tuple_index = read_u16(block, hdr_off + 2)?;
hdr_off += 4;
let peak = if tuple_index & TI_EMBEDDED_PEAK != 0 {
let need = self.axis_count as usize * 2;
if hdr_off + need > block.len() {
return Err(Error::BadStructure("gvar embedded peak truncated"));
}
let mut p = Vec::with_capacity(self.axis_count as usize);
for ai in 0..self.axis_count as usize {
p.push(f2dot14(read_i16(block, hdr_off + ai * 2)?));
}
hdr_off += need;
p
} else {
let idx = tuple_index & TI_TUPLE_INDEX_MASK;
self.shared_tuple(idx)?
};
let (start_t, end_t) = if tuple_index & TI_INTERMEDIATE != 0 {
let need = self.axis_count as usize * 4;
if hdr_off + need > block.len() {
return Err(Error::BadStructure("gvar intermediate region truncated"));
}
let mut s = Vec::with_capacity(self.axis_count as usize);
let mut e = Vec::with_capacity(self.axis_count as usize);
for ai in 0..self.axis_count as usize {
s.push(f2dot14(read_i16(block, hdr_off + ai * 2)?));
}
for ai in 0..self.axis_count as usize {
e.push(f2dot14(read_i16(
block,
hdr_off + self.axis_count as usize * 2 + ai * 2,
)?));
}
hdr_off += need;
(Some(s), Some(e))
} else {
(None, None)
};
let scalar = tuple_scalar(coords, &peak, start_t.as_deref(), end_t.as_deref());
if data_cursor + var_data_size > block.len() {
return Err(Error::BadStructure("gvar tuple data overruns"));
}
let tuple_data = &block[data_cursor..data_cursor + var_data_size];
data_cursor += var_data_size;
if scalar == 0.0 {
continue;
}
let mut td_off = 0usize;
let points = if tuple_index & TI_PRIVATE_POINTS != 0 {
let (pts, used) = decode_packed_points(tuple_data, total_points as u16)?;
td_off += used;
pts
} else {
shared_points.clone().unwrap_or_else(|| {
(0..total_points as u16).collect()
})
};
let n_pts = points.len();
let dxs = decode_packed_deltas(tuple_data, &mut td_off, n_pts)?;
let dys = decode_packed_deltas(tuple_data, &mut td_off, n_pts)?;
let all_points = n_pts == total_points;
if let (Some(info), false) = (outline, all_points) {
let region = infer_region_deltas(info, total_points, &points, &dxs, &dys);
for (pi, (rdx, rdy)) in region.into_iter().enumerate() {
if rdx == 0.0 && rdy == 0.0 {
continue;
}
out[pi].0 += (rdx * scalar as f64).round() as i32;
out[pi].1 += (rdy * scalar as f64).round() as i32;
}
} else {
for (i, &p_idx) in points.iter().enumerate() {
let pi = p_idx as usize;
if pi >= total_points {
continue;
}
let dx = (dxs[i] as f32 * scalar).round() as i32;
let dy = (dys[i] as f32 * scalar).round() as i32;
out[pi].0 += dx;
out[pi].1 += dy;
}
}
}
Ok(out)
}
}
#[derive(Debug, Clone)]
pub struct SimpleOutlineInfo {
pub points: Vec<(i32, i32)>,
pub contour_ends: Vec<u16>,
}
impl SimpleOutlineInfo {
pub fn from_contours(contours: &[Vec<(i32, i32)>]) -> Self {
let mut points = Vec::new();
let mut contour_ends = Vec::with_capacity(contours.len());
for c in contours {
points.extend_from_slice(c);
if !points.is_empty() {
contour_ends.push((points.len() - 1) as u16);
}
}
Self {
points,
contour_ends,
}
}
}
fn infer_region_deltas(
info: &SimpleOutlineInfo,
total_points: usize,
referenced: &[u16],
dxs: &[i32],
dys: &[i32],
) -> Vec<(f64, f64)> {
let np = info.points.len();
let mut delta = vec![(0f64, 0f64); total_points];
let mut has = vec![false; total_points];
for (i, &p) in referenced.iter().enumerate() {
let pi = p as usize;
if pi < total_points {
delta[pi].0 += dxs[i] as f64;
delta[pi].1 += dys[i] as f64;
has[pi] = true;
}
}
let mut contour_start = 0usize;
for &end in &info.contour_ends {
let end = end as usize;
if end >= np || contour_start > end {
contour_start = end + 1;
continue;
}
infer_contour(&mut delta, &has, &info.points, contour_start, end);
contour_start = end + 1;
}
delta
}
fn infer_contour(
delta: &mut [(f64, f64)],
has: &[bool],
points: &[(i32, i32)],
start: usize,
end: usize,
) {
let n = end - start + 1;
let referenced: Vec<usize> = (start..=end).filter(|&i| has[i]).collect();
if referenced.is_empty() {
return;
}
if referenced.len() == n {
return;
}
if referenced.len() == 1 {
let r = referenced[0];
let (dx, dy) = delta[r];
for i in start..=end {
if !has[i] {
delta[i] = (dx, dy);
}
}
return;
}
let m = referenced.len();
for ri in 0..m {
let cur = referenced[ri];
let nxt = referenced[(ri + 1) % m];
let mut t = next_in_contour(cur, start, end);
while t != nxt {
let dx = infer_axis(
points[t].0,
points[cur].0,
points[nxt].0,
delta[cur].0,
delta[nxt].0,
);
let dy = infer_axis(
points[t].1,
points[cur].1,
points[nxt].1,
delta[cur].1,
delta[nxt].1,
);
delta[t] = (dx, dy);
t = next_in_contour(t, start, end);
}
}
}
#[inline]
fn next_in_contour(i: usize, start: usize, end: usize) -> usize {
if i >= end {
start
} else {
i + 1
}
}
fn infer_axis(
target_coord: i32,
prec_coord: i32,
foll_coord: i32,
prec_delta: f64,
foll_delta: f64,
) -> f64 {
if prec_coord == foll_coord {
if prec_delta == foll_delta {
prec_delta
} else {
0.0
}
} else {
let (min_c, max_c) = if prec_coord < foll_coord {
(prec_coord, foll_coord)
} else {
(foll_coord, prec_coord)
};
if target_coord <= min_c {
if prec_coord < foll_coord {
prec_delta
} else {
foll_delta
}
} else if target_coord >= max_c {
if prec_coord > foll_coord {
prec_delta
} else {
foll_delta
}
} else {
let proportion = (target_coord - prec_coord) as f64 / (foll_coord - prec_coord) as f64;
prec_delta + proportion * (foll_delta - prec_delta)
}
}
}
#[inline]
pub(crate) fn f2dot14(raw: i16) -> f32 {
raw as f32 / 16384.0
}
pub(crate) fn tuple_scalar(
coords: &[f32],
peak: &[f32],
start: Option<&[f32]>,
end: Option<&[f32]>,
) -> f32 {
let mut s = 1.0f32;
for (ai, &c) in coords.iter().enumerate() {
let p = peak.get(ai).copied().unwrap_or(0.0);
if p == 0.0 {
continue;
}
if c == p {
continue;
}
if (c < 0.0) != (p < 0.0) && c != 0.0 {
return 0.0;
}
match (start, end) {
(Some(st), Some(en)) => {
let s_v = st.get(ai).copied().unwrap_or(0.0);
let e_v = en.get(ai).copied().unwrap_or(0.0);
if c < s_v || c > e_v {
return 0.0;
}
if c < p {
if (p - s_v).abs() < f32::EPSILON {
return 0.0;
}
s *= (c - s_v) / (p - s_v);
} else {
if (e_v - p).abs() < f32::EPSILON {
return 0.0;
}
s *= (e_v - c) / (e_v - p);
}
}
_ => {
if c.abs() > p.abs() {
return 0.0;
}
if p.abs() < f32::EPSILON {
return 0.0;
}
s *= c / p;
}
}
}
s
}
pub(crate) fn decode_packed_points(
bytes: &[u8],
total_points: u16,
) -> Result<(Vec<u16>, usize), Error> {
if bytes.is_empty() {
return Err(Error::BadStructure("gvar packed points truncated"));
}
let mut off = 0usize;
let first = read_u8(bytes, off)?;
off += 1;
if first == 0 {
return Ok(((0..total_points).collect(), off));
}
let count = if first & 0x80 != 0 {
let lo = read_u8(bytes, off)? as u16;
off += 1;
((first & 0x7F) as u16) << 8 | lo
} else {
first as u16
};
let mut out = Vec::with_capacity(count as usize);
let mut last: u16 = 0;
while (out.len() as u16) < count {
let ctrl = read_u8(bytes, off)?;
off += 1;
let words = ctrl & 0x80 != 0;
let run = (ctrl & 0x7F) as u16 + 1;
for _ in 0..run {
if (out.len() as u16) >= count {
break;
}
let delta = if words {
let v = read_u16(bytes, off)?;
off += 2;
v
} else {
let v = read_u8(bytes, off)? as u16;
off += 1;
v
};
last = last
.checked_add(delta)
.ok_or(Error::BadStructure("gvar packed point overflow"))?;
out.push(last);
}
}
Ok((out, off))
}
pub(crate) fn decode_packed_deltas(
bytes: &[u8],
off: &mut usize,
n: usize,
) -> Result<Vec<i32>, Error> {
let mut out = Vec::with_capacity(n);
while out.len() < n {
if *off >= bytes.len() {
return Err(Error::BadStructure("gvar packed deltas truncated"));
}
let ctrl = read_u8(bytes, *off)?;
*off += 1;
let zeros = ctrl & 0x80 != 0;
let words = ctrl & 0x40 != 0;
let run = (ctrl & 0x3F) as usize + 1;
for _ in 0..run {
if out.len() >= n {
break;
}
if zeros {
out.push(0);
} else if words {
let v = read_i16(bytes, *off)? as i32;
*off += 2;
out.push(v);
} else {
let v = read_i8(bytes, *off)? as i32;
*off += 1;
out.push(v);
}
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn build_empty_one_glyph() -> Vec<u8> {
let mut b = vec![0u8; 20 + 4];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[4..6].copy_from_slice(&1u16.to_be_bytes()); b[12..14].copy_from_slice(&1u16.to_be_bytes()); b[16..20].copy_from_slice(&24u32.to_be_bytes()); b
}
#[test]
fn gvar_zero_coords_yields_static_outline() {
let raw = build_empty_one_glyph();
let g = GvarTable::parse(&raw).expect("parse");
let deltas = g.glyph_deltas(0, 5, &[0.5]).expect("deltas");
assert_eq!(deltas.len(), 5);
assert!(deltas.iter().all(|&(x, y)| x == 0 && y == 0));
let deltas0 = g.glyph_deltas(0, 5, &[0.0]).expect("deltas");
assert!(deltas0.iter().all(|&(x, y)| x == 0 && y == 0));
}
#[test]
fn gvar_packed_points_all_sentinel() {
let (pts, used) = decode_packed_points(&[0x00, 0xff, 0xff], 5).unwrap();
assert_eq!(pts, vec![0, 1, 2, 3, 4]);
assert_eq!(used, 1);
}
#[test]
fn gvar_packed_points_short_run() {
let raw = [3u8, 0x02, 1, 1, 1];
let (pts, used) = decode_packed_points(&raw, 100).unwrap();
assert_eq!(pts, vec![1, 2, 3]);
assert_eq!(used, 5);
}
#[test]
fn gvar_packed_deltas_words_then_zeros() {
let mut raw = vec![0x41u8];
raw.extend_from_slice(&10i16.to_be_bytes());
raw.extend_from_slice(&(-3i16).to_be_bytes());
raw.push(0x82);
let mut off = 0usize;
let d = decode_packed_deltas(&raw, &mut off, 5).unwrap();
assert_eq!(d, vec![10, -3, 0, 0, 0]);
assert_eq!(off, 6);
}
#[test]
fn gvar_packed_deltas_byte_run() {
let raw = [0x03u8, 1, 0xFF, 2, 0xFE];
let mut off = 0usize;
let d = decode_packed_deltas(&raw, &mut off, 4).unwrap();
assert_eq!(d, vec![1, -1, 2, -2]);
}
#[test]
fn gvar_tuple_scalar_at_peak_is_one() {
let coords = [0.5];
let peak = [0.5];
assert!((tuple_scalar(&coords, &peak, None, None) - 1.0).abs() < 1e-6);
}
#[test]
fn gvar_tuple_scalar_at_zero_is_zero() {
let coords = [0.0];
let peak = [1.0];
assert!(tuple_scalar(&coords, &peak, None, None).abs() < 1e-6);
}
#[test]
fn gvar_tuple_scalar_default_region_linear() {
assert!((tuple_scalar(&[0.5], &[1.0], None, None) - 0.5).abs() < 1e-6);
assert!((tuple_scalar(&[-0.25], &[-1.0], None, None) - 0.25).abs() < 1e-6);
assert_eq!(tuple_scalar(&[-0.5], &[1.0], None, None), 0.0);
}
#[test]
fn gvar_tuple_scalar_intermediate_region() {
let s = tuple_scalar(&[0.5], &[1.0], Some(&[0.0]), Some(&[2.0]));
assert!((s - 0.5).abs() < 1e-6);
let s = tuple_scalar(&[1.5], &[1.0], Some(&[0.0]), Some(&[2.0]));
assert!((s - 0.5).abs() < 1e-6);
let s = tuple_scalar(&[2.5], &[1.0], Some(&[0.0]), Some(&[2.0]));
assert_eq!(s, 0.0);
}
fn build_one_tuple_glyph(dxs: &[i16], dys: &[i16]) -> Vec<u8> {
assert_eq!(dxs.len(), dys.len());
let n = dxs.len();
let mut data_area = vec![0x00u8];
let mut tuple_data = Vec::new();
tuple_data.push(0x40 | ((n - 1) as u8));
for &d in dxs {
tuple_data.extend_from_slice(&d.to_be_bytes());
}
tuple_data.push(0x40 | ((n - 1) as u8));
for &d in dys {
tuple_data.extend_from_slice(&d.to_be_bytes());
}
let var_data_size = tuple_data.len() as u16;
data_area.extend_from_slice(&tuple_data);
let tuple_header_len = 4 + 2; let data_offset = (4 + tuple_header_len) as u16;
let mut block = Vec::new();
block.extend_from_slice(&1u16.to_be_bytes()); block.extend_from_slice(&data_offset.to_be_bytes());
block.extend_from_slice(&var_data_size.to_be_bytes());
block.extend_from_slice(&TI_EMBEDDED_PEAK.to_be_bytes());
block.extend_from_slice(&0x4000i16.to_be_bytes());
block.extend_from_slice(&data_area);
let mut b = vec![0u8; 20];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[4..6].copy_from_slice(&1u16.to_be_bytes()); b[12..14].copy_from_slice(&1u16.to_be_bytes()); let data_array_off = 24u32; b[16..20].copy_from_slice(&data_array_off.to_be_bytes());
if block.len() % 2 != 0 {
block.push(0);
}
let end_half = (block.len() / 2) as u16;
b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&end_half.to_be_bytes()); b.extend_from_slice(&block);
b
}
#[test]
fn gvar_glyph_deltas_at_peak_returns_outline_deltas() {
let dxs = [10, 20, 30, 0, 0, 0, 0];
let dys = [-1, -2, -3, 0, 0, 0, 0];
let raw = build_one_tuple_glyph(&dxs, &dys);
let g = GvarTable::parse(&raw).expect("parse");
let out = g.glyph_deltas(0, 3, &[1.0]).expect("deltas");
assert_eq!(out, vec![(10, -1), (20, -2), (30, -3)]);
let half = g.glyph_deltas(0, 3, &[0.5]).expect("deltas");
assert_eq!(half, vec![(5, -1), (10, -1), (15, -2)]);
}
#[test]
fn gvar_glyph_component_deltas_addresses_components_not_points() {
let dxs = [100, 200, 7, 7, 0, 0];
let dys = [5, 6, 0, 0, 9, 9];
let raw = build_one_tuple_glyph(&dxs, &dys);
let g = GvarTable::parse(&raw).expect("parse");
let comp = g.glyph_component_deltas(0, 2, &[1.0]).expect("comp deltas");
assert_eq!(comp, vec![(100, 5), (200, 6)]);
let comp0 = g.glyph_component_deltas(0, 2, &[0.0]).expect("comp deltas");
assert_eq!(comp0, vec![(0, 0), (0, 0)]);
}
#[test]
fn infer_axis_same_coord_same_delta_propagates() {
assert_eq!(infer_axis(50, 10, 10, 7.0, 7.0), 7.0);
assert_eq!(infer_axis(50, 10, 10, 7.0, -3.0), 0.0);
}
#[test]
fn infer_axis_outside_takes_nearer_in_direction() {
assert_eq!(infer_axis(5, 20, 80, 11.0, 99.0), 11.0);
assert_eq!(infer_axis(200, 20, 80, 11.0, 99.0), 99.0);
assert_eq!(infer_axis(5, 80, 20, 11.0, 99.0), 99.0);
assert_eq!(infer_axis(200, 80, 20, 11.0, 99.0), 11.0);
}
#[test]
fn infer_axis_between_linear_interpolates_spec_example() {
let dx = infer_axis(
0, 0, 4, 28.0, -42.0,
);
assert_eq!(dx, 28.0);
let dx = infer_axis(1, 0, 4, 28.0, -42.0);
assert!((dx - 10.5).abs() < 1e-9, "got {dx}");
}
#[test]
fn simple_outline_info_from_contours_builds_ends() {
let contours = vec![vec![(0, 0), (10, 0), (10, 10)], vec![(20, 20), (30, 20)]];
let info = SimpleOutlineInfo::from_contours(&contours);
assert_eq!(info.points.len(), 5);
assert_eq!(info.contour_ends, vec![2, 4]);
}
#[test]
fn infer_region_single_referenced_propagates_to_contour() {
let info = SimpleOutlineInfo {
points: vec![(0, 0), (10, 0), (10, 10), (0, 10)],
contour_ends: vec![3],
};
let region = infer_region_deltas(&info, 8, &[1], &[5], &[-9]);
for (i, d) in region.iter().enumerate().take(4) {
assert_eq!(*d, (5.0, -9.0), "point {i}");
}
for d in region.iter().skip(4) {
assert_eq!(*d, (0.0, 0.0));
}
}
#[test]
fn infer_region_unreferenced_contour_stays_zero() {
let info = SimpleOutlineInfo {
points: vec![(0, 0), (10, 0), (50, 50), (60, 60)],
contour_ends: vec![1, 3],
};
let region = infer_region_deltas(&info, 8, &[2], &[4], &[4]);
assert_eq!(region[0], (0.0, 0.0));
assert_eq!(region[1], (0.0, 0.0));
assert_eq!(region[2], (4.0, 4.0));
assert_eq!(region[3], (4.0, 4.0));
}
#[test]
fn infer_region_between_two_referenced_interpolates() {
let info = SimpleOutlineInfo {
points: vec![(0, 0), (1, 10), (4, 8)],
contour_ends: vec![2],
};
let region = infer_region_deltas(&info, 7, &[0, 2], &[28, -42], &[-62, -57]);
assert_eq!(region[0], (28.0, -62.0));
assert_eq!(region[2], (-42.0, -57.0));
assert!((region[1].0 - 10.5).abs() < 1e-9, "x {}", region[1].0);
assert_eq!(region[1].1, -57.0);
}
fn build_partial_points_glyph(pts: &[u16], dxs: &[i16], dys: &[i16]) -> Vec<u8> {
assert_eq!(pts.len(), dxs.len());
assert_eq!(pts.len(), dys.len());
let n = pts.len();
let mut tuple_data = Vec::new();
tuple_data.push(n as u8); tuple_data.push((n - 1) as u8); let mut last = 0u16;
for &p in pts {
tuple_data.push((p - last) as u8);
last = p;
}
tuple_data.push(0x40 | ((n - 1) as u8));
for &d in dxs {
tuple_data.extend_from_slice(&d.to_be_bytes());
}
tuple_data.push(0x40 | ((n - 1) as u8));
for &d in dys {
tuple_data.extend_from_slice(&d.to_be_bytes());
}
let var_data_size = tuple_data.len() as u16;
let mut data_area = vec![0x00u8];
data_area.extend_from_slice(&tuple_data);
let tuple_header_len = 4 + 2; let data_offset = (4 + tuple_header_len) as u16;
let mut block = Vec::new();
block.extend_from_slice(&1u16.to_be_bytes()); block.extend_from_slice(&data_offset.to_be_bytes());
block.extend_from_slice(&var_data_size.to_be_bytes());
block.extend_from_slice(&(TI_EMBEDDED_PEAK | TI_PRIVATE_POINTS).to_be_bytes());
block.extend_from_slice(&0x4000i16.to_be_bytes()); block.extend_from_slice(&data_area);
let mut b = vec![0u8; 20];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[4..6].copy_from_slice(&1u16.to_be_bytes());
b[12..14].copy_from_slice(&1u16.to_be_bytes());
let data_array_off = 24u32;
b[16..20].copy_from_slice(&data_array_off.to_be_bytes());
if block.len() % 2 != 0 {
block.push(0);
}
let end_half = (block.len() / 2) as u16;
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&end_half.to_be_bytes());
b.extend_from_slice(&block);
b
}
#[test]
fn glyph_deltas_iup_end_to_end_partial_set() {
let raw = build_partial_points_glyph(&[0, 2], &[28, -42], &[-62, -57]);
let g = GvarTable::parse(&raw).expect("parse");
let info = SimpleOutlineInfo {
points: vec![(0, 0), (1, 10), (4, 8)],
contour_ends: vec![2],
};
let out = g.glyph_deltas_iup(0, &info, &[1.0]).expect("iup deltas");
assert_eq!(out.len(), 3);
assert_eq!(out[0], (28, -62));
assert_eq!(out[2], (-42, -57));
assert_eq!(out[1], (11, -57)); }
#[test]
fn repeated_point_numbers_accumulate_cumulatively() {
let raw = build_partial_points_glyph(&[0, 0, 2], &[10, 5, -42], &[-8, -2, -57]);
let g = GvarTable::parse(&raw).expect("parse");
let info = SimpleOutlineInfo {
points: vec![(0, 0), (1, 10), (4, 8)],
contour_ends: vec![2],
};
let out = g.glyph_deltas_iup(0, &info, &[1.0]).expect("iup");
assert_eq!(out[0], (15, -10));
assert_eq!(out[2], (-42, -57));
assert_eq!(out[1], (1, -57));
let legacy = g.glyph_deltas(0, 3, &[1.0]).expect("legacy");
assert_eq!(legacy[0], (15, -10));
assert_eq!(legacy[2], (-42, -57));
}
#[test]
fn glyph_deltas_iup_matches_legacy_when_all_points_referenced() {
let dxs = [10, 20, 30, 0, 0, 0, 0];
let dys = [-1, -2, -3, 0, 0, 0, 0];
let raw = build_one_tuple_glyph(&dxs, &dys);
let g = GvarTable::parse(&raw).expect("parse");
let info = SimpleOutlineInfo {
points: vec![(0, 0), (100, 0), (50, 100)],
contour_ends: vec![2],
};
let legacy = g.glyph_deltas(0, 3, &[0.5]).expect("legacy");
let iup = g.glyph_deltas_iup(0, &info, &[0.5]).expect("iup");
assert_eq!(legacy, iup);
}
#[test]
fn glyph_deltas_iup_scales_inferred_deltas() {
let raw = build_partial_points_glyph(&[0, 2], &[28, -42], &[-62, -57]);
let g = GvarTable::parse(&raw).expect("parse");
let info = SimpleOutlineInfo {
points: vec![(0, 0), (1, 10), (4, 8)],
contour_ends: vec![2],
};
let out = g.glyph_deltas_iup(0, &info, &[0.5]).expect("iup");
assert_eq!(
out[0],
(
(28.0 * 0.5f64).round() as i32,
(-62.0 * 0.5f64).round() as i32
)
);
assert_eq!(out[1].0, (10.5 * 0.5f64).round() as i32);
assert_eq!(out[1].1, (-57.0 * 0.5f64).round() as i32);
}
}