Source code
Revision control
Copy as Markdown
Other Tools
//! Font transform types.
use super::num;
use crate::Cursor;
use types::Fixed;
/// A fixed point font matrix.
pub type FontMatrix = types::Matrix<Fixed>;
/// Simple fixed point matrix multiplication with a scaling factor.
///
/// Note: this transforms the translation component of `b` by the upper 2x2 of
/// `a`. This matches the offset transform FreeType uses when concatenating
/// the matrices from the top and font dicts.
pub fn combine_scaled(a: &FontMatrix, b: &FontMatrix, scale: i32) -> FontMatrix {
let a = a.elements();
let b = b.elements();
let val = Fixed::from_i32(scale);
let xx = a[0].mul_div(b[0], val) + a[2].mul_div(b[1], val);
let yx = a[1].mul_div(b[0], val) + a[3].mul_div(b[1], val);
let xy = a[0].mul_div(b[2], val) + a[2].mul_div(b[3], val);
let yy = a[1].mul_div(b[2], val) + a[3].mul_div(b[3], val);
let x = b[4];
let y = b[5];
let dx = x.mul_div(a[0], val) + y.mul_div(a[2], val);
let dy = x.mul_div(a[1], val) + y.mul_div(a[3], val);
FontMatrix::from_elements([xx, yx, xy, yy, dx, dy])
}
/// Check for a degenerate matrix.
pub(crate) fn is_degenerate(matrix: &FontMatrix) -> bool {
let [mut xx, mut yx, mut xy, mut yy, ..] = matrix.elements().map(|x| x.to_bits() as i64);
let val = xx.abs() | yx.abs() | xy.abs() | yy.abs();
if val == 0 || val > 0x7FFFFFFF {
return true;
}
// Scale the matrix to avoid temp1 overflow
let msb = 32 - (val as i32).leading_zeros() - 1;
let shift = msb as i32 - 12;
if shift > 0 {
xx >>= shift;
xy >>= shift;
yx >>= shift;
yy >>= shift;
}
let temp1 = 32 * (xx * yy - xy * yx).abs();
let temp2 = (xx * xx) + (xy * xy) + (yx * yx) + (yy * yy);
if temp1 <= temp2 {
return true;
}
false
}
/// Combination of a matrix and optional scale.
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct Transform {
/// Affine font matrix.
pub matrix: FontMatrix,
/// Fixed point scale factor.
///
/// This is assumed to convert from font units to 26.6 values.
pub scale: Option<Fixed>,
}
impl Transform {
/// The transform that doesn't modify coordinates or metrics.
pub const IDENTITY: Self = Self {
matrix: FontMatrix::IDENTITY,
scale: None,
};
/// Computes a scale factor for the given ppem and upem values.
pub fn compute_scale(ppem: f32, upem: i32) -> Fixed {
Fixed::from_bits((ppem * 64.0) as i32) / Fixed::from_bits(upem.max(1))
}
/// Applies the transform to a horizontal metric such as an advance
/// width.
pub fn transform_h_metric(&self, metric: Fixed) -> Fixed {
let mut metric = Fixed::from_bits(metric.to_i32());
if self.matrix.xx != Fixed::ONE {
// x scale
metric *= self.matrix.xx;
}
// x translation
metric += self.matrix.dx;
if let Some(scale) = self.scale {
// Multiplying by scale converts to 26.6 but we want to keep the
// result in 16.16
Fixed::from_bits((metric * scale).to_bits() << 10)
} else {
// Metric is currently in font units. Convert back to 16.16
Fixed::from_bits(metric.to_bits() << 16)
}
}
}
/// An affine matrix with a scaling factor.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ScaledFontMatrix {
/// The matrix.
pub matrix: FontMatrix,
/// The dynamic scale factor used when parsing this matrix.
pub scale: i32,
}
impl ScaledFontMatrix {
/// Parses a font matrix using dynamic scaling factors.
///
/// Returns the matrix and an adjusted upem factor.
pub(crate) fn parse(cursor: &mut Cursor) -> Option<Self> {
let mut values = [Fixed::ZERO; 6];
let mut scalings = [0i32; 6];
let mut max_scaling = i32::MIN;
let mut min_scaling = i32::MAX;
for (value, scaling) in values.iter_mut().zip(&mut scalings) {
let (v, s) = num::parse_fixed_dynamic(cursor).ok()?;
if v != Fixed::ZERO {
max_scaling = max_scaling.max(s);
min_scaling = min_scaling.min(s);
}
*value = v;
*scaling = s;
}
if !(-9..=0).contains(&max_scaling)
|| (max_scaling - min_scaling < 0)
|| (max_scaling - min_scaling) > 9
{
return None;
}
for (value, scaling) in values.iter_mut().zip(scalings) {
if *value == Fixed::ZERO {
continue;
}
let divisor = num::BCD_POWER_TENS[(max_scaling - scaling) as usize];
let half_divisor = divisor >> 1;
if *value < Fixed::ZERO {
if i32::MIN + half_divisor < value.to_bits() {
*value = Fixed::from_bits((value.to_bits() - half_divisor) / divisor);
} else {
*value = Fixed::from_bits(i32::MIN / divisor);
}
} else if i32::MAX - half_divisor > value.to_bits() {
*value = Fixed::from_bits((value.to_bits() + half_divisor) / divisor);
} else {
*value = Fixed::from_bits(i32::MAX / divisor);
}
}
let matrix = FontMatrix::from_elements(values);
// Check for a degenerate matrix
if is_degenerate(&matrix) {
return None;
}
let scale = num::BCD_POWER_TENS[(-max_scaling) as usize];
Some(Self { matrix, scale })
}
/// Compute a new font matrix and UPEM scale factor where the Y scale of
/// the matrix is 1.0.
#[must_use]
pub fn normalize(&self) -> Self {
let mut matrix = self.matrix.elements();
let mut scaled_upem = self.scale;
let factor = if matrix[3] != Fixed::ZERO {
matrix[3].abs()
} else {
// Use yx if yy is zero
matrix[1].abs()
};
if factor != Fixed::ONE {
scaled_upem = (Fixed::from_bits(scaled_upem) / factor).to_bits();
for value in &mut matrix {
*value /= factor;
}
}
// FT shifts off the fractional parts of the translation?
for offset in matrix[4..6].iter_mut() {
*offset = Fixed::from_bits(offset.to_bits() >> 16);
}
Self {
matrix: FontMatrix::from_elements(matrix),
scale: scaled_upem,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_scale() {
assert_eq!(Transform::compute_scale(1000.0, 1000).to_bits(), 64 << 16);
assert_eq!(Transform::compute_scale(500.0, 1000).to_bits(), 32 << 16);
assert_eq!(Transform::compute_scale(2000.0, 1000).to_bits(), 128 << 16);
assert_eq!(Transform::compute_scale(16.0, 1000).to_bits(), 67109);
}
#[test]
fn h_metric_identity_integral() {
for metric in [Fixed::ZERO, Fixed::ONE, Fixed::NEG_ONE, Fixed::from_i32(42)] {
assert_eq!(Transform::IDENTITY.transform_h_metric(metric), metric);
}
}
#[test]
fn h_metric_identity_fractional() {
// Like FT, metrics are rounded to font units before applying matrix
// and scale
for metric in [
Fixed::from_f64(42.5),
Fixed::from_f64(-20.1),
Fixed::from_f64(18.8),
] {
assert_eq!(
Transform::IDENTITY.transform_h_metric(metric),
metric.round()
);
}
}
#[test]
fn h_metric_matrix_scale() {
let transform = Transform {
matrix: FontMatrix::from_elements([2.0, 0.0, 0.0, 1.0, 0.0, 0.0].map(Fixed::from_f64)),
scale: None,
};
// metric.round() * 2
let pairs = [(42.5, 86.0), (-20.1, -40.0), (18.8, 38.0)];
for (metric, transformed_metric) in pairs {
assert_eq!(
transform
.transform_h_metric(Fixed::from_f64(metric))
.to_f64(),
transformed_metric
);
}
}
#[test]
fn h_metric_matrix_scale_offset() {
let transform = Transform {
matrix: FontMatrix::from_elements(
[2.0, 0.0, 0.0, 1.0, 10.0 / 65536.0, 0.0].map(Fixed::from_f64),
),
scale: None,
};
// metric.round() * 2 + 10
let pairs = [(42.5, 96.0), (-20.1, -30.0), (18.8, 48.0)];
for (metric, transformed_metric) in pairs {
assert_eq!(
transform
.transform_h_metric(Fixed::from_f64(metric))
.to_f64(),
transformed_metric
);
}
}
#[test]
fn h_metric_scale() {
let transform = Transform {
matrix: FontMatrix::IDENTITY,
// Scale by 0.5
scale: Some(Fixed::from_i32(32)),
};
// metric.round() / 2
let pairs = [(42.5, 21.5), (-20.1, -10.0), (18.8, 9.5)];
for (metric, transformed_metric) in pairs {
assert_eq!(
transform
.transform_h_metric(Fixed::from_f64(metric))
.to_f64(),
transformed_metric
);
}
}
#[test]
fn h_metric_scale_matrix_scale_offset() {
let transform = Transform {
matrix: FontMatrix::from_elements(
[4.0, 0.0, 0.0, 1.0, 10.0 / 65536.0, 0.0].map(Fixed::from_f64),
),
// Scale by 0.5
scale: Some(Fixed::from_i32(32)),
};
// (metric.round() * 4 + 10) / 2
let pairs = [(42.5, 91.0), (-20.1, -35.0), (18.8, 43.0)];
for (metric, transformed_metric) in pairs {
assert_eq!(
transform
.transform_h_metric(Fixed::from_f64(metric))
.to_f64(),
transformed_metric
);
}
}
#[test]
fn degenerate_matrix_check_doesnt_overflow() {
// Values taken from font in the above issue
let matrix = FontMatrix::from_elements([
Fixed::from_bits(639999672),
Fixed::ZERO,
Fixed::ZERO,
Fixed::from_bits(639999672),
Fixed::ZERO,
Fixed::ZERO,
]);
// Just don't panic with overflow
is_degenerate(&matrix);
// Try again with all max values
is_degenerate(&FontMatrix::from_elements([Fixed::MAX; 6]));
// And all min values
is_degenerate(&FontMatrix::from_elements([Fixed::MIN; 6]));
}
#[test]
fn normalize_matrix() {
// This matrix has a y scale of 0.5 so we should produce a new matrix
// with a y scale of 1.0 and a scale factor of 2
let matrix = ScaledFontMatrix {
matrix: FontMatrix::from_elements([65536, 0, 0, 32768, 0, 0].map(Fixed::from_bits)),
scale: 1,
};
let normalized = matrix.normalize();
let expected_normalized = [131072, 0, 0, 65536, 0, 0].map(Fixed::from_bits);
assert_eq!(normalized.matrix.elements(), expected_normalized);
assert_eq!(normalized.scale, 2);
}
#[test]
fn combine_matrix() {
let a = FontMatrix::from_elements([0.5, 0.75, -1.0, 2.0, 0.0, 0.0].map(Fixed::from_f64));
let b = FontMatrix::from_elements([1.5, -1.0, 0.25, -1.0, 1.0, 2.0].map(Fixed::from_f64));
let expected = [1.75, -0.875, 1.125, -1.8125, -1.5, 4.75].map(Fixed::from_f64);
let result = combine_scaled(&a, &b, 1);
assert_eq!(result.elements(), expected);
}
}