use core::ops::RangeInclusive;
use raw::tables::glyf::PointCoord;
use read_fonts::{
tables::glyf::{PointFlags, PointMarker},
tables::gvar::{GlyphDelta, Gvar},
tables::variations::TupleVariation,
types::{F2Dot14, Fixed, GlyphId, Point},
ReadError,
};
use super::PHANTOM_POINT_COUNT;
pub fn composite_glyph<D: PointCoord>(
gvar: &Gvar,
glyph_id: GlyphId,
coords: &[F2Dot14],
deltas: &mut [Point<D>],
) -> Result<(), ReadError> {
compute_deltas_for_glyph(gvar, glyph_id, coords, deltas, |scalar, tuple, deltas| {
for tuple_delta in tuple.deltas() {
let ix = tuple_delta.position as usize;
if let Some(delta) = deltas.get_mut(ix) {
*delta += tuple_delta.apply_scalar(scalar);
}
}
Ok(())
})?;
Ok(())
}
pub struct SimpleGlyph<'a, C: PointCoord> {
pub points: &'a [Point<C>],
pub flags: &'a mut [PointFlags],
pub contours: &'a [u16],
}
pub fn simple_glyph<C, D>(
gvar: &Gvar,
glyph_id: GlyphId,
coords: &[F2Dot14],
has_var_lsb: bool,
glyph: SimpleGlyph<C>,
iup_buffer: &mut [Point<D>],
deltas: &mut [Point<D>],
) -> Result<(), ReadError>
where
C: PointCoord,
D: PointCoord,
D: From<C>,
{
if iup_buffer.len() < glyph.points.len() || glyph.points.len() < PHANTOM_POINT_COUNT {
return Err(ReadError::InvalidArrayLen);
}
for delta in deltas.iter_mut() {
*delta = Default::default();
}
if gvar.glyph_variation_data(glyph_id).is_err() {
return Ok(());
};
let SimpleGlyph {
points,
flags,
contours,
} = glyph;
let actual_len = if has_var_lsb {
points.len() - 4
} else {
points.len() - 3
};
let deltas = &mut deltas[..actual_len];
compute_deltas_for_glyph(gvar, glyph_id, coords, deltas, |scalar, tuple, deltas| {
for ((flag, point), iup_point) in flags.iter_mut().zip(points).zip(&mut iup_buffer[..]) {
*iup_point = point.map(D::from);
flag.clear_marker(PointMarker::HAS_DELTA);
}
for tuple_delta in tuple.deltas() {
let ix = tuple_delta.position as usize;
if let (Some(flag), Some(point)) = (flags.get_mut(ix), iup_buffer.get_mut(ix)) {
flag.set_marker(PointMarker::HAS_DELTA);
*point += tuple_delta.apply_scalar(scalar);
}
}
interpolate_deltas(points, flags, contours, &mut iup_buffer[..])
.ok_or(ReadError::OutOfBounds)?;
for ((delta, point), iup_point) in deltas.iter_mut().zip(points).zip(iup_buffer.iter()) {
*delta += *iup_point - point.map(D::from);
}
Ok(())
})?;
Ok(())
}
fn compute_deltas_for_glyph<C, D>(
gvar: &Gvar,
glyph_id: GlyphId,
coords: &[F2Dot14],
deltas: &mut [Point<D>],
mut apply_tuple_missing_deltas_fn: impl FnMut(
Fixed,
TupleVariation<GlyphDelta>,
&mut [Point<D>],
) -> Result<(), ReadError>,
) -> Result<(), ReadError>
where
C: PointCoord,
D: PointCoord,
D: From<C>,
{
for delta in deltas.iter_mut() {
*delta = Default::default();
}
let Ok(var_data) = gvar.glyph_variation_data(glyph_id) else {
return Ok(());
};
for (tuple, scalar) in var_data.active_tuples_at(coords) {
if tuple.has_deltas_for_all_points() {
for (delta, tuple_delta) in deltas.iter_mut().zip(tuple.deltas()) {
*delta += tuple_delta.apply_scalar(scalar);
}
} else {
apply_tuple_missing_deltas_fn(scalar, tuple, deltas)?;
}
}
Ok(())
}
fn interpolate_deltas<C, D>(
points: &[Point<C>],
flags: &[PointFlags],
contours: &[u16],
out_points: &mut [Point<D>],
) -> Option<()>
where
C: PointCoord,
D: PointCoord,
D: From<C>,
{
let mut jiggler = Jiggler { points, out_points };
let mut point_ix = 0usize;
for &end_point_ix in contours {
let end_point_ix = end_point_ix as usize;
let first_point_ix = point_ix;
while point_ix <= end_point_ix && !flags.get(point_ix)?.has_marker(PointMarker::HAS_DELTA) {
point_ix += 1;
}
if point_ix > end_point_ix {
continue;
}
let first_delta_ix = point_ix;
let mut cur_delta_ix = point_ix;
point_ix += 1;
while point_ix <= end_point_ix {
if flags.get(point_ix)?.has_marker(PointMarker::HAS_DELTA) {
jiggler.interpolate(
cur_delta_ix + 1..=point_ix - 1,
RefPoints(cur_delta_ix, point_ix),
)?;
cur_delta_ix = point_ix;
}
point_ix += 1;
}
if cur_delta_ix == first_delta_ix {
jiggler.shift(first_point_ix..=end_point_ix, cur_delta_ix)?;
} else {
jiggler.interpolate(
cur_delta_ix + 1..=end_point_ix,
RefPoints(cur_delta_ix, first_delta_ix),
)?;
if first_delta_ix > 0 {
jiggler.interpolate(
first_point_ix..=first_delta_ix - 1,
RefPoints(cur_delta_ix, first_delta_ix),
)?;
}
}
}
Some(())
}
struct RefPoints(usize, usize);
struct Jiggler<'a, C, D>
where
C: PointCoord,
D: PointCoord,
D: From<C>,
{
points: &'a [Point<C>],
out_points: &'a mut [Point<D>],
}
impl<'a, C, D> Jiggler<'a, C, D>
where
C: PointCoord,
D: PointCoord,
D: From<C>,
{
fn shift(&mut self, range: RangeInclusive<usize>, ref_ix: usize) -> Option<()> {
let ref_in = self.points.get(ref_ix)?.map(D::from);
let ref_out = self.out_points.get(ref_ix)?;
let delta = *ref_out - ref_in;
if delta.x == D::zeroed() && delta.y == D::zeroed() {
return Some(());
}
for out_point in self.out_points.get_mut(*range.start()..ref_ix)? {
*out_point += delta;
}
for out_point in self.out_points.get_mut(ref_ix + 1..=*range.end())? {
*out_point += delta;
}
Some(())
}
fn interpolate(&mut self, range: RangeInclusive<usize>, ref_points: RefPoints) -> Option<()> {
if range.is_empty() {
return Some(());
}
macro_rules! interp_coord {
($coord:ident) => {
let RefPoints(mut ref1_ix, mut ref2_ix) = ref_points;
if self.points.get(ref1_ix)?.$coord > self.points.get(ref2_ix)?.$coord {
core::mem::swap(&mut ref1_ix, &mut ref2_ix);
}
let in1 = D::from(self.points.get(ref1_ix)?.$coord);
let in2 = D::from(self.points.get(ref2_ix)?.$coord);
let out1 = self.out_points.get(ref1_ix)?.$coord;
let out2 = self.out_points.get(ref2_ix)?.$coord;
if in1 != in2 || out1 == out2 {
let scale = if in1 != in2 {
(out2 - out1) / (in2 - in1)
} else {
D::zeroed()
};
let d1 = out1 - in1;
let d2 = out2 - in2;
for (point, out_point) in self
.points
.get(range.clone())?
.iter()
.zip(self.out_points.get_mut(range.clone())?)
{
let mut out = D::from(point.$coord);
if out <= in1 {
out += d1;
} else if out >= in2 {
out += d2;
} else {
out = out1 + (out - in1) * scale;
}
out_point.$coord = out;
}
}
};
}
interp_coord!(x);
interp_coord!(y);
Some(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_points(tuples: &[(i32, i32)]) -> Vec<Point<i32>> {
tuples.iter().map(|&(x, y)| Point::new(x, y)).collect()
}
fn make_working_points_and_flags(
points: &[Point<i32>],
deltas: &[Point<i32>],
) -> (Vec<Point<Fixed>>, Vec<PointFlags>) {
let working_points = points
.iter()
.zip(deltas)
.map(|(point, delta)| point.map(Fixed::from_i32) + delta.map(Fixed::from_i32))
.collect();
let flags = deltas
.iter()
.map(|delta| {
let mut flags = PointFlags::default();
if delta.x != 0 || delta.y != 0 {
flags.set_marker(PointMarker::HAS_DELTA);
}
flags
})
.collect();
(working_points, flags)
}
#[test]
fn shift() {
let points = make_points(&[(245, 630), (260, 700), (305, 680)]);
let deltas = make_points(&[(20, -10), (0, 0), (0, 0)]);
let (mut working_points, flags) = make_working_points_and_flags(&points, &deltas);
interpolate_deltas(&points, &flags, &[2], &mut working_points).unwrap();
let expected = &[
Point::new(265, 620).map(Fixed::from_i32),
Point::new(280, 690).map(Fixed::from_i32),
Point::new(325, 670).map(Fixed::from_i32),
];
assert_eq!(&working_points, expected);
}
#[test]
fn interpolate() {
let points = make_points(&[(245, 630), (260, 700), (305, 680)]);
let deltas = make_points(&[(28, -62), (0, 0), (-42, -57)]);
let (mut working_points, flags) = make_working_points_and_flags(&points, &deltas);
interpolate_deltas(&points, &flags, &[2], &mut working_points).unwrap();
assert_eq!(
working_points[1],
Point::new(
Fixed::from_f64(260.0 + 10.4999237060547),
Fixed::from_f64(700.0 - 57.0)
)
);
}
}