use crate::model::Error;
use bytemuck::NoUninit;
use crate::{
tables::{
glyf::{PointCoord, PointFlags, SimpleGlyph, Transform, PHANTOM_POINT_COUNT},
gvar::{DeltaBuffers, GlyphVariationData},
},
types::{F26Dot6, F2Dot14, Fixed, Point},
ReadError,
};
pub trait Scale: Sized {
type Coord: PointCoord + NoUninit;
type Delta: PointCoord + NoUninit;
const NEEDS_UNSCALED: bool;
fn is_scaled(&self) -> bool;
fn phantom_from_font_units(&self, value: i32) -> Self::Coord;
fn coord_to_hinting_raw(&self, value: Self::Coord) -> i32;
fn scale_phantom(&self, point: Point<Self::Coord>) -> Point<Self::Coord>;
fn add_phantom_delta(
&self,
point: Point<Self::Coord>,
delta: Point<Self::Delta>,
) -> Point<Self::Coord>;
fn round_phantom(&self, point: Point<Self::Coord>) -> Point<Self::Coord>;
fn load_empty_phantom(
&self,
phantom: &mut [Point<Self::Coord>; PHANTOM_POINT_COUNT],
deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
);
fn load_simple_points(
&self,
pass: SimplePass<'_, '_, Self>,
) -> Result<[Point<Self::Coord>; PHANTOM_POINT_COUNT], Error>;
fn transform_points(&self, points: &mut [Point<Self::Coord>], transform: &Transform);
fn component_offset(
&self,
offset: Point<i32>,
transform: &Transform,
scale_offset: bool,
delta: Option<Point<Self::Delta>>,
rounding: OffsetRounding,
) -> Point<Self::Coord>;
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct OffsetRounding {
pub x: bool,
pub y: bool,
}
pub struct SimplePass<'a, 'v, S: Scale> {
pub glyph: &'a SimpleGlyph<'a>,
pub scaled: &'a mut [Point<S::Coord>],
pub unscaled: &'a mut [Point<i32>],
pub flags: &'a mut [PointFlags],
pub contours: &'a [u16],
pub phantom: [Point<S::Coord>; PHANTOM_POINT_COUNT],
pub deltas: DeltaBuffers<'a, S::Delta>,
pub var_data: Option<GlyphVariationData<'v>>,
pub varies: bool,
pub coords: &'a [F2Dot14],
pub gvar_scalars: &'a [Fixed],
pub has_hvar: bool,
pub is_hinted: bool,
}
#[derive(Copy, Clone, Debug)]
pub struct Scale26Dot6 {
scale: Fixed,
is_scaled: bool,
}
impl Scale26Dot6 {
pub fn new(ppem: Option<f32>, units_per_em: u16) -> Self {
if let Some(ppem) = ppem {
if units_per_em > 0 {
return Self {
scale: Fixed::from_bits((ppem * 64.) as i32)
/ Fixed::from_bits(units_per_em as i32),
is_scaled: true,
};
}
}
Self {
scale: Fixed::from_bits(0x10000),
is_scaled: false,
}
}
pub fn to_bits(self) -> i32 {
self.scale.to_bits()
}
fn mul(&self, value: i32) -> F26Dot6 {
F26Dot6::from_bits((Fixed::from_bits(value) * self.scale).to_bits())
}
fn mul_point(&self, value: Point<i32>) -> Point<F26Dot6> {
Point::new(self.mul(value.x), self.mul(value.y))
}
fn mul_point_with_delta(
&self,
font_units: Point<i32>,
delta: Point<F26Dot6>,
) -> Point<F26Dot6> {
let scaled =
self.mul_point((font_units.map(F26Dot6::from_i32) + delta).map(|v| v.to_bits()));
scaled.map(|v| F26Dot6::from_bits(v.to_i32()))
}
}
impl Scale for Scale26Dot6 {
type Coord = F26Dot6;
type Delta = Fixed;
const NEEDS_UNSCALED: bool = true;
fn is_scaled(&self) -> bool {
self.is_scaled
}
fn phantom_from_font_units(&self, value: i32) -> F26Dot6 {
F26Dot6::from_bits(value)
}
fn coord_to_hinting_raw(&self, value: F26Dot6) -> i32 {
value.to_bits()
}
fn scale_phantom(&self, point: Point<F26Dot6>) -> Point<F26Dot6> {
if self.is_scaled {
self.mul_point(point.map(|v| v.to_bits()))
} else {
point.map(|v| F26Dot6::from_i32(v.to_bits()))
}
}
fn add_phantom_delta(&self, point: Point<F26Dot6>, delta: Point<Fixed>) -> Point<F26Dot6> {
point + delta.map(Fixed::to_i32).map(F26Dot6::from_bits)
}
fn round_phantom(&self, point: Point<F26Dot6>) -> Point<F26Dot6> {
point.map(|v| v.round())
}
fn load_empty_phantom(
&self,
phantom: &mut [Point<F26Dot6>; PHANTOM_POINT_COUNT],
deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
) {
let mut font_units = phantom.map(|point| point.map(|v| v.to_bits()));
if let Some(deltas) = deltas {
font_units[0] += deltas[0].map(Fixed::to_i32);
font_units[1] += deltas[1].map(Fixed::to_i32);
}
for (out, font_units) in phantom.iter_mut().zip(&font_units) {
*out = if self.is_scaled {
self.mul_point(*font_units)
} else {
font_units.map(F26Dot6::from_i32)
};
}
}
fn load_simple_points(
&self,
pass: SimplePass<'_, '_, Self>,
) -> Result<[Point<F26Dot6>; PHANTOM_POINT_COUNT], Error> {
let SimplePass {
glyph,
scaled,
unscaled,
flags,
contours,
phantom,
mut deltas,
var_data,
varies,
coords,
gvar_scalars,
has_hvar,
is_hinted,
} = pass;
let phantom_start = glyph.num_points();
let raw_phantom = phantom.map(|point| point.map(|v| v.to_bits()));
read_points_and_phantom(glyph, unscaled, flags, &raw_phantom)?;
let have_deltas = varies
&& simple_deltas(
var_data,
(coords, gvar_scalars),
unscaled,
flags,
contours,
&mut deltas,
);
if self.is_scaled {
if have_deltas {
for ((point, unscaled), delta) in scaled
.iter_mut()
.zip(unscaled.iter())
.zip(deltas.deltas.iter())
{
*point = self.mul_point_with_delta(*unscaled, delta.map(Fixed::to_f26dot6));
}
if has_hvar {
for ((point, unscaled), delta) in scaled[phantom_start..]
.iter_mut()
.zip(&unscaled[phantom_start..])
.zip(&deltas.deltas[phantom_start..])
{
let delta = delta.map(Fixed::to_i32).map(F26Dot6::from_i32);
*point = self.mul_point_with_delta(*unscaled, delta);
}
}
if is_hinted {
fold_deltas_into_font_units(unscaled, deltas.deltas);
}
} else {
for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
*point = self.mul_point(*unscaled);
}
}
} else {
if have_deltas {
fold_deltas_into_font_units(unscaled, deltas.deltas);
}
for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
*point = unscaled.map(F26Dot6::from_i32);
}
}
let mut out = [Point::default(); PHANTOM_POINT_COUNT];
out.copy_from_slice(&scaled[phantom_start..]);
Ok(out)
}
fn transform_points(&self, points: &mut [Point<F26Dot6>], transform: &Transform) {
let t = FixedTransform::new(transform);
if self.is_scaled {
for point in points.iter_mut() {
let p = point.map(|c| Fixed::from_bits(c.to_bits()));
*point = Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy)
.map(|c| F26Dot6::from_bits(c.to_bits()));
}
} else {
for point in points.iter_mut() {
let p = point.map(|c| Fixed::from_bits(c.to_i32()));
*point = Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy)
.map(|c| F26Dot6::from_i32(c.to_bits()));
}
}
}
fn component_offset(
&self,
offset: Point<i32>,
transform: &Transform,
scale_offset: bool,
delta: Option<Point<Fixed>>,
rounding: OffsetRounding,
) -> Point<F26Dot6> {
let (mut x, mut y) = (offset.x, offset.y);
if scale_offset {
let t = FixedTransform::new(transform);
x = (Fixed::from_bits(x) * hypot_fixed(t.xx, t.xy)).to_bits();
y = (Fixed::from_bits(y) * hypot_fixed(t.yy, t.yx)).to_bits();
}
if let Some(delta) = delta {
x += delta.x.to_i32();
y += delta.y.to_i32();
}
if self.is_scaled {
let mut offset = self.mul_point(Point::new(x, y));
if rounding.x {
offset.x = offset.x.round();
}
if rounding.y {
offset.y = offset.y.round();
}
offset
} else {
Point::new(x, y).map(F26Dot6::from_i32)
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ScaleF32 {
scale: f32,
}
impl ScaleF32 {
pub fn new(ppem: Option<f32>, units_per_em: u16) -> Self {
let scale = if units_per_em == 0 {
1.0
} else {
ppem.map(|ppem| ppem / units_per_em as f32).unwrap_or(1.0)
};
Self { scale }
}
}
impl Scale for ScaleF32 {
type Coord = f32;
type Delta = f32;
const NEEDS_UNSCALED: bool = false;
fn is_scaled(&self) -> bool {
self.scale != 1.0
}
fn phantom_from_font_units(&self, value: i32) -> f32 {
value as f32
}
fn coord_to_hinting_raw(&self, value: f32) -> i32 {
value as i32
}
fn scale_phantom(&self, point: Point<f32>) -> Point<f32> {
if self.scale != 1.0 {
point * self.scale
} else {
point
}
}
fn add_phantom_delta(&self, point: Point<f32>, delta: Point<f32>) -> Point<f32> {
point + delta
}
fn round_phantom(&self, point: Point<f32>) -> Point<f32> {
point
}
fn load_empty_phantom(
&self,
phantom: &mut [Point<f32>; PHANTOM_POINT_COUNT],
deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
) {
let mut font_units = *phantom;
if let Some(deltas) = deltas {
font_units[0] += deltas[0].map(Fixed::to_f32);
font_units[1] += deltas[1].map(Fixed::to_f32);
}
for (out, font_units) in phantom.iter_mut().zip(&font_units) {
*out = *font_units * self.scale;
}
}
fn load_simple_points(
&self,
pass: SimplePass<'_, '_, Self>,
) -> Result<[Point<f32>; PHANTOM_POINT_COUNT], Error> {
let SimplePass {
glyph,
scaled,
flags,
contours,
phantom,
mut deltas,
var_data,
varies,
coords,
gvar_scalars,
..
} = pass;
let phantom_start = glyph.num_points();
read_points_and_phantom(glyph, scaled, flags, &phantom)?;
if varies
&& simple_deltas(
var_data,
(coords, gvar_scalars),
scaled,
flags,
contours,
&mut deltas,
)
{
for (point, delta) in scaled.iter_mut().zip(deltas.deltas.iter()) {
*point += *delta;
}
}
if self.scale != 1.0 {
for point in scaled.iter_mut() {
*point *= self.scale;
}
}
let mut out = [Point::default(); PHANTOM_POINT_COUNT];
out.copy_from_slice(&scaled[phantom_start..]);
Ok(out)
}
fn transform_points(&self, points: &mut [Point<f32>], transform: &Transform) {
let (xx, yx, xy, yy) = (
transform.xx.to_f32(),
transform.yx.to_f32(),
transform.xy.to_f32(),
transform.yy.to_f32(),
);
for point in points.iter_mut() {
*point = Point::new(point.x * xx + point.y * xy, point.x * yx + point.y * yy);
}
}
fn component_offset(
&self,
offset: Point<i32>,
transform: &Transform,
scale_offset: bool,
delta: Option<Point<f32>>,
_rounding: OffsetRounding,
) -> Point<f32> {
let (mut x, mut y) = (offset.x as f32, offset.y as f32);
if scale_offset {
fn hypot(a: f32, b: f32) -> f32 {
#[cfg(not(feature = "libm"))]
{
a.hypot(b)
}
#[cfg(feature = "libm")]
{
#[allow(unused_imports)]
use core_maths::CoreFloat;
a.hypot(b)
}
}
x *= hypot(transform.xx.to_f32(), transform.xy.to_f32());
y *= hypot(transform.yx.to_f32(), transform.yy.to_f32());
}
let mut offset = Point::new(x, y);
if let Some(delta) = delta {
offset += delta;
}
offset * self.scale
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct Unscaled;
impl Scale for Unscaled {
type Coord = i32;
type Delta = Fixed;
const NEEDS_UNSCALED: bool = false;
fn is_scaled(&self) -> bool {
false
}
fn phantom_from_font_units(&self, value: i32) -> i32 {
value
}
fn coord_to_hinting_raw(&self, value: i32) -> i32 {
value
}
fn scale_phantom(&self, point: Point<i32>) -> Point<i32> {
point
}
fn add_phantom_delta(&self, point: Point<i32>, delta: Point<Fixed>) -> Point<i32> {
point + delta.map(Fixed::to_i32)
}
fn round_phantom(&self, point: Point<i32>) -> Point<i32> {
point
}
fn load_empty_phantom(
&self,
phantom: &mut [Point<i32>; PHANTOM_POINT_COUNT],
deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
) {
if let Some(deltas) = deltas {
phantom[0] += deltas[0].map(Fixed::to_i32);
phantom[1] += deltas[1].map(Fixed::to_i32);
}
}
fn load_simple_points(
&self,
pass: SimplePass<'_, '_, Self>,
) -> Result<[Point<i32>; PHANTOM_POINT_COUNT], Error> {
let SimplePass {
glyph,
scaled,
flags,
contours,
phantom,
mut deltas,
var_data,
varies,
coords,
gvar_scalars,
..
} = pass;
let phantom_start = glyph.num_points();
read_points_and_phantom(glyph, scaled, flags, &phantom)?;
let have_deltas = varies
&& simple_deltas(
var_data,
(coords, gvar_scalars),
scaled,
flags,
contours,
&mut deltas,
);
if have_deltas {
fold_deltas_into_font_units(scaled, deltas.deltas);
}
let mut out = [Point::default(); PHANTOM_POINT_COUNT];
out.copy_from_slice(&scaled[phantom_start..]);
Ok(out)
}
fn transform_points(&self, points: &mut [Point<i32>], transform: &Transform) {
let t = FixedTransform::new(transform);
for point in points.iter_mut() {
let p = point.map(Fixed::from_bits);
*point =
Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy).map(|c| c.to_bits());
}
}
fn component_offset(
&self,
offset: Point<i32>,
transform: &Transform,
scale_offset: bool,
delta: Option<Point<Fixed>>,
_rounding: OffsetRounding,
) -> Point<i32> {
let mut offset = offset;
if scale_offset {
let t = FixedTransform::new(transform);
offset = Point::new(
(Fixed::from_bits(offset.x) * hypot_fixed(t.xx, t.xy)).to_bits(),
(Fixed::from_bits(offset.y) * hypot_fixed(t.yy, t.yx)).to_bits(),
);
}
if let Some(delta) = delta {
offset += delta.map(Fixed::to_i32);
}
offset
}
}
fn read_points_and_phantom<C: PointCoord>(
glyph: &SimpleGlyph,
dest: &mut [Point<C>],
flags: &mut [PointFlags],
phantom: &[Point<C>; PHANTOM_POINT_COUNT],
) -> Result<(), ReadError> {
let point_count = glyph.num_points();
glyph.read_points_fast(&mut dest[..point_count], &mut flags[..point_count])?;
for (i, phantom) in phantom.iter().enumerate() {
dest[point_count + i] = *phantom;
flags[point_count + i] = Default::default();
}
Ok(())
}
fn fold_deltas_into_font_units(font_units: &mut [Point<i32>], deltas: &[Point<Fixed>]) {
for (point, delta) in font_units.iter_mut().zip(deltas) {
*point += delta.map(Fixed::to_i32);
}
}
struct FixedTransform {
xx: Fixed,
yx: Fixed,
xy: Fixed,
yy: Fixed,
}
impl FixedTransform {
fn new(transform: &Transform) -> Self {
fn widen(x: F2Dot14) -> Fixed {
Fixed::from_bits(x.to_bits() as i32 * 4)
}
Self {
xx: widen(transform.xx),
yx: widen(transform.yx),
xy: widen(transform.xy),
yy: widen(transform.yy),
}
}
}
fn hypot_fixed(a: Fixed, b: Fixed) -> Fixed {
let a = a.to_bits().abs();
let b = b.to_bits().abs();
Fixed::from_bits(if a > b {
a + ((3 * b) >> 3)
} else {
b + ((3 * a) >> 3)
})
}
fn simple_deltas<C, D>(
var_data: Option<GlyphVariationData<'_>>,
(coords, gvar_scalars): (&[F2Dot14], &[Fixed]),
points: &[Point<C>],
flags: &mut [PointFlags],
contours: &[u16],
buffers: &mut DeltaBuffers<'_, D>,
) -> bool
where
C: PointCoord,
D: PointCoord + From<C>,
{
match var_data {
Some(var_data) => var_data
.simple_deltas_with_scalars(coords, gvar_scalars, points, flags, contours, buffers)
.is_some(),
None => {
for delta in buffers.deltas.iter_mut() {
*delta = Default::default();
}
true
}
}
}
#[cfg(test)]
mod tests {
use super::super::testing::build;
use super::super::{OutlineContext, OutlinePlan, OutlineTables};
use super::*;
use crate::tables::loca::LocaGlyph;
use crate::{tables::glyf::Glyph, types::GlyphId, FontRef};
use alloc::{vec, vec::Vec};
#[test]
fn unscaled_simple_glyph_is_font_units() {
let scale = Scale26Dot6::new(None, 1000);
let built = build(font_test_data::GLYF_COMPONENTS, 1, &scale);
let (points, contours) = (built.points, built.contours);
assert_eq!(contours, vec![3]);
assert_eq!(points.len(), 4);
for point in &points {
assert_eq!(point.x.to_bits() & 0x3F, 0, "expected whole font units");
}
}
fn is_composite(gid: u32) -> bool {
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let context = OutlineTables::new(&font).unwrap();
matches!(
context.glyph(GlyphId::new(gid)).unwrap().into_glyph(),
Some(Glyph::Composite(_))
)
}
#[test]
fn modes_agree_without_a_size() {
for gid in [0, 1, 5, 7] {
let slack = i32::from(is_composite(gid));
let fixed = build(
font_test_data::GLYF_COMPONENTS,
gid,
&Scale26Dot6::new(None, 1000),
);
let float = build(
font_test_data::GLYF_COMPONENTS,
gid,
&ScaleF32::new(None, 1000),
);
assert_eq!(fixed.contours, float.contours, "gid {gid}");
assert_eq!(fixed.points.len(), float.points.len(), "gid {gid}");
for (a, b) in fixed.points.iter().zip(&float.points) {
assert!(
((a.x.to_bits() >> 6) - b.x as i32).abs() <= slack,
"gid {gid}"
);
assert!(
((a.y.to_bits() >> 6) - b.y as i32).abs() <= slack,
"gid {gid}"
);
}
}
}
#[test]
fn modes_agree_on_phantom_points() {
for gid in [0, 1, 5, 7] {
let fixed = build(
font_test_data::GLYF_COMPONENTS,
gid,
&Scale26Dot6::new(None, 1000),
);
let float = build(
font_test_data::GLYF_COMPONENTS,
gid,
&ScaleF32::new(None, 1000),
);
for (a, b) in fixed.phantom.iter().zip(&float.phantom) {
assert_eq!(a.x.to_bits() >> 6, b.x as i32, "gid {gid}");
assert_eq!(a.y.to_bits() >> 6, b.y as i32, "gid {gid}");
}
}
}
#[test]
fn component_offset_stays_far_inside_i32() {
const BOUND: i32 = 1 << 18;
let extremes = [i16::MIN, i16::MAX];
let mut worst = 0i32;
for x in extremes {
for y in extremes {
for basis in extremes {
let transform = Transform {
xx: F2Dot14::from_bits(basis),
yx: F2Dot14::from_bits(basis),
xy: F2Dot14::from_bits(basis),
yy: F2Dot14::from_bits(basis),
};
let offset = Point::new(x as i32, y as i32);
let delta = Point::new(Fixed::from_bits(i32::MAX), Fixed::from_bits(i32::MIN));
for scale_offset in [false, true] {
let raw = Unscaled.component_offset(
offset,
&transform,
scale_offset,
Some(delta),
OffsetRounding::default(),
);
worst = worst.max(raw.x.abs()).max(raw.y.abs());
let fixed = Scale26Dot6::new(Some(2048.0), 16).component_offset(
offset,
&transform,
scale_offset,
Some(delta),
OffsetRounding { x: true, y: true },
);
assert!(fixed.x.to_bits().abs() < i32::MAX / 2);
assert!(fixed.y.to_bits().abs() < i32::MAX / 2);
let float = ScaleF32::new(Some(2048.0), 16).component_offset(
offset,
&transform,
scale_offset,
Some(Point::new(f32::from(i16::MAX), f32::from(i16::MIN))),
OffsetRounding::default(),
);
assert!(float.x.is_finite() && float.y.is_finite());
}
}
}
}
assert!(worst < BOUND, "worst offset was {worst}");
}
#[test]
fn a_scale_knows_whether_it_has_a_size() {
assert!(!Scale26Dot6::new(None, 1000).is_scaled());
assert!(Scale26Dot6::new(Some(16.0), 1000).is_scaled());
assert!(!ScaleF32::new(None, 1000).is_scaled());
assert!(ScaleF32::new(Some(16.0), 1000).is_scaled());
assert!(!Unscaled.is_scaled());
}
#[test]
fn unscaled_output_is_the_glyf_coordinates() {
let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
let context = OutlineTables::new(&font).unwrap();
let Some(LocaGlyph::Glyph(Glyph::Simple(simple))) = context.glyph(GlyphId::new(1)) else {
panic!("glyph 1 is simple");
};
let raw: Vec<_> = simple.points().map(|point| (point.x, point.y)).collect();
let built = build(font_test_data::GLYF_COMPONENTS, 1, &Unscaled);
assert_eq!(built.points.len(), raw.len());
for (point, (x, y)) in built.points.iter().zip(&raw) {
assert_eq!((point.x, point.y), (i32::from(*x), i32::from(*y)));
}
}
#[test]
fn unscaled_needs_no_second_buffer() {
const { assert!(!Unscaled::NEEDS_UNSCALED) };
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();
assert_eq!(plan.buffer_lengths::<Unscaled>().unscaled, 0);
}
#[test]
fn scaling_is_proportional() {
for gid in [1, 5] {
let small = build(
font_test_data::GLYF_COMPONENTS,
gid,
&Scale26Dot6::new(Some(16.0), 1000),
)
.points;
let large = build(
font_test_data::GLYF_COMPONENTS,
gid,
&Scale26Dot6::new(Some(32.0), 1000),
)
.points;
let slack = 1 + i32::from(is_composite(gid));
for (s, l) in small.iter().zip(&large) {
assert!(
(l.x.to_bits() - 2 * s.x.to_bits()).abs() <= slack,
"gid {gid}"
);
assert!(
(l.y.to_bits() - 2 * s.y.to_bits()).abs() <= slack,
"gid {gid}"
);
}
}
}
}