249 lines
8.1 KiB
Rust
249 lines
8.1 KiB
Rust
//! Helpers for selecting a font size and location in variation space.
|
|
|
|
use read_fonts::types::Fixed;
|
|
|
|
use crate::collections::SmallVec;
|
|
|
|
/// Type for a normalized variation coordinate.
|
|
pub type NormalizedCoord = read_fonts::types::F2Dot14;
|
|
|
|
/// Font size in pixels per em units.
|
|
///
|
|
/// Sizes in this crate are represented as a ratio of pixels to the size of
|
|
/// the em square defined by the font. This is equivalent to the `px` unit
|
|
/// in CSS (assuming a DPI scale factor of 1.0).
|
|
///
|
|
/// To retrieve metrics and outlines in font units, use the [unscaled](Self::unscaled)
|
|
/// constructor on this type.
|
|
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
|
|
pub struct Size(Option<f32>);
|
|
|
|
impl Size {
|
|
/// Creates a new font size from the given value in pixels per em units.
|
|
pub fn new(ppem: f32) -> Self {
|
|
Self(Some(ppem))
|
|
}
|
|
|
|
/// Creates a new font size for generating unscaled metrics or outlines in
|
|
/// font units.
|
|
pub fn unscaled() -> Self {
|
|
Self(None)
|
|
}
|
|
|
|
/// Returns the raw size in pixels per em units.
|
|
///
|
|
/// Results in `None` if the size is unscaled.
|
|
pub fn ppem(self) -> Option<f32> {
|
|
self.0
|
|
}
|
|
|
|
/// Computes a linear scale factor for this font size and the given units
|
|
/// per em value which can be retrieved from the [Metrics](crate::metrics::Metrics)
|
|
/// type or from the [head](read_fonts::tables::head::Head) table.
|
|
///
|
|
/// Returns 1.0 for an unscaled size or when `units_per_em` is 0.
|
|
pub fn linear_scale(self, units_per_em: u16) -> f32 {
|
|
match self.0 {
|
|
Some(ppem) if units_per_em != 0 => ppem / units_per_em as f32,
|
|
_ => 1.0,
|
|
}
|
|
}
|
|
|
|
/// Computes a fixed point linear scale factor that matches FreeType.
|
|
pub(crate) fn fixed_linear_scale(self, units_per_em: u16) -> Fixed {
|
|
// FreeType computes a 16.16 scale factor that converts to 26.6.
|
|
// This is done in two steps, assuming use of FT_Set_Pixel_Size:
|
|
// 1) height is multiplied by 64:
|
|
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/49781ab72b2dfd0f78172023921d08d08f323ade/src/base/ftobjs.c#L3596>
|
|
// 2) this value is divided by UPEM:
|
|
// (here, scaled_h=height and h=upem)
|
|
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/49781ab72b2dfd0f78172023921d08d08f323ade/src/base/ftobjs.c#L3312>
|
|
match self.0 {
|
|
Some(ppem) if units_per_em > 0 => {
|
|
Fixed::from_bits((ppem * 64.) as i32) / Fixed::from_bits(units_per_em as i32)
|
|
}
|
|
_ => {
|
|
// This is an identity scale for the pattern
|
|
// `mul_div(value, scale, 64)`
|
|
Fixed::from_bits(0x10000 * 64)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reference to an ordered sequence of normalized variation coordinates.
|
|
///
|
|
/// To convert from user coordinates see [`crate::AxisCollection::location`].
|
|
///
|
|
/// This type represents a position in the variation space where each
|
|
/// coordinate corresponds to an axis (in the same order as the `fvar` table)
|
|
/// and is a normalized value in the range `[-1..1]`.
|
|
///
|
|
/// See [Coordinate Scales and Normalization](https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization)
|
|
/// for further details.
|
|
///
|
|
/// If the array is larger in length than the number of axes, extraneous
|
|
/// values are ignored. If it is smaller, unrepresented axes are assumed to be
|
|
/// at their default positions (i.e. 0).
|
|
///
|
|
/// A value of this type constructed with `default()` represents the default
|
|
/// position for each axis.
|
|
///
|
|
/// Normalized coordinates are ignored for non-variable fonts.
|
|
#[derive(Copy, Clone, Default, Debug)]
|
|
pub struct LocationRef<'a>(&'a [NormalizedCoord]);
|
|
|
|
impl<'a> LocationRef<'a> {
|
|
/// Creates a new sequence of normalized coordinates from the given array.
|
|
pub fn new(coords: &'a [NormalizedCoord]) -> Self {
|
|
Self(coords)
|
|
}
|
|
|
|
/// Returns the underlying array of normalized coordinates.
|
|
pub fn coords(&self) -> &'a [NormalizedCoord] {
|
|
self.0
|
|
}
|
|
|
|
/// Returns true if this represents the default location in variation
|
|
/// space.
|
|
///
|
|
/// This is represented a set of normalized coordinates that is either
|
|
/// empty or contains all zeroes.
|
|
pub fn is_default(&self) -> bool {
|
|
self.0.is_empty() || self.0.iter().all(|coord| *coord == NormalizedCoord::ZERO)
|
|
}
|
|
|
|
/// Returns the underlying coordinate array if any of the entries are
|
|
/// non-zero. Otherwise returns the empty slice.
|
|
///
|
|
/// This allows internal routines to bypass expensive variation code
|
|
/// paths by just checking for an empty slice.
|
|
pub(crate) fn effective_coords(&self) -> &'a [NormalizedCoord] {
|
|
if self.is_default() {
|
|
&[]
|
|
} else {
|
|
self.0
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a [NormalizedCoord]> for LocationRef<'a> {
|
|
fn from(value: &'a [NormalizedCoord]) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for LocationRef<'a> {
|
|
type IntoIter = core::slice::Iter<'a, NormalizedCoord>;
|
|
type Item = &'a NormalizedCoord;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.iter()
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for &'_ LocationRef<'a> {
|
|
type IntoIter = core::slice::Iter<'a, NormalizedCoord>;
|
|
type Item = &'a NormalizedCoord;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.iter()
|
|
}
|
|
}
|
|
|
|
/// Maximum number of coords to store inline in a `Location` object.
|
|
///
|
|
/// This value was chosen to maximize use of space in the underlying
|
|
/// `SmallVec` storage.
|
|
const MAX_INLINE_COORDS: usize = 8;
|
|
|
|
/// Ordered sequence of normalized variation coordinates.
|
|
///
|
|
/// To produce from user coordinates see [`crate::AxisCollection::location`].
|
|
///
|
|
/// This is an owned version of [`LocationRef`]. See the documentation on that
|
|
/// type for more detail.
|
|
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
pub struct Location {
|
|
coords: SmallVec<NormalizedCoord, MAX_INLINE_COORDS>,
|
|
}
|
|
|
|
impl Location {
|
|
/// Creates a new location with the given number of normalized coordinates.
|
|
///
|
|
/// Each element will be initialized to the default value (0.0).
|
|
pub fn new(len: usize) -> Self {
|
|
Self {
|
|
coords: SmallVec::with_len(len, NormalizedCoord::default()),
|
|
}
|
|
}
|
|
|
|
/// Returns the underlying slice of normalized coordinates.
|
|
pub fn coords(&self) -> &[NormalizedCoord] {
|
|
self.coords.as_slice()
|
|
}
|
|
|
|
/// Returns a mutable reference to the underlying slice of normalized
|
|
/// coordinates.
|
|
pub fn coords_mut(&mut self) -> &mut [NormalizedCoord] {
|
|
self.coords.as_mut_slice()
|
|
}
|
|
}
|
|
|
|
impl Default for Location {
|
|
fn default() -> Self {
|
|
Self {
|
|
coords: SmallVec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a Location> for LocationRef<'a> {
|
|
fn from(value: &'a Location) -> Self {
|
|
LocationRef(value.coords())
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for &'a Location {
|
|
type IntoIter = core::slice::Iter<'a, NormalizedCoord>;
|
|
type Item = &'a NormalizedCoord;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.coords().iter()
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for &'a mut Location {
|
|
type IntoIter = core::slice::IterMut<'a, NormalizedCoord>;
|
|
type Item = &'a mut NormalizedCoord;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.coords_mut().iter_mut()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{FontRef, MetadataProvider};
|
|
|
|
#[test]
|
|
fn effective_coords() {
|
|
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
|
|
let location = font.axes().location([("AVAR", 50.0), ("AVWK", 75.0)]);
|
|
let loc_ref = LocationRef::from(&location);
|
|
assert!(!loc_ref.is_default());
|
|
assert_eq!(loc_ref.effective_coords().len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn effective_coords_for_default() {
|
|
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
|
|
let location = font.axes().location([("AVAR", 0.0), ("AVWK", 0.0)]);
|
|
let loc_ref = LocationRef::from(&location);
|
|
assert!(loc_ref.is_default());
|
|
assert_eq!(loc_ref.effective_coords().len(), 0);
|
|
assert_eq!(loc_ref.coords().len(), 2);
|
|
}
|
|
}
|