Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ rust-version = "1.85"
# Chrome: https://chromium.googlesource.com/chromium/src/third_party/rust/+/refs/heads/main/bytemuck/v1/BUILD.gn
# Android: https://android.googlesource.com/platform/external/rust/crates/bytemuck/+/refs/heads/main/Cargo.toml
bytemuck = "1.14.0"
# note: as with bytemuck, the smallvec version must be available in all
# deployment environments, specifically the floor of the versions supported by
# google3, Chrome and Android.
# Chrome: https://chromium.googlesource.com/chromium/src/third_party/rust/+/refs/heads/main/smallvec/v1/BUILD.gn
# Android: https://android.googlesource.com/platform/external/rust/crates/smallvec/+/refs/heads/main/Cargo.toml
smallvec = { version = "1.13.1", default-features = false, features = [
"const_generics",
"union",
] }
# dev dependencies
env_logger = "0.11"
pretty_assertions = "1.3.0"
Expand Down
1 change: 1 addition & 0 deletions read-fonts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ libm = ["dep:core_maths"]
agl = []

[dependencies]
smallvec = { workspace = true }
font-types = { workspace = true, features = ["bytemuck"] }
serde = { version = "1.0", features = ["derive"], optional = true }
core_maths = { workspace = true, optional = true }
Expand Down
109 changes: 22 additions & 87 deletions read-fonts/src/model/font/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use crate::{
},
TableProvider,
};
use alloc::vec::Vec;
use core::{
str::FromStr,
sync::atomic::{self, AtomicU32},
};
use smallvec::SmallVec;
use types::{Fixed, Tag};

/// A specific instance of a font, with a size and variation settings.
Expand All @@ -31,7 +31,7 @@ impl FontInstance {
instance: Self {
font: font.clone(),
size: None,
coords: CoordStorage::default(),
coords: CoordStorage::new(),
feature_vars: FeatureVarsStorage::new(),
},
}
Expand Down Expand Up @@ -157,24 +157,24 @@ impl FontInstanceBuilder {
variations,
);
} else {
self.instance.coords.resize(0);
self.instance.coords.clear();
}
}

fn set_coords(&mut self, coords: impl IntoIterator<Item = NormalizedCoord>) {
if let Ok(fvar) = self.instance.font.tables().fvar() {
let count = fvar.axis_count() as usize;
self.instance.coords.resize(count);
self.instance.coords.resize(count, NormalizedCoord::ZERO);
for (dst, src) in self.instance.coords.as_mut_slice().iter_mut().zip(
coords
.into_iter()
.chain(core::iter::repeat(NormalizedCoord::ZERO)),
) {
*dst = src;
}
self.instance.coords.clear_if_all_zeroes();
clear_if_all_zeroes(&mut self.instance.coords);
} else {
self.instance.coords.resize(0);
self.instance.coords.clear();
}
}

Expand All @@ -188,7 +188,7 @@ impl FontInstanceBuilder {
named_instance_variations(&fvar, index),
);
} else {
self.instance.coords.resize(0);
self.instance.coords.clear();
}
}

Expand All @@ -207,7 +207,7 @@ impl FontInstanceBuilder {
.chain(overrides.into_iter().map(Into::into)),
);
} else {
self.instance.coords.resize(0);
self.instance.coords.clear();
}
}
}
Expand Down Expand Up @@ -239,7 +239,7 @@ where
V: IntoIterator,
V::Item: Into<FontVariation>,
{
coords.resize(fvar.axis_count() as usize);
coords.resize(fvar.axis_count() as usize, NormalizedCoord::ZERO);
fvar.user_to_normalized(
avar.as_ref(),
variations
Expand All @@ -248,7 +248,7 @@ where
.map(|var| (var.tag, Fixed::from_f64(var.value as _))),
coords.as_mut_slice(),
);
coords.clear_if_all_zeroes();
clear_if_all_zeroes(coords);
}

/// A normalized variation coordinate in 2.14 fixed point in the range
Expand Down Expand Up @@ -309,85 +309,20 @@ impl From<&(&str, f32)> for FontVariation {
}
}

/// Maximum number of coordinates we store inline. Chosen to maximize
/// number of coords while minimizing space overhead.
const MAX_INLINE_COORDS: usize = 15;
/// Maximum number of coordinates we store inline. Chosen to maximize the
/// number of coords without growing the storage: a `SmallVec` is a capacity
/// alongside a union of the inline array and the heap pointer, so twelve
/// two byte coords are the most that fit in the same 32 bytes the previous
/// hand rolled storage took.
const MAX_INLINE_COORDS: usize = 12;

enum CoordStorage {
None,
Inline([NormalizedCoord; MAX_INLINE_COORDS], u8),
Heap(Vec<NormalizedCoord>),
}

impl Default for CoordStorage {
fn default() -> Self {
Self::None
}
}

impl CoordStorage {
/// Empty storage if all the coordinates are zeros. This allows us to
/// bypass variation processing for the default instance with a simple
/// is_empty() check.
fn clear_if_all_zeroes(&mut self) {
match self {
Self::None => {}
Self::Inline(coords, len) => {
if coords[..*len as usize]
.iter()
.all(|&c| c == NormalizedCoord::ZERO)
{
*len = 0;
}
}
Self::Heap(heap) => {
if heap.iter().all(|&c| c == NormalizedCoord::ZERO) {
heap.clear();
}
}
}
}
type CoordStorage = SmallVec<[NormalizedCoord; MAX_INLINE_COORDS]>;

fn resize(&mut self, new_len: usize) {
match self {
Self::None => {
if new_len > MAX_INLINE_COORDS {
let mut heap = Vec::with_capacity(new_len);
heap.resize(new_len, NormalizedCoord::ZERO);
*self = Self::Heap(heap);
} else {
*self = Self::Inline([NormalizedCoord::ZERO; MAX_INLINE_COORDS], new_len as u8);
}
}
Self::Inline(_, len) => {
if new_len > MAX_INLINE_COORDS {
let mut heap = Vec::with_capacity(new_len);
heap.resize(new_len, NormalizedCoord::ZERO);
*self = Self::Heap(heap);
} else {
*len = new_len as u8;
}
}
Self::Heap(heap) => {
heap.resize(new_len, NormalizedCoord::ZERO);
}
}
}

fn as_slice(&self) -> &[NormalizedCoord] {
match self {
Self::None => &[],
Self::Inline(coords, len) => &coords[..*len as usize],
Self::Heap(heap) => heap.as_slice(),
}
}

fn as_mut_slice(&mut self) -> &mut [NormalizedCoord] {
match self {
Self::None => &mut [],
Self::Inline(coords, len) => &mut coords[..*len as usize],
Self::Heap(heap) => heap.as_mut_slice(),
}
/// Empties `coords` if every coordinate is zero. This lets the default
/// instance skip variation processing behind a plain `is_empty` check.
fn clear_if_all_zeroes(coords: &mut CoordStorage) {
if coords.iter().all(|&c| c == NormalizedCoord::ZERO) {
coords.clear();
}
}

Expand Down
34 changes: 9 additions & 25 deletions read-fonts/src/tables/fvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ include!("../../generated/generated_fvar.rs");
mod instance_record;

use super::{avar::Avar, variations::DeltaSetIndex};
use alloc::vec::Vec;
use smallvec::SmallVec;

pub use instance_record::InstanceRecord;

Expand Down Expand Up @@ -60,14 +60,8 @@ fn normalize_user_coords<T>(
return;
}
// Above MAX_NORMALIZE_QUADRATIC_AXES, use an indexed path to avoid O(n^2) behavior
let mut stack_axis_order = [0u16; MAX_INLINE_NORMALIZE_AXES];
let mut heap_axis_order = Vec::new();
let axis_order = if axis_count > MAX_INLINE_NORMALIZE_AXES {
heap_axis_order.resize(axis_count, 0);
heap_axis_order.as_mut_slice()
} else {
&mut stack_axis_order[..axis_count]
};
let mut axis_order = SmallVec::<[u16; MAX_INLINE_NORMALIZE_AXES]>::from_elem(0, axis_count);
let axis_order = axis_order.as_mut_slice();
// Initialize axis_order with the identity mapping
for (i, axis_index) in axis_order.iter_mut().enumerate() {
*axis_index = i as u16;
Expand Down Expand Up @@ -147,14 +141,9 @@ impl<'a> Fvar<'a> {
let actual_len = axes.len().min(normalized_coords.len());
let normalized_coords = &mut normalized_coords[..actual_len];

let mut stack_fixed_coords = [Fixed::ZERO; MAX_INLINE_AVAR2_AXES];
let mut heap_fixed_coords = Vec::new();
let fixed_coords = if actual_len > MAX_INLINE_AVAR2_AXES {
heap_fixed_coords.resize(actual_len, Fixed::ZERO);
heap_fixed_coords.as_mut_slice()
} else {
&mut stack_fixed_coords[..actual_len]
};
let mut fixed_coords =
SmallVec::<[Fixed; MAX_INLINE_AVAR2_AXES]>::from_elem(Fixed::ZERO, actual_len);
let fixed_coords = fixed_coords.as_mut_slice();
normalize_user_coords(axes, user_coords, fixed_coords, core::convert::identity);
apply_avar_mappings(avar, fixed_coords, |coord| *coord, core::convert::identity);

Expand All @@ -170,14 +159,9 @@ impl<'a> Fvar<'a> {
let var_store = avar.var_store();
let var_index_map = avar.axis_index_map();

let mut stack_coords_2dot14 = [F2Dot14::ZERO; MAX_INLINE_AVAR2_AXES];
let mut heap_coords_2dot14 = Vec::new();
let coords_2dot14 = if actual_len > MAX_INLINE_AVAR2_AXES {
heap_coords_2dot14.resize(actual_len, F2Dot14::ZERO);
heap_coords_2dot14.as_mut_slice()
} else {
&mut stack_coords_2dot14[..actual_len]
};
let mut coords_2dot14 =
SmallVec::<[F2Dot14; MAX_INLINE_AVAR2_AXES]>::from_elem(F2Dot14::ZERO, actual_len);
let coords_2dot14 = coords_2dot14.as_mut_slice();
for (coord_2dot14, coord) in coords_2dot14.iter_mut().zip(fixed_coords.iter()) {
*coord_2dot14 = coord.to_f2dot14();
}
Expand Down
1 change: 1 addition & 0 deletions skrifa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ spec_next = ["read-fonts/spec_next"]
libm = ["dep:core_maths", "read-fonts/libm"]

[dependencies]
smallvec = { workspace = true }
read-fonts = { workspace = true, default-features = false }
core_maths = { workspace = true, optional = true }
bytemuck = { workspace = true }
Expand Down
Loading
Loading