use super::{
super::{Point, PointFlags},
scale::Scale,
};
use crate::mem::take_slice;
use crate::model::Error;
use bytemuck::{AnyBitPattern, NoUninit};
pub struct Buffers<'a, S: Scale> {
pub points: &'a mut [Point<S::Coord>],
pub flags: &'a mut [PointFlags],
pub contours: &'a mut [u16],
pub unscaled: &'a mut [Point<i32>],
pub deltas: &'a mut [Point<S::Delta>],
pub iup: &'a mut [Point<S::Delta>],
pub composite_deltas: &'a mut [Point<S::Delta>],
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct BufferLengths {
pub points: u32,
pub contours: u32,
pub unscaled: u32,
pub deltas: u32,
pub iup: u32,
pub composite_deltas: u32,
}
impl BufferLengths {
pub fn packed_len<S: Scale>(&self) -> usize {
let coord = size_of::<Point<S::Coord>>();
let delta = size_of::<Point<S::Delta>>();
let size = self.points as usize * (coord + size_of::<PointFlags>())
+ self.contours as usize * size_of::<u16>()
+ self.unscaled as usize * size_of::<Point<i32>>()
+ (self.deltas + self.iup + self.composite_deltas) as usize * delta;
if size == 0 {
return 0;
}
size + align_of::<Point<S::Coord>>()
.max(align_of::<Point<S::Delta>>())
.max(align_of::<Point<i32>>())
}
}
impl<'a, S: Scale> Buffers<'a, S> {
pub fn from_bytes(bytes: &'a mut [u8], lengths: &BufferLengths) -> Result<Self, Error> {
let (points, bytes) = take(bytes, lengths.points)?;
let (unscaled, bytes) = take(bytes, lengths.unscaled)?;
let (deltas, bytes) = take(bytes, lengths.deltas)?;
let (iup, bytes) = take(bytes, lengths.iup)?;
let (composite_deltas, bytes) = take(bytes, lengths.composite_deltas)?;
let (contours, bytes) = take(bytes, lengths.contours)?;
let (flags, _) = take(bytes, lengths.points)?;
Ok(Self {
points,
flags,
contours,
unscaled,
deltas,
iup,
composite_deltas,
})
}
}
fn take<T>(bytes: &mut [u8], len: u32) -> Result<(&mut [T], &mut [u8]), Error>
where
T: AnyBitPattern + NoUninit,
{
take_slice(bytes, len as usize).ok_or(Error::InsufficientMemory)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
tables::glyf::outline::{
Outline, OutlinePlan, OutlineTables, Scale26Dot6, ScaleF32, Unscaled,
},
types::{F26Dot6, F2Dot14, GlyphId},
FontRef, TableProvider,
};
use alloc::vec;
#[test]
fn packed_buffers_load_the_same_outline() {
fn check<S: Scale>(scale: &S)
where
S::Coord: core::fmt::Debug,
{
let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
let tables = OutlineTables::new(&font).unwrap();
for gid in 0..font.maxp().unwrap().num_glyphs() {
let gid = GlyphId::from(gid);
let plan = OutlinePlan::new(&tables, gid).unwrap();
let lengths = plan.buffer_lengths::<S>();
let mut owned = Outline::<S>::new();
let expected = owned.load_with(&tables, gid, scale, None).unwrap();
let expected = (expected.points().to_vec(), expected.contours().to_vec());
let mut block = vec![0u8; lengths.packed_len::<S>()];
let buffers = Buffers::from_bytes(&mut block, &lengths).unwrap();
let actual = plan.load(&tables, scale, buffers, None).unwrap();
assert_eq!(actual.points(), expected.0, "gid {gid}");
assert_eq!(actual.contours(), expected.1, "gid {gid}");
}
}
check(&Scale26Dot6::new(Some(16.0), 1000));
check(&ScaleF32::new(Some(16.0), 1000));
check(&Unscaled);
}
#[test]
fn the_reported_size_is_what_it_takes() {
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let tables = OutlineTables::new(&font).unwrap();
let plan = OutlinePlan::new(&tables, GlyphId::new(5)).unwrap();
let lengths = plan.buffer_lengths::<Scale26Dot6>();
let size = lengths.packed_len::<Scale26Dot6>();
assert!(size > 0);
let mut block = vec![0u8; size];
assert!(Buffers::<Scale26Dot6>::from_bytes(&mut block, &lengths).is_ok());
let mut short = vec![0u8; size - core::mem::align_of::<Point<F26Dot6>>() - 1];
assert_eq!(
Buffers::<Scale26Dot6>::from_bytes(&mut short, &lengths).err(),
Some(Error::InsufficientMemory)
);
}
#[test]
fn an_unaligned_block_is_fine() {
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let tables = OutlineTables::new(&font).unwrap();
let plan = OutlinePlan::new(&tables, GlyphId::new(5)).unwrap();
let lengths = plan.buffer_lengths::<Scale26Dot6>();
let scale = Scale26Dot6::new(Some(16.0), 1000);
let aligned = {
let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>()];
let buffers = Buffers::from_bytes(&mut block, &lengths).unwrap();
plan.load(&tables, &scale, buffers, None)
.unwrap()
.points()
.to_vec()
};
for offset in 1..8 {
let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>() + offset];
let buffers = Buffers::from_bytes(&mut block[offset..], &lengths).unwrap();
let outline = plan.load(&tables, &scale, buffers, None).unwrap();
assert_eq!(outline.points(), aligned, "offset {offset}");
}
}
#[test]
fn every_buffer_is_sized_and_aligned() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let coords = [F2Dot14::from_f32(1.0)];
let tables = OutlineTables::new(&font).unwrap().at(&coords, &[]);
let plan = OutlinePlan::new(&tables, GlyphId::new(2)).unwrap();
assert!(plan.applies_variations);
let lengths = plan.buffer_lengths::<Scale26Dot6>();
let mut block = vec![0u8; lengths.packed_len::<Scale26Dot6>()];
let buffers = Buffers::<Scale26Dot6>::from_bytes(&mut block, &lengths).unwrap();
assert_eq!(buffers.points.len(), lengths.points as usize);
assert_eq!(buffers.flags.len(), lengths.points as usize);
assert_eq!(buffers.contours.len(), lengths.contours as usize);
assert_eq!(buffers.unscaled.len(), lengths.unscaled as usize);
assert_eq!(buffers.deltas.len(), lengths.deltas as usize);
assert_eq!(buffers.iup.len(), lengths.iup as usize);
assert_eq!(
buffers.composite_deltas.len(),
lengths.composite_deltas as usize
);
assert!(lengths.deltas > 0, "expected a varying glyph");
assert_eq!(buffers.points.as_ptr().align_offset(4), 0);
assert_eq!(buffers.contours.as_ptr().align_offset(2), 0);
}
#[test]
fn an_empty_outline_needs_no_bytes() {
let lengths = BufferLengths::default();
assert_eq!(lengths.packed_len::<Scale26Dot6>(), 0);
let buffers = Buffers::<Scale26Dot6>::from_bytes(&mut [], &lengths).unwrap();
assert!(buffers.points.is_empty());
assert!(buffers.flags.is_empty());
}
}