use super::{
Decoration, GlyphExtents, GlyphMetrics, LineBox, LineExtents, Metrics, ScriptMetrics,
StyleMetrics,
};
use crate::model::glyph::GlyphSourceOrder;
use types::{BoundingBox, F26Dot6, F48Dot16, Fixed, GlyphId};
pub trait Scale {
type Value: Copy;
fn add(a: Self::Value, b: Self::Value) -> Self::Value;
fn sub(a: Self::Value, b: Self::Value) -> Self::Value;
fn half(value: Self::Value) -> Self::Value;
fn scale_x(&self, value: F48Dot16) -> Self::Value;
fn scale_y(&self, value: F48Dot16) -> Self::Value;
fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<Self::Value>;
fn scale_rect(&self, bounds: BoundingBox<F48Dot16>) -> BoundingBox<Self::Value>;
fn ppem(&self) -> Option<f32> {
None
}
}
#[derive(Copy, Clone, Debug)]
pub struct Scale26Dot6 {
x: Fixed,
y: Fixed,
ppem: f32,
}
impl Scale26Dot6 {
pub fn new(x_ppem: f32, y_ppem: f32, units_per_em: u16) -> Self {
Self {
x: Self::factor(x_ppem, units_per_em),
y: Self::factor(y_ppem, units_per_em),
ppem: x_ppem.max(y_ppem),
}
}
pub fn from_ppem(ppem: f32, units_per_em: u16) -> Self {
Self::new(ppem, ppem, units_per_em)
}
fn factor(ppem: f32, units_per_em: u16) -> Fixed {
Fixed::from_bits((ppem * 64.0) as i32) / Fixed::from_bits(units_per_em.max(1) as i32)
}
fn scale(value: F48Dot16, factor: Fixed) -> F26Dot6 {
F26Dot6::from_bits((Fixed::from_bits(value.to_i32()) * factor).to_bits())
}
}
impl Scale for Scale26Dot6 {
type Value = F26Dot6;
fn add(a: F26Dot6, b: F26Dot6) -> F26Dot6 {
a.saturating_add(b)
}
fn sub(a: F26Dot6, b: F26Dot6) -> F26Dot6 {
a.saturating_sub(b)
}
fn half(value: F26Dot6) -> F26Dot6 {
F26Dot6::from_bits(value.to_bits() >> 1)
}
fn scale_x(&self, value: F48Dot16) -> F26Dot6 {
Self::scale(value, self.x)
}
fn scale_y(&self, value: F48Dot16) -> F26Dot6 {
Self::scale(value, self.y)
}
fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<F26Dot6> {
if e.width <= F48Dot16::ZERO || e.height <= F48Dot16::ZERO {
return GlyphExtents::default();
}
let round = |value: F48Dot16| {
let bits = value.to_bits();
F48Dot16::from_bits(bits.wrapping_add(0x8000 - i64::from(bits < 0)) & !0xffff)
};
let left = round(e.x_bearing);
let top = round(e.y_bearing);
let right = round(e.x_bearing + e.width);
let bottom = round(e.y_bearing - e.height);
GlyphExtents {
x_bearing: self.scale_x(left),
y_bearing: self.scale_y(top),
width: self.scale_x(right - left),
height: self.scale_y(top - bottom),
}
}
fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<F26Dot6> {
BoundingBox {
x_min: self.scale_x(b.x_min),
y_min: self.scale_y(b.y_min),
x_max: self.scale_x(b.x_max),
y_max: self.scale_y(b.y_max),
}
}
fn ppem(&self) -> Option<f32> {
Some(self.ppem)
}
}
#[derive(Copy, Clone, Debug)]
pub struct ScaleF32 {
x: f32,
y: f32,
ppem: f32,
}
impl ScaleF32 {
pub fn new(x_ppem: f32, y_ppem: f32, units_per_em: u16) -> Self {
let per_unit = |ppem: f32| ppem / units_per_em.max(1) as f32;
Self {
x: per_unit(x_ppem),
y: per_unit(y_ppem),
ppem: x_ppem.max(y_ppem),
}
}
pub fn from_ppem(ppem: f32, units_per_em: u16) -> Self {
Self::new(ppem, ppem, units_per_em)
}
}
impl Scale for ScaleF32 {
type Value = f32;
fn add(a: f32, b: f32) -> f32 {
a + b
}
fn sub(a: f32, b: f32) -> f32 {
a - b
}
fn half(value: f32) -> f32 {
value * 0.5
}
fn scale_x(&self, value: F48Dot16) -> f32 {
value.to_f32() * self.x
}
fn scale_y(&self, value: F48Dot16) -> f32 {
value.to_f32() * self.y
}
fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
GlyphExtents {
x_bearing: self.scale_x(e.x_bearing),
y_bearing: self.scale_y(e.y_bearing),
width: self.scale_x(e.width),
height: self.scale_y(e.height),
}
}
fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
BoundingBox {
x_min: self.scale_x(b.x_min),
y_min: self.scale_y(b.y_min),
x_max: self.scale_x(b.x_max),
y_max: self.scale_y(b.y_max),
}
}
fn ppem(&self) -> Option<f32> {
Some(self.ppem)
}
}
#[derive(Clone, Copy)]
pub struct ScaledMetrics<'a, S: Scale> {
metrics: &'a Metrics,
scale: S,
}
impl<'a, S: Scale> ScaledMetrics<'a, S> {
pub(crate) fn new(metrics: &'a Metrics, scale: S) -> Self {
Self { metrics, scale }
}
#[inline]
pub fn num_glyphs(&self) -> u32 {
self.metrics.num_glyphs
}
#[inline]
pub fn units_per_em(&self) -> u16 {
self.metrics.units_per_em
}
#[inline]
pub fn bounds(&self) -> BoundingBox<S::Value> {
self.scale.scale_rect(self.metrics.bounds)
}
#[inline]
pub fn h_line(&self) -> Option<LineBox<S::Value>> {
self.metrics.h_line().map(|line| self.scale_line_y(line))
}
#[inline]
pub fn hhea_line(&self) -> Option<LineBox<S::Value>> {
self.metrics.hhea_line.map(|line| self.scale_line_y(line))
}
#[inline]
pub fn typo_line(&self) -> Option<LineBox<S::Value>> {
self.metrics.typo_line.map(|line| self.scale_line_y(line))
}
#[inline]
pub fn use_typo_metrics(&self) -> bool {
self.metrics.use_typo_metrics
}
#[inline]
pub fn vhea_line(&self) -> Option<LineBox<S::Value>> {
self.metrics.vhea_line.map(|line| LineBox {
ascender: self.scale.scale_x(line.ascender),
descender: self.scale.scale_x(line.descender),
line_gap: self.scale.scale_x(line.line_gap),
})
}
#[inline]
pub fn x_height(&self) -> Option<S::Value> {
self.scaled_y(self.metrics.x_height)
}
#[inline]
pub fn cap_height(&self) -> Option<S::Value> {
self.scaled_y(self.metrics.cap_height)
}
#[inline]
pub fn max_h_advance(&self) -> Option<S::Value> {
self.metrics
.max_h_advance
.map(|value| self.scale.scale_x(value))
}
#[inline]
pub fn max_v_advance(&self) -> Option<S::Value> {
self.scaled_y(self.metrics.max_v_advance)
}
#[inline]
pub fn average_char_width(&self) -> Option<S::Value> {
self.metrics
.average_char_width
.map(|value| self.scale.scale_x(value))
}
#[inline]
pub fn win_line(&self) -> Option<LineExtents<S::Value>> {
self.metrics.win_line.map(|line| self.scale_line(line))
}
#[inline]
fn scale_line_y(&self, line: LineBox<F48Dot16>) -> LineBox<S::Value> {
LineBox {
ascender: self.scale.scale_y(line.ascender),
descender: self.scale.scale_y(line.descender),
line_gap: self.scale.scale_y(line.line_gap),
}
}
#[inline]
fn scale_line(&self, line: LineExtents<F48Dot16>) -> LineExtents<S::Value> {
LineExtents {
ascender: self.scale.scale_y(line.ascender),
descender: self.scale.scale_y(line.descender),
}
}
#[inline]
fn scaled_y(&self, value: Option<F48Dot16>) -> Option<S::Value> {
value.map(|value| self.scale.scale_y(value))
}
}
impl Metrics {
#[inline]
pub fn scaled<S: Scale>(&self, scale: S) -> ScaledMetrics<'_, S> {
ScaledMetrics::new(self, scale)
}
#[inline]
pub fn scaled_f32(&self, ppem: f32) -> ScaledMetrics<'_, ScaleF32> {
self.scaled(ScaleF32::from_ppem(ppem, self.units_per_em))
}
#[inline]
pub fn scaled_26dot6(&self, ppem: f32) -> ScaledMetrics<'_, Scale26Dot6> {
self.scaled(Scale26Dot6::from_ppem(ppem, self.units_per_em))
}
}
#[derive(Clone, Copy)]
pub struct ScaledStyleMetrics<'a, S: Scale> {
metrics: &'a StyleMetrics,
scale: S,
}
impl<'a, S: Scale> ScaledStyleMetrics<'a, S> {
pub(crate) fn new(metrics: &'a StyleMetrics, scale: S) -> Self {
Self { metrics, scale }
}
pub fn subscript(&self) -> Option<ScriptMetrics<S::Value>> {
self.metrics
.subscript
.map(|script| self.scale_script(script))
}
pub fn superscript(&self) -> Option<ScriptMetrics<S::Value>> {
self.metrics
.superscript
.map(|script| self.scale_script(script))
}
pub fn underline(&self) -> Option<Decoration<S::Value>> {
self.metrics
.underline
.map(|decoration| self.scale_decoration(decoration))
}
pub fn strike_through(&self) -> Option<Decoration<S::Value>> {
self.metrics
.strike_through
.map(|decoration| self.scale_decoration(decoration))
}
pub fn italic_angle(&self) -> Option<Fixed> {
self.metrics.italic_angle
}
pub fn is_fixed_pitch(&self) -> Option<bool> {
self.metrics.is_fixed_pitch
}
fn scale_script(&self, script: ScriptMetrics) -> ScriptMetrics<S::Value> {
ScriptMetrics {
x_size: self.scale.scale_x(script.x_size),
y_size: self.scale.scale_y(script.y_size),
x_offset: self.scale.scale_x(script.x_offset),
y_offset: self.scale.scale_y(script.y_offset),
}
}
fn scale_decoration(&self, decoration: Decoration) -> Decoration<S::Value> {
Decoration {
position: self.scale.scale_y(decoration.position),
thickness: self.scale.scale_y(decoration.thickness),
}
}
}
impl StyleMetrics {
pub fn scaled<S: Scale>(&self, scale: S) -> ScaledStyleMetrics<'_, S> {
ScaledStyleMetrics::new(self, scale)
}
#[inline]
pub fn scaled_f32(&self, ppem: f32) -> ScaledStyleMetrics<'_, ScaleF32> {
self.scaled(ScaleF32::from_ppem(ppem, self.units_per_em))
}
#[inline]
pub fn scaled_26dot6(&self, ppem: f32) -> ScaledStyleMetrics<'_, Scale26Dot6> {
self.scaled(Scale26Dot6::from_ppem(ppem, self.units_per_em))
}
}
#[derive(Clone, Copy)]
pub struct ScaledGlyphMetrics<'a, 'extents, S: Scale> {
metrics: GlyphMetrics<'a>,
scale: S,
line: Option<LineExtents<S::Value>>,
extents: Option<&'extents dyn Fn(GlyphId) -> Option<GlyphExtents<S::Value>>>,
}
impl<'a, 'extents, S: Scale> ScaledGlyphMetrics<'a, 'extents, S> {
pub(crate) fn new(metrics: GlyphMetrics<'a>, scale: S) -> Self {
Self {
metrics: metrics.with_bitmap_ppem(scale.ppem()),
scale,
line: None,
extents: None,
}
}
pub fn with_line_extents(self, line: Option<LineExtents<S::Value>>) -> Self {
Self { line, ..self }
}
pub fn with_extents_source_order(self, order: GlyphSourceOrder) -> Self {
Self {
metrics: self.metrics.with_extents_source_order(order),
..self
}
}
pub fn with_glyph_extents<'new>(
self,
extents: Option<&'new dyn Fn(GlyphId) -> Option<GlyphExtents<S::Value>>>,
) -> ScaledGlyphMetrics<'a, 'new, S> {
ScaledGlyphMetrics {
metrics: self.metrics,
scale: self.scale,
line: self.line,
extents,
}
}
#[inline]
pub fn h_advance(&self, glyph: GlyphId) -> S::Value {
self.scale.scale_x(self.metrics.h_advance_exact(glyph))
}
#[inline]
pub fn h_advance_batched<'o, V: 'o>(
&self,
convert: impl Fn(S::Value) -> V,
glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
) {
self.metrics
.h_advance_batched(|value| convert(self.scale.scale_x(value)), glyphs);
}
#[inline]
pub fn v_advance(&self, glyph: GlyphId) -> S::Value {
match self.line_height_fallback() {
Some(height) => height,
None => self.scale.scale_y(self.metrics.v_advance_exact(glyph)),
}
}
#[inline]
pub fn v_advance_batched<'o, V: 'o>(
&self,
convert: impl Fn(S::Value) -> V,
glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
) {
if let Some(height) = self.line_height_fallback() {
for (_, out) in glyphs {
*out = convert(height);
}
return;
}
self.metrics
.v_advance_batched(|value| convert(self.scale.scale_y(value)), glyphs);
}
pub fn v_origin_y(&self, glyph: GlyphId) -> S::Value {
let mut origin = self.scale.scale_y(F48Dot16::ZERO);
self.v_origin_y_batched(|value| value, core::iter::once((glyph, &mut origin)));
origin
}
pub fn v_origin_y_batched<'o, V: 'o>(
&self,
convert: impl Fn(S::Value) -> V,
glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
) {
let mut line = None;
self.metrics.v_origin_y_batched_with(
|glyph, origin| {
let value = match origin {
Some(origin) => self.scale.scale_y(origin),
None => self
.origin_against_ink(glyph, *line.get_or_insert_with(|| self.origin_line())),
};
convert(value)
},
|glyphs| {
let line = self.origin_line();
self.extents_batched(
|extents| convert(self.origin_from_extents(extents, line)),
glyphs,
);
},
glyphs,
);
}
#[inline]
pub fn extents(&self, glyph: GlyphId) -> Option<GlyphExtents<S::Value>> {
match self.extents {
Some(extents) => extents(glyph),
None => {
let mut extents = None;
self.metrics.extents_batched(
|value| value.map(|extents| self.scale.scale_glyph_extents(extents)),
core::iter::once((glyph, &mut extents)),
);
extents
}
}
}
#[inline]
pub fn extents_batched<'o, V: 'o>(
&self,
convert: impl Fn(Option<GlyphExtents<S::Value>>) -> V,
glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
) {
if let Some(extents) = self.extents {
for (glyph, out) in glyphs {
*out = convert(extents(glyph));
}
return;
}
self.metrics.extents_batched(
|extents| convert(extents.map(|extents| self.scale.scale_glyph_extents(extents))),
glyphs,
);
}
#[inline]
fn line_height_fallback(&self) -> Option<S::Value> {
if self.metrics.states_vertical_advances() {
return None;
}
let line = self.origin_line();
Some(S::sub(line.ascender, line.descender))
}
fn origin_line(&self) -> LineExtents<S::Value> {
if let Some(line) = self.line {
return line;
}
let line = self.metrics.origin_line();
LineExtents {
ascender: self.scale.scale_y(line.ascender),
descender: self.scale.scale_y(line.descender),
}
}
fn origin_against_ink(&self, glyph: GlyphId, line: LineExtents<S::Value>) -> S::Value {
self.origin_from_extents(self.extents(glyph), line)
}
fn origin_from_extents(
&self,
extents: Option<GlyphExtents<S::Value>>,
line: LineExtents<S::Value>,
) -> S::Value {
let Some(extents) = extents else {
return line.ascender;
};
let line_height = S::sub(line.ascender, line.descender);
S::add(
extents.y_bearing,
S::half(S::sub(line_height, extents.height)),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Blob;
use crate::model::Font;
use alloc::sync::Arc;
use types::{GlyphId, Tag};
#[derive(Clone, Copy)]
struct Ppem {
ppem: f32,
upem: u16,
}
impl Scale for Ppem {
type Value = f32;
fn scale_x(&self, value: F48Dot16) -> f32 {
value.to_f32() * self.ppem / self.upem as f32
}
fn scale_y(&self, value: F48Dot16) -> f32 {
self.scale_x(value)
}
fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
GlyphExtents {
x_bearing: self.scale_x(extents.x_bearing),
y_bearing: self.scale_y(extents.y_bearing),
width: self.scale_x(extents.width),
height: self.scale_y(extents.height),
}
}
fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
BoundingBox {
x_min: self.scale_x(b.x_min),
y_min: self.scale_y(b.y_min),
x_max: self.scale_x(b.x_max),
y_max: self.scale_y(b.y_max),
}
}
fn add(a: f32, b: f32) -> f32 {
a + b
}
fn sub(a: f32, b: f32) -> f32 {
a - b
}
fn half(value: f32) -> f32 {
value / 2.0
}
}
const STATIC: &[u8] = font_test_data::TINOS_SUBSET;
const VERT: &[u8] = font_test_data::MPLUS1CODE_VERTICAL_SUBSET;
const VORG: &[u8] = font_test_data::VORG;
fn scaled(data: &[u8], ppem: f32) -> (Font, Ppem) {
let font = Font::new(data.to_vec(), 0).unwrap();
let upem = font.metrics().units_per_em;
(font, Ppem { ppem, upem })
}
fn line(ascender: f32, descender: f32) -> LineExtents<f32> {
LineExtents {
ascender,
descender,
}
}
#[derive(Clone, Copy)]
struct Rounding(f32, u16);
impl Scale for Rounding {
type Value = f32;
fn scale_x(&self, value: F48Dot16) -> f32 {
(value.to_f32() * self.0 / self.1 as f32).round()
}
fn scale_y(&self, value: F48Dot16) -> f32 {
self.scale_x(value)
}
fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
GlyphExtents {
x_bearing: self.scale_x(e.x_bearing),
y_bearing: self.scale_y(e.y_bearing),
width: self.scale_x(e.width),
height: self.scale_y(e.height),
}
}
fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
BoundingBox {
x_min: self.scale_x(b.x_min),
y_min: self.scale_y(b.y_min),
x_max: self.scale_x(b.x_max),
y_max: self.scale_y(b.y_max),
}
}
fn add(a: f32, b: f32) -> f32 {
a + b
}
fn sub(a: f32, b: f32) -> f32 {
a - b
}
fn half(value: f32) -> f32 {
value / 2.0
}
}
#[test]
fn a_rounding_scale_rounds_the_ends_of_the_line_not_the_height() {
let font = Font::new(STATIC.to_vec(), 0).unwrap();
let upem = font.metrics().units_per_em;
let from_font = font.metrics().h_line().unwrap().extents();
let mut rounded_apart = 0;
for ppem in [11.0f32, 12.0, 13.0, 14.0, 16.0, 19.0, 24.0] {
let scale = Rounding(ppem, upem);
let metrics = font.glyph_metrics().scaled(scale);
let ends = LineExtents {
ascender: scale.scale_y(from_font.ascender),
descender: scale.scale_y(from_font.descender),
};
let from_ends = Rounding::sub(ends.ascender, ends.descender);
assert_eq!(
metrics.v_advance(GlyphId::new(1)),
from_ends,
"at {ppem}ppem"
);
assert_eq!(
metrics
.with_line_extents(Some(ends))
.v_advance(GlyphId::new(1)),
from_ends,
"at {ppem}ppem, supplied"
);
if scale.scale_y(from_font.ascender - from_font.descender) != from_ends {
rounded_apart += 1;
}
}
assert!(
rounded_apart > 0,
"no size here rounds the two ways apart, so this proves nothing"
);
}
#[test]
fn the_scaled_line_is_the_one_the_glyph_metrics_stack_by() {
let font = Font::new(STATIC.to_vec(), 0).unwrap();
let upem = font.metrics().units_per_em;
for ppem in [11.0f32, 12.0, 13.0, 16.0, 19.0, 24.0] {
let scale = Rounding(ppem, upem);
let line = font.metrics().scaled(scale).h_line().unwrap();
let height = Rounding::sub(line.ascender, line.descender);
let glyphs = font.glyph_metrics().scaled(scale);
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(glyphs.v_advance(gid), height, "glyph {gid} at {ppem}ppem");
}
}
}
#[test]
fn every_measurement_is_the_unscaled_one_through_the_scale() {
let font = Font::new(STATIC.to_vec(), 0).unwrap();
let global = font.metrics();
let scale = Ppem {
ppem: 16.0,
upem: global.units_per_em,
};
let scaled = global.scaled(scale);
let y = |line: LineBox<F48Dot16>| LineBox {
ascender: scale.scale_y(line.ascender),
descender: scale.scale_y(line.descender),
line_gap: scale.scale_y(line.line_gap),
};
let x = |line: LineBox<F48Dot16>| LineBox {
ascender: scale.scale_x(line.ascender),
descender: scale.scale_x(line.descender),
line_gap: scale.scale_x(line.line_gap),
};
assert_eq!(scaled.bounds(), scale.scale_rect(global.bounds));
assert_eq!(scaled.hhea_line(), global.hhea_line.map(y));
assert_eq!(scaled.typo_line(), global.typo_line.map(y));
assert_eq!(scaled.vhea_line(), global.vhea_line.map(x));
assert_eq!(scaled.h_line(), global.h_line().map(y));
assert_eq!(
scaled.win_line(),
global.win_line.map(|l| LineExtents {
ascender: scale.scale_y(l.ascender),
descender: scale.scale_y(l.descender),
})
);
assert_eq!(scaled.x_height(), global.x_height.map(|v| scale.scale_y(v)));
assert_eq!(
scaled.cap_height(),
global.cap_height.map(|v| scale.scale_y(v))
);
assert_eq!(
scaled.max_h_advance(),
global.max_h_advance.map(|v| scale.scale_x(v))
);
assert_eq!(
scaled.max_v_advance(),
global.max_v_advance.map(|v| scale.scale_y(v))
);
assert_eq!(
scaled.average_char_width(),
global.average_char_width.map(|v| scale.scale_x(v))
);
assert_eq!(scaled.units_per_em(), global.units_per_em);
assert_eq!(scaled.num_glyphs(), global.num_glyphs);
assert_eq!(scaled.use_typo_metrics(), global.use_typo_metrics);
assert!(scaled.hhea_line().is_some() && scaled.typo_line().is_some());
}
#[test]
fn the_clipping_line_reports_its_descent_as_a_position() {
let font = Font::new(STATIC.to_vec(), 0).unwrap();
let global = font.metrics();
let win = global
.win_line
.expect("this font provides clipping metrics");
assert!(win.ascender > F48Dot16::ZERO);
assert!(win.descender < F48Dot16::ZERO);
let scale = Ppem {
ppem: 16.0,
upem: global.units_per_em,
};
let scaled = global.scaled(scale).win_line().unwrap();
assert_eq!(scaled.ascender, scale.scale_y(win.ascender));
assert_eq!(scaled.descender, scale.scale_y(win.descender));
}
#[test]
fn the_two_scales_agree_within_what_each_can_hold() {
let font = Font::new(STATIC.to_vec(), 0).unwrap();
let upem = font.metrics().units_per_em;
let coarse = font
.glyph_metrics()
.scaled(Scale26Dot6::from_ppem(16.0, upem));
let fine = font.glyph_metrics().scaled(ScaleF32::from_ppem(16.0, upem));
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
let apart = (coarse.h_advance(gid).to_f32() - fine.h_advance(gid)).abs();
assert!(apart < 1.0 / 32.0, "glyph {gid} is {apart} apart");
}
}
#[test]
fn a_scale_with_a_size_per_axis_uses_each_in_its_own_direction() {
let font = Font::new(VERT.to_vec(), 0).unwrap();
let upem = font.metrics().units_per_em;
let em = upem as f32;
let wide = ScaleF32::new(em * 2.0, em, upem);
let tall = ScaleF32::new(em, em * 2.0, upem);
let plain = ScaleF32::new(em, em, upem);
let gid = GlyphId::new(1);
let (w, t, p) = (
font.glyph_metrics().scaled(wide),
font.glyph_metrics().scaled(tall),
font.glyph_metrics().scaled(plain),
);
assert_eq!(w.h_advance(gid), p.h_advance(gid) * 2.0);
assert_eq!(w.v_advance(gid), p.v_advance(gid));
assert_eq!(t.v_advance(gid), p.v_advance(gid) * 2.0);
assert_eq!(t.h_advance(gid), p.h_advance(gid));
}
#[test]
fn freetype_scales_whole_design_units() {
let upem = 1000;
let fraction = F48Dot16::from_f64(10.5);
assert_eq!(
Scale26Dot6::from_ppem(upem as f32, upem).scale_x(fraction),
F26Dot6::from_f64(11.0)
);
assert_eq!(
ScaleF32::from_ppem(upem as f32, upem).scale_x(fraction),
10.5
);
}
#[test]
fn a_rounding_scale_rounds_extents_corners_before_size() {
let extents = GlyphExtents {
x_bearing: F48Dot16::from_f64(10.6),
y_bearing: F48Dot16::from_f64(-0.5),
width: F48Dot16::from_f64(9.8),
height: F48Dot16::from_f64(20.0),
};
let scale = Scale26Dot6::from_ppem(1000.0, 1000);
assert_eq!(
scale.scale_glyph_extents(extents),
GlyphExtents {
x_bearing: F26Dot6::from_f64(11.0),
y_bearing: F26Dot6::from_f64(-1.0),
width: F26Dot6::from_f64(9.0),
height: F26Dot6::from_f64(20.0),
}
);
let below_half = F48Dot16::from_bits((100_000 << 16) + 0x7fff);
let extents = GlyphExtents {
x_bearing: below_half,
y_bearing: F48Dot16::from_i32(10),
width: F48Dot16::from_i32(2),
height: F48Dot16::from_i32(2),
};
assert_eq!(
scale.scale_glyph_extents(extents).x_bearing,
F26Dot6::from_i32(100_000)
);
}
#[test]
fn halving_rounds_toward_negative_infinity_in_26_6() {
assert_eq!(
Scale26Dot6::half(F26Dot6::from_bits(-3)),
F26Dot6::from_bits(-2)
);
assert_eq!(ScaleF32::half(-3.0), -1.5);
}
#[test]
fn a_scaled_advance_is_the_design_unit_one_through_the_scale() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics();
let scaled = metrics.scaled(scale);
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(
scaled.h_advance(gid),
scale.scale_x(metrics.h_advance_exact(gid)),
"glyph {gid}"
);
}
}
#[test]
fn a_stated_line_measures_a_font_that_states_none() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let supplied = metrics.with_line_extents(Some(line(12.0, -4.0)));
let gid = GlyphId::new(1);
assert_eq!(supplied.v_advance(gid), 16.0);
assert!(metrics.v_advance(gid) != 16.0);
}
#[test]
fn a_font_that_states_its_own_ignores_the_line() {
let (font, scale) = scaled(VERT, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let supplied = metrics.with_line_extents(Some(line(99.0, -99.0)));
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(
metrics.v_advance(gid),
supplied.v_advance(gid),
"glyph {gid}"
);
}
}
#[test]
fn stating_nothing_is_stating_nothing() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let gid = GlyphId::new(1);
assert_eq!(
metrics.with_line_extents(None).v_advance(gid),
metrics.v_advance(gid)
);
}
#[test]
fn a_batch_agrees_with_one_at_a_time() {
for (data, supplied) in [
(STATIC, None),
(STATIC, Some(line(12.0, -4.0))),
(VERT, None),
(VERT, Some(line(12.0, -4.0))),
] {
let (font, scale) = scaled(data, 16.0);
let metrics = font
.glyph_metrics()
.scaled(scale)
.with_line_extents(supplied);
let gids: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
let mut h = vec![0.0f32; gids.len()];
let mut v = vec![0.0f32; gids.len()];
metrics.h_advance_batched(|value| value, gids.iter().copied().zip(h.iter_mut()));
metrics.v_advance_batched(|value| value, gids.iter().copied().zip(v.iter_mut()));
for (i, gid) in gids.iter().enumerate() {
assert_eq!(metrics.h_advance(*gid), h[i], "h, glyph {gid}");
assert_eq!(metrics.v_advance(*gid), v[i], "v, glyph {gid}");
}
}
}
#[test]
fn a_scaled_box_is_the_font_box_through_the_scale() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics();
let scaled = metrics.scaled(scale);
let mut measured = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
let expected = metrics
.extents_exact(gid)
.map(|extents| scale.scale_glyph_extents(extents));
assert_eq!(scaled.extents(gid), expected, "glyph {gid}");
measured += expected.is_some() as u32;
}
assert!(measured > 0);
}
#[test]
fn a_supplied_measurement_replaces_the_font_one() {
let (font, scale) = scaled(STATIC, 16.0);
let ink = GlyphExtents {
x_bearing: 1.0,
y_bearing: 2.0,
width: 3.0,
height: 4.0,
};
let supplied = |glyph: GlyphId| (glyph.to_u32() % 2 == 0).then_some(ink);
let metrics = font.glyph_metrics().scaled(scale);
let overridden = metrics.with_glyph_extents(Some(&supplied));
let mut replaced = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(overridden.extents(gid), supplied(gid), "glyph {gid}");
replaced += (metrics.extents(gid) != supplied(gid)) as u32;
}
assert!(replaced > 0, "the font already agreed everywhere");
}
#[test]
fn a_supplied_measurement_answers_a_whole_run() {
let (font, scale) = scaled(STATIC, 16.0);
let ink = GlyphExtents {
x_bearing: 5.0,
y_bearing: 6.0,
width: 7.0,
height: 8.0,
};
let supplied = |glyph: GlyphId| (glyph.to_u32() % 3 != 0).then_some(ink);
let metrics = font
.glyph_metrics()
.scaled(scale)
.with_glyph_extents(Some(&supplied));
let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
let mut batched = vec![None; glyphs.len()];
metrics.extents_batched(
|extents| extents,
glyphs.iter().copied().zip(batched.iter_mut()),
);
for (gid, batched) in glyphs.iter().copied().zip(batched) {
assert_eq!(batched, supplied(gid), "glyph {gid}");
}
}
#[test]
fn no_supplied_measurement_leaves_the_font_in_place() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(
metrics.with_glyph_extents(None).extents(gid),
metrics.extents(gid),
"glyph {gid}"
);
}
}
#[test]
fn a_supplied_measurement_can_borrow_for_one_query() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let glyph = GlyphId::new(1);
let original = metrics.extents(glyph);
let supplied = {
let ink = GlyphExtents {
x_bearing: 1.0,
y_bearing: 2.0,
width: 3.0,
height: 4.0,
};
let callback = |_: GlyphId| Some(ink);
metrics.with_glyph_extents(Some(&callback)).extents(glyph)
};
assert_ne!(supplied, original);
assert_eq!(metrics.extents(glyph), original);
}
#[test]
fn vorg_answers_ahead_of_everything_else() {
let (font, scale) = scaled(VORG, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let direct = crate::FontRef::new(VORG).unwrap();
let vorg = crate::TableProvider::vorg(&direct).unwrap();
for gid in (0..4).map(GlyphId::new) {
assert_eq!(
metrics.v_origin_y(gid),
scale.scale_y(F48Dot16::from_i32(vorg.vertical_origin_y(gid) as i32)),
"glyph {gid}"
);
}
let ink = GlyphExtents {
x_bearing: 0.0,
y_bearing: 999.0,
width: 1.0,
height: 1.0,
};
let supplied = |_: GlyphId| Some(ink);
let overridden = metrics.with_glyph_extents(Some(&supplied));
for gid in (0..4).map(GlyphId::new) {
assert_eq!(overridden.v_origin_y(gid), metrics.v_origin_y(gid));
}
}
#[test]
fn vmtx_uses_the_glyf_top_phantom_point() {
let (font, scale) = scaled(VERT, 16.0);
let glyph_metrics = font.glyph_metrics();
let metrics = glyph_metrics.scaled(scale);
assert!(glyph_metrics.stated_v_origin(GlyphId::new(1)).is_none());
let mut checked = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
let (Some(extents), Some(bearing)) = (
glyph_metrics.extents_exact(gid),
glyph_metrics.stated_v_bearing(gid),
) else {
continue;
};
assert_eq!(
metrics.v_origin_y(gid),
scale.scale_y(glyph_metrics.glyf_v_origin(gid).unwrap()),
"glyph {gid}"
);
assert_eq!(
glyph_metrics.glyf_v_origin(gid).unwrap(),
extents.y_bearing + bearing,
"glyph {gid}"
);
checked += 1;
}
assert!(checked > 0, "no glyph stated both");
let supplied = |_: GlyphId| {
Some(GlyphExtents {
x_bearing: 0.0,
y_bearing: 999.0,
width: 1.0,
height: 1.0,
})
};
let overridden = metrics.with_glyph_extents(Some(&supplied));
assert_eq!(
overridden.v_origin_y(GlyphId::new(1)),
metrics.v_origin_y(GlyphId::new(1))
);
}
#[test]
fn without_vmtx_the_ink_is_centered_in_the_line() {
let (font, scale) = scaled(STATIC, 16.0);
let glyph_metrics = font.glyph_metrics();
let metrics = glyph_metrics.scaled(scale);
assert!(glyph_metrics.stated_v_bearing(GlyphId::new(1)).is_none());
let line = glyph_metrics.line_extents().unwrap();
let height = scale.scale_y(line.ascender) - scale.scale_y(line.descender);
let mut checked = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
let Some(extents) = glyph_metrics.extents_exact(gid) else {
continue;
};
let extents = scale.scale_glyph_extents(extents);
assert_eq!(
metrics.v_origin_y(gid),
extents.y_bearing + (height - extents.height) / 2.0,
"glyph {gid}"
);
checked += 1;
}
assert!(checked > 0);
}
#[test]
fn an_empty_glyph_is_centered_but_unavailable_extents_use_the_ascender() {
let (font, scale) = scaled(STATIC, 16.0);
let glyph_metrics = font.glyph_metrics();
let metrics = glyph_metrics.scaled(scale);
let line = metrics.origin_line();
let absent = |_: GlyphId| None;
let overridden = metrics.with_glyph_extents(Some(&absent));
let mut checked = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
if glyph_metrics.extents(gid) != Some(GlyphExtents::default()) {
continue;
}
assert_eq!(
metrics.v_origin_y(gid),
(line.ascender - line.descender) / 2.0,
"glyph {gid}"
);
assert_eq!(overridden.v_origin_y(gid), line.ascender);
checked += 1;
}
assert!(checked > 0, "no glyph was empty");
}
#[test]
fn a_supplied_measurement_moves_the_origin_with_it() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let ink = GlyphExtents {
x_bearing: 0.0,
y_bearing: 100.0,
width: 10.0,
height: 20.0,
};
let supplied = |_: GlyphId| Some(ink);
let overridden = metrics.with_glyph_extents(Some(&supplied));
let line = metrics.origin_line();
let height = line.ascender - line.descender;
let expected = ink.y_bearing + (height - ink.height) / 2.0;
let mut moved = 0;
for gid in (0..font.num_glyphs()).map(GlyphId::new) {
assert_eq!(overridden.v_origin_y(gid), expected, "glyph {gid}");
moved += (metrics.v_origin_y(gid) != expected) as u32;
}
assert!(moved > 0, "the font already agreed everywhere");
let gids: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
let mut origins = vec![0.0; gids.len()];
overridden.v_origin_y_batched(
|origin| origin,
gids.iter().copied().zip(origins.iter_mut()),
);
assert!(origins.iter().all(|origin| *origin == expected));
}
#[test]
fn a_supplied_line_moves_the_origin_with_it() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let taller = metrics.with_line_extents(Some(line(100.0, -20.0)));
let gid = (0..font.num_glyphs())
.map(GlyphId::new)
.find(|gid| font.glyph_metrics().extents(*gid).is_some())
.unwrap();
assert_ne!(taller.v_origin_y(gid), metrics.v_origin_y(gid));
}
#[test]
fn a_run_of_origins_agrees_with_one_at_a_time() {
for data in [VORG, VERT, STATIC] {
let (font, scale) = scaled(data, 16.0);
let metrics = font.glyph_metrics().scaled(scale);
let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
let mut batched = vec![0.0; glyphs.len()];
metrics.v_origin_y_batched(
|origin| origin,
glyphs.iter().copied().zip(batched.iter_mut()),
);
for (gid, batched) in glyphs.iter().copied().zip(batched) {
assert_eq!(metrics.v_origin_y(gid), batched, "glyph {gid}");
}
}
}
#[test]
fn a_font_stating_no_line_is_given_one_proportioned_to_its_em() {
let font = font_without(&[b"hhea", b"OS/2"]);
let glyph_metrics = font.glyph_metrics();
assert!(glyph_metrics.line_extents().is_none());
let em = font.units_per_em();
assert!(
em > 0,
"the em itself has to survive for this to mean anything"
);
let scale = Ppem {
ppem: 16.0,
upem: em,
};
let metrics = glyph_metrics.scaled(scale);
let ascender = F48Dot16::from_f64(em as f64 * 0.8);
let empty = empty_glyph(&font);
assert_eq!(
metrics.v_origin_y(empty),
scale.scale_y(F48Dot16::from_i32(em as i32)) / 2.0
);
let absent = |_: GlyphId| None;
assert_eq!(
metrics.with_glyph_extents(Some(&absent)).v_origin_y(empty),
scale.scale_y(ascender)
);
assert_eq!(
metrics.v_advance(empty),
scale.scale_y(ascender) - scale.scale_y(ascender - F48Dot16::from_i32(em as i32))
);
}
fn font_without(hide: &'static [&[u8; 4]]) -> Font {
let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
if hide.iter().any(|hidden| Tag::new(hidden) == tag) {
return None;
}
let font = crate::FontRef::new(STATIC).ok()?;
Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
});
Font::new(source, 0).unwrap()
}
#[test]
fn the_ends_of_the_line_are_signed_whatever_the_font_says() {
let stated = font_with_hhea_ends(1600, -400);
let scale = Ppem {
ppem: 16.0,
upem: stated.units_per_em(),
};
let empty = empty_glyph(&stated);
let ink = drawn_glyph(&stated);
let expected = stated.glyph_metrics().scaled(scale);
for (ascender, descender) in [(1600, 400), (-1600, 400), (-1600, -400)] {
let font = font_with_hhea_ends(ascender, descender);
let actual = font.glyph_metrics().scaled(scale);
assert_eq!(
actual.v_origin_y(empty),
expected.v_origin_y(empty),
"ascender {ascender}, descender {descender}"
);
assert_eq!(
actual.v_origin_y(ink),
expected.v_origin_y(ink),
"ascender {ascender}, descender {descender}"
);
assert_eq!(
actual.v_advance(ink),
expected.v_advance(ink),
"ascender {ascender}, descender {descender}"
);
}
assert_eq!(
expected.v_origin_y(empty),
scale.scale_y(F48Dot16::from_i32(1000))
);
}
#[test]
fn the_scale_chooses_the_bitmap_strike() {
let font = Font::new(font_test_data::CBDT, 0).unwrap();
let glyph = GlyphId::new(2);
let upem = font.units_per_em();
let metrics = font.glyph_metrics();
for scale in [
ScaleF32::from_ppem(12.0, upem),
ScaleF32::new(12.0, 10.0, upem),
] {
assert_eq!(scale.ppem(), Some(12.0));
let expected = metrics
.with_bitmap_ppem(Some(12.0))
.extents_exact(glyph)
.map(|extents| scale.scale_glyph_extents(extents));
assert_eq!(metrics.scaled(scale).extents(glyph), expected);
}
assert_eq!(Scale26Dot6::new(10.0, 14.0, upem).ppem(), Some(14.0));
assert_ne!(
metrics.with_bitmap_ppem(Some(12.0)).extents_exact(glyph),
metrics.with_bitmap_ppem(None).extents_exact(glyph)
);
struct NoSize(ScaleF32);
impl Scale for NoSize {
type Value = f32;
fn add(a: f32, b: f32) -> f32 {
ScaleF32::add(a, b)
}
fn sub(a: f32, b: f32) -> f32 {
ScaleF32::sub(a, b)
}
fn half(value: f32) -> f32 {
ScaleF32::half(value)
}
fn scale_x(&self, value: F48Dot16) -> f32 {
self.0.scale_x(value)
}
fn scale_y(&self, value: F48Dot16) -> f32 {
self.0.scale_y(value)
}
fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
self.0.scale_glyph_extents(extents)
}
fn scale_rect(&self, bounds: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
self.0.scale_rect(bounds)
}
}
let no_size = NoSize(ScaleF32::from_ppem(12.0, upem));
let largest = metrics
.with_bitmap_ppem(None)
.extents_exact(glyph)
.map(|extents| no_size.scale_glyph_extents(extents));
assert_eq!(metrics.scaled(no_size).extents(glyph), largest);
}
#[test]
fn a_supplied_line_is_taken_as_it_was_given() {
let (font, scale) = scaled(STATIC, 16.0);
let metrics = font
.glyph_metrics()
.scaled(scale)
.with_line_extents(Some(line(-100.0, 20.0)));
let absent = |_: GlyphId| None;
assert_eq!(
metrics
.with_glyph_extents(Some(&absent))
.v_origin_y(empty_glyph(&font)),
-100.0
);
}
fn font_with_hhea_ends(ascender: i16, descender: i16) -> Font {
let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
if tag == Tag::new(b"OS/2") {
return None;
}
let font = crate::FontRef::new(STATIC).ok()?;
let mut data = font.table_data(tag)?.as_bytes().to_vec();
if tag == Tag::new(b"hhea") {
data.get_mut(4..6)?.copy_from_slice(&ascender.to_be_bytes());
data.get_mut(6..8)?
.copy_from_slice(&descender.to_be_bytes());
}
Some(Blob::from(data))
});
Font::new(source, 0).unwrap()
}
fn empty_glyph(font: &Font) -> GlyphId {
let metrics = font.glyph_metrics();
(0..font.num_glyphs())
.map(GlyphId::new)
.find(|gid| metrics.extents(*gid) == Some(GlyphExtents::default()))
.expect("no glyph has a zero box")
}
fn drawn_glyph(font: &Font) -> GlyphId {
let metrics = font.glyph_metrics();
(0..font.num_glyphs())
.map(GlyphId::new)
.find(|gid| metrics.extents(*gid).is_some())
.expect("no glyph drew anything")
}
}