use super::{
scale::{OffsetRounding, Scale, SimplePass},
Buffers, GlyphZone, Hinter, OutlineContext, OutlinePlan, OutlineRef,
};
use crate::model::Error;
use crate::{
limits::MAX_RECURSION_DEPTH,
tables::{
glyf::{
Anchor, CompositeGlyph, CompositeGlyphFlags, Glyph, PointMarker, SimpleGlyph,
PHANTOM_POINT_COUNT,
},
gvar::DeltaBuffers,
},
types::{F2Dot14, Fixed, GlyphId, Point},
};
pub(super) fn load<'a, 's, 'buf, S: Scale>(
plan: &'s OutlinePlan<'a>,
context: &'a dyn OutlineContext<'a>,
scale: &'s S,
buffers: Buffers<'buf, S>,
hinter: Option<&mut dyn Hinter<S::Coord>>,
) -> Result<OutlineRef<'buf, S::Coord>, Error> {
let coords = context.coords();
let varies = plan.applies_variations;
let is_hinted = hinter.is_some() && scale.is_scaled();
let mut pass = Pass {
context,
scale,
varies,
coords,
gvar_scalars: context.gvar_scalars(),
has_hvar: context.has_hvar(),
buffers,
point_count: 0,
contour_count: 0,
component_delta_count: 0,
phantom: Default::default(),
hinter,
is_hinted,
};
pass.load(&plan.glyph_data, plan.glyph, 0)?;
Ok(pass.finish())
}
trait TrySlice<T> {
fn try_mut<I>(&mut self, index: I) -> Result<&mut [T], Error>
where
I: core::slice::SliceIndex<[T], Output = [T]>;
fn try_ref<I>(&self, index: I) -> Result<&[T], Error>
where
I: core::slice::SliceIndex<[T], Output = [T]>;
}
impl<T> TrySlice<T> for [T] {
fn try_mut<I>(&mut self, index: I) -> Result<&mut [T], Error>
where
I: core::slice::SliceIndex<[T], Output = [T]>,
{
self.get_mut(index).ok_or(Error::InsufficientMemory)
}
fn try_ref<I>(&self, index: I) -> Result<&[T], Error>
where
I: core::slice::SliceIndex<[T], Output = [T]>,
{
self.get(index).ok_or(Error::InsufficientMemory)
}
}
struct Pass<'a, 's, 'buf, 'hint, S: Scale> {
context: &'a dyn OutlineContext<'a>,
scale: &'s S,
varies: bool,
coords: &'s [F2Dot14],
gvar_scalars: &'s [Fixed],
has_hvar: bool,
buffers: Buffers<'buf, S>,
point_count: usize,
contour_count: usize,
component_delta_count: usize,
phantom: [Point<S::Coord>; PHANTOM_POINT_COUNT],
hinter: Option<&'hint mut dyn Hinter<S::Coord>>,
is_hinted: bool,
}
impl<'a, 's, 'buf, S: Scale> Pass<'a, 's, 'buf, '_, S> {
fn finish(self) -> OutlineRef<'buf, S::Coord> {
let Self {
buffers,
point_count,
contour_count,
phantom,
..
} = self;
let points = &mut buffers.points[..point_count];
let x_shift = phantom[0].x;
if x_shift != S::Coord::default() {
for point in points.iter_mut() {
point.x = point.x - x_shift;
}
}
OutlineRef {
points,
flags: &buffers.flags[..point_count],
contours: &buffers.contours[..contour_count],
phantom,
}
}
fn load(
&mut self,
glyph: &Option<Glyph<'a>>,
gid: GlyphId,
recurse_depth: usize,
) -> Result<(), Error> {
if recurse_depth > MAX_RECURSION_DEPTH {
return Err(Error::LimitExceeded);
}
self.setup_phantom_points(glyph, gid);
match glyph {
Some(Glyph::Simple(simple)) => self.load_simple(simple, gid),
Some(Glyph::Composite(composite)) => self.load_composite(composite, gid, recurse_depth),
None => self.load_empty(gid),
}
}
fn setup_phantom_points(&mut self, glyph: &Option<Glyph<'a>>, gid: GlyphId) {
let (x_min, y_max) = match glyph {
Some(glyph) => (glyph.x_min() as i32, glyph.y_max() as i32),
None => (0, 0),
};
let (lsb, advance) = self.context.h_metrics(gid);
let (tsb, vadvance) = self.context.v_metrics(gid).unwrap_or_else(|| {
let (ascent, descent) = self.context.h_line_metrics();
(ascent - y_max, ascent - descent)
});
let from = |value| self.scale.phantom_from_font_units(value);
self.phantom[0] = Point::new(from(x_min - lsb), from(0));
self.phantom[1] = Point::new(from(x_min - lsb + advance), from(0));
self.phantom[2] = Point::new(from(0), from(y_max + tsb));
self.phantom[3] = Point::new(from(0), from(y_max + tsb - vadvance));
}
fn load_empty(&mut self, gid: GlyphId) -> Result<(), Error> {
let deltas = if self.varies {
self.context.phantom_point_deltas(gid)
} else {
None
};
self.scale.load_empty_phantom(&mut self.phantom, deltas);
Ok(())
}
fn read_contours(&mut self, glyph: &SimpleGlyph<'a>) -> Result<core::ops::Range<usize>, Error> {
let end_pts = glyph.end_pts_of_contours();
let range = self.contour_count..self.contour_count + end_pts.len();
let contours = self.buffers.contours.try_mut(range.clone())?;
let mut previous = 0;
for (end_pt, contour) in end_pts.iter().zip(contours.iter_mut()) {
let end_pt = end_pt.get();
if end_pt < previous {
return Err(Error::Malformed);
}
previous = end_pt;
*contour = end_pt;
}
Ok(range)
}
fn load_simple(&mut self, glyph: &SimpleGlyph<'a>, gid: GlyphId) -> Result<(), Error> {
let points_start = self.point_count;
let point_count = glyph.num_points();
let with_phantom = point_count + PHANTOM_POINT_COUNT;
let point_range = points_start..points_start + with_phantom;
let contours_range = self.read_contours(glyph)?;
let var_data = if self.varies {
self.context.glyph_variation_data(gid)
} else {
None
};
let (is_hinted, phantom, coords, has_hvar, scale) = (
self.is_hinted,
self.phantom,
self.coords,
self.has_hvar,
self.scale,
);
let scaled = self.buffers.points.try_mut(point_range.clone())?;
let flags = self.buffers.flags.try_mut(point_range)?;
let unscaled = if S::NEEDS_UNSCALED {
self.buffers.unscaled.try_mut(..with_phantom)?
} else {
&mut []
};
let deltas = if self.varies {
DeltaBuffers {
deltas: self.buffers.deltas.try_mut(..with_phantom)?,
iup: self.buffers.iup.try_mut(..with_phantom)?,
}
} else {
DeltaBuffers {
deltas: &mut [],
iup: &mut [],
}
};
let contours = self.buffers.contours.try_ref(contours_range.clone())?;
self.phantom = scale.load_simple_points(SimplePass {
glyph,
scaled,
unscaled,
flags,
contours,
phantom,
deltas,
var_data,
varies: self.varies,
coords,
gvar_scalars: self.gvar_scalars,
has_hvar,
is_hinted,
})?;
self.point_count += point_count;
self.contour_count += contours_range.len();
if self.is_hinted {
self.hint_glyph(
glyph.instructions(),
points_start,
contours_range.clone(),
point_count,
gid,
false,
)?;
}
if points_start != 0 {
let contours = &mut self.buffers.contours[contours_range];
for contour_end in contours.iter_mut() {
*contour_end += points_start as u16;
}
}
Ok(())
}
fn hint_glyph(
&mut self,
bytecode: &'a [u8],
points_start: usize,
contours_range: core::ops::Range<usize>,
phantom_offset: usize,
gid: GlyphId,
is_composite: bool,
) -> Result<(), Error> {
if !self.is_hinted {
return Ok(());
}
let Some(hinter) = self.hinter.as_deref_mut() else {
return Ok(());
};
let point_range = points_start..self.point_count + PHANTOM_POINT_COUNT;
if bytecode.is_empty() {
if !is_composite && !hinter.backward_compatibility() {
let scaled = &self.buffers.points[point_range.clone()];
for (scaled, phantom) in scaled[phantom_offset..].iter().zip(&mut self.phantom) {
*phantom = self.scale.round_phantom(*scaled);
}
}
return Ok(());
}
let scaled = self.buffers.points.try_mut(point_range.clone())?;
let flags = self.buffers.flags.try_mut(point_range)?;
let unscaled = self.buffers.unscaled.try_mut(..scaled.len())?;
if is_composite {
for (i, phantom) in self.phantom.iter().enumerate() {
scaled[phantom_offset + i] = *phantom;
flags[phantom_offset + i] = Default::default();
}
for (unscaled, scaled) in unscaled.iter_mut().zip(scaled.iter()) {
*unscaled = Point::new(
self.scale.coord_to_hinting_raw(scaled.x),
self.scale.coord_to_hinting_raw(scaled.y),
);
}
for flag in flags.iter_mut() {
flag.clear_marker(PointMarker::TOUCHED);
}
}
for point in &mut scaled[phantom_offset..] {
*point = self.scale.round_phantom(*point);
}
let rebase = if is_composite { points_start as u16 } else { 0 };
if rebase != 0 {
for contour in self.buffers.contours.try_mut(contours_range.clone())? {
*contour -= rebase;
}
}
let result = {
let contours = self.buffers.contours.try_ref(contours_range.clone())?;
let mut zone = GlyphZone {
glyph: gid,
bytecode,
points: scaled,
unscaled,
flags,
contours,
phantom: &mut self.phantom,
is_composite,
coords: self.coords,
};
hinter.hint(&mut zone)
};
if rebase != 0 {
for contour in self.buffers.contours.try_mut(contours_range)? {
*contour += rebase;
}
}
Ok(result?)
}
fn load_composite(
&mut self,
glyph: &CompositeGlyph<'a>,
gid: GlyphId,
recurse_depth: usize,
) -> Result<(), Error> {
let point_base = self.point_count;
let contour_base = self.contour_count;
let delta_base = self.component_delta_count;
let mut have_deltas = false;
let mut counted_instructions = None;
let var_data = if self.varies {
self.context.glyph_variation_data(gid)
} else {
None
};
if let Some(var_data) = var_data {
let (count, instructions) = glyph.count_and_instructions();
counted_instructions = Some(instructions);
let count = count + PHANTOM_POINT_COUNT;
let deltas = self
.buffers
.composite_deltas
.try_mut(delta_base..delta_base + count)?;
if var_data
.composite_deltas_with_scalars(self.coords, self.gvar_scalars, deltas)
.is_some()
{
for (phantom, delta) in self
.phantom
.iter_mut()
.zip(&deltas[count - PHANTOM_POINT_COUNT..])
{
*phantom = self.scale.add_phantom_delta(*phantom, *delta);
}
have_deltas = true;
}
self.component_delta_count += count;
}
for point in self.phantom.iter_mut() {
*point = self.scale.scale_phantom(*point);
}
for (i, component) in glyph.components().enumerate() {
let phantom = self.phantom;
let start_point = self.point_count;
let component_glyph = self
.context
.glyph(component.glyph)
.ok_or(Error::MissingResource)?
.into_glyph();
self.load(&component_glyph, component.glyph, recurse_depth + 1)?;
let end_point = self.point_count;
if !component
.flags
.contains(CompositeGlyphFlags::USE_MY_METRICS)
{
self.phantom = phantom;
}
let have_xform = component.flags.intersects(
CompositeGlyphFlags::WE_HAVE_A_SCALE
| CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
| CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
);
if have_xform {
let points = &mut self.buffers.points[start_point..end_point];
self.scale.transform_points(points, &component.transform);
}
let offset = match component.anchor {
Anchor::Offset { x, y } => {
let scale_offset = have_xform
&& component.flags
& (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
| CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
== CompositeGlyphFlags::SCALED_COMPONENT_OFFSET;
let delta = have_deltas
.then(|| self.buffers.composite_deltas.get(delta_base + i).copied())
.flatten();
let rounding = if self.is_hinted
&& component
.flags
.contains(CompositeGlyphFlags::ROUND_XY_TO_GRID)
{
OffsetRounding {
x: self
.hinter
.as_deref()
.is_some_and(|hinter| hinter.round_composite_x_offset()),
y: true,
}
} else {
OffsetRounding::default()
};
self.scale.component_offset(
Point::new(x as i32, y as i32),
&component.transform,
scale_offset,
delta,
rounding,
)
}
Anchor::Point { base, component } => {
let base_point = *self
.buffers
.points
.get(point_base + base as usize)
.ok_or(Error::Malformed)?;
let component_point = *self
.buffers
.points
.get(start_point + component as usize)
.ok_or(Error::Malformed)?;
base_point - component_point
}
};
if offset.x != S::Coord::default() || offset.y != S::Coord::default() {
for point in &mut self.buffers.points[start_point..end_point] {
*point += offset;
}
}
}
self.component_delta_count = delta_base;
if self.is_hinted {
let instructions = counted_instructions.unwrap_or_else(|| glyph.instructions());
self.hint_glyph(
instructions.unwrap_or_default(),
point_base,
contour_base..self.contour_count,
self.point_count - point_base,
gid,
true,
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::super::testing::build;
use super::*;
use crate::{
tables::glyf::outline::{Outline, OutlineTables, Scale26Dot6, Unscaled},
tables::glyf::PointFlags,
tables::{glyf::Glyf, gvar::Gvar, loca::Loca},
types::{F26Dot6, Fixed},
FontData, FontRead, FontRef,
};
use alloc::{vec, vec::Vec};
#[test]
fn a_malformed_composite_does_not_leak_the_delta_stack() {
fn empty_simple() -> Vec<u8> {
let mut out = vec![];
out.extend_from_slice(&0i16.to_be_bytes()); out.extend_from_slice(&[0u8; 8]); out.extend_from_slice(&0u16.to_be_bytes()); out
}
fn composite(components: &[u16]) -> Vec<u8> {
let mut out = vec![];
out.extend_from_slice(&(-1i16).to_be_bytes()); out.extend_from_slice(&[0u8; 8]); for (i, gid) in components.iter().enumerate() {
let last = i == components.len() - 1;
let flags = 0x0001u16 | 0x0002 | if last { 0 } else { 0x0020 };
out.extend_from_slice(&flags.to_be_bytes());
out.extend_from_slice(&gid.to_be_bytes());
out.extend_from_slice(&0i16.to_be_bytes()); out.extend_from_slice(&0i16.to_be_bytes()); }
out
}
fn tables_from_glyphs(glyphs: &[Vec<u8>]) -> (Vec<u8>, Vec<u8>) {
let mut glyf = vec![];
let mut loca = vec![];
for glyph in glyphs {
loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
glyf.extend_from_slice(glyph);
}
loca.extend_from_slice(&(glyf.len() as u32).to_be_bytes());
(glyf, loca)
}
let (glyf, loca) = tables_from_glyphs(&[
composite(&[1, 2]),
composite(&[3]),
composite(&[3]),
empty_simple(),
]);
let glyf = Glyf::read(FontData::new(&glyf)).unwrap();
let loca = Loca::read(FontData::new(&loca), true).unwrap();
let gvar_bytes = malformed_gvar_for_composites();
let gvar = Gvar::read(FontData::new(&gvar_bytes)).unwrap();
for gid in 0..3 {
assert!(
gvar.glyph_variation_data(GlyphId::new(gid))
.unwrap()
.unwrap()
.composite_deltas(
&[F2Dot14::from_f32(1.0)],
&mut [Point::<Fixed>::default(); 8]
)
.is_none(),
"gid {gid} parsed when it should not"
);
}
let coords = [F2Dot14::from_f32(1.0)];
let context = OutlineTables {
glyf,
loca,
gvar: Some(crate::tables::gvar::SelectedGvar::Original(gvar)),
hmtx: None,
os2: None,
hvar: None,
units_per_em: 1000,
coords: &coords,
gvar_scalars: &[],
};
let mut outline = Outline::<Unscaled>::new();
assert!(outline.load(&context, GlyphId::new(0)).is_ok());
}
fn malformed_gvar_for_composites() -> Vec<u8> {
use font_test_data::bebuffer::BeBuffer;
let mut g = BeBuffer::new();
g = g.push(1u16); g = g.push(10u16); g = g.push(2u16); g = g.push(0xA000u16); g = g.push(0x4000u16); g = g.push(0u8); g = g.push(0x3Fu8); let blob = g.to_vec();
let len = blob.len() as u32;
let mut buf = BeBuffer::new();
buf = buf.push(1u16).push(0u16); buf = buf.push(1u16); buf = buf.push(0u16).push(20u32);
buf = buf.push(4u16); buf = buf.push(1u16); buf = buf.push(0u32); buf = buf
.push(0u32)
.push(len)
.push(len * 2)
.push(len * 3)
.push(len * 3);
let mut bytes = buf.to_vec();
let array_start = bytes.len() as u32;
bytes[16..20].copy_from_slice(&array_start.to_be_bytes());
for _ in 0..3 {
bytes.extend_from_slice(&blob);
}
bytes
}
#[test]
fn unscaled_matches_26dot6_without_a_size() {
for gid in [0, 1, 5, 7] {
let fixed = build(
font_test_data::GLYF_COMPONENTS,
gid,
&Scale26Dot6::new(None, 1000),
);
let raw = build(font_test_data::GLYF_COMPONENTS, gid, &Unscaled);
assert_eq!(raw.contours, fixed.contours, "gid {gid}");
assert_eq!(raw.points.len(), fixed.points.len(), "gid {gid}");
for (r, f) in raw.points.iter().zip(&fixed.points) {
assert_eq!(r.x, f.x.to_bits() >> 6, "gid {gid}");
assert_eq!(r.y, f.y.to_bits() >> 6, "gid {gid}");
}
}
}
#[test]
fn composite_inherits_its_components() {
let scale = Scale26Dot6::new(None, 1000);
let simple = build(font_test_data::GLYF_COMPONENTS, 1, &scale).points;
let built = build(font_test_data::GLYF_COMPONENTS, 5, &scale);
let composite = built.points;
assert_eq!(composite.len(), simple.len() * 2);
assert_eq!(built.contours.len(), 2);
assert_ne!(composite[0], composite[simple.len()]);
}
#[test]
fn empty_glyph_has_metrics_but_no_points() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let context = OutlineTables::new(&font).unwrap();
let mut outline = Outline::<Scale26Dot6>::new();
let out = outline.load(&context, GlyphId::new(0), Some(16.0)).unwrap();
assert!(out.points.is_empty());
assert!(out.contours.is_empty());
assert!(out.adjusted_advance_width() != F26Dot6::ZERO);
}
#[test]
fn short_buffers_are_rejected() {
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let context = OutlineTables::new(&font).unwrap();
let plan = OutlinePlan::new(&context, GlyphId::new(1)).unwrap();
let scale = Scale26Dot6::new(None, 1000);
let lengths = plan.buffer_lengths::<Scale26Dot6>();
let mut points = vec![Point::default(); 1];
let mut flags = vec![PointFlags::default(); lengths.points as usize];
let mut contours = vec![0u16; lengths.contours as usize];
let mut unscaled = vec![Point::default(); lengths.unscaled as usize];
let buffers = Buffers {
points: &mut points,
flags: &mut flags,
contours: &mut contours,
unscaled: &mut unscaled,
deltas: &mut [],
iup: &mut [],
composite_deltas: &mut [],
};
assert_eq!(
plan.load(&context, &scale, buffers, None).err(),
Some(Error::InsufficientMemory)
);
}
}