diff --git a/Cargo.toml b/Cargo.toml index 53717fb6e..e4bac8bb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/read-fonts/Cargo.toml b/read-fonts/Cargo.toml index 285cc135d..c6cc00244 100644 --- a/read-fonts/Cargo.toml +++ b/read-fonts/Cargo.toml @@ -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 } diff --git a/read-fonts/src/model/font/instance.rs b/read-fonts/src/model/font/instance.rs index f8f2a38ac..99a3ff8c8 100644 --- a/read-fonts/src/model/font/instance.rs +++ b/read-fonts/src/model/font/instance.rs @@ -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. @@ -31,7 +31,7 @@ impl FontInstance { instance: Self { font: font.clone(), size: None, - coords: CoordStorage::default(), + coords: CoordStorage::new(), feature_vars: FeatureVarsStorage::new(), }, } @@ -157,14 +157,14 @@ impl FontInstanceBuilder { variations, ); } else { - self.instance.coords.resize(0); + self.instance.coords.clear(); } } fn set_coords(&mut self, coords: impl IntoIterator) { 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() @@ -172,9 +172,9 @@ impl FontInstanceBuilder { ) { *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(); } } @@ -188,7 +188,7 @@ impl FontInstanceBuilder { named_instance_variations(&fvar, index), ); } else { - self.instance.coords.resize(0); + self.instance.coords.clear(); } } @@ -207,7 +207,7 @@ impl FontInstanceBuilder { .chain(overrides.into_iter().map(Into::into)), ); } else { - self.instance.coords.resize(0); + self.instance.coords.clear(); } } } @@ -239,7 +239,7 @@ where V: IntoIterator, V::Item: Into, { - coords.resize(fvar.axis_count() as usize); + coords.resize(fvar.axis_count() as usize, NormalizedCoord::ZERO); fvar.user_to_normalized( avar.as_ref(), variations @@ -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 @@ -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), -} - -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(); } } diff --git a/read-fonts/src/tables/fvar.rs b/read-fonts/src/tables/fvar.rs index 6b270063f..6817b75dd 100644 --- a/read-fonts/src/tables/fvar.rs +++ b/read-fonts/src/tables/fvar.rs @@ -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; @@ -60,14 +60,8 @@ fn normalize_user_coords( 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; @@ -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); @@ -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(); } diff --git a/skrifa/Cargo.toml b/skrifa/Cargo.toml index f89417d74..4e17c123f 100644 --- a/skrifa/Cargo.toml +++ b/skrifa/Cargo.toml @@ -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 } diff --git a/skrifa/src/collections.rs b/skrifa/src/collections.rs deleted file mode 100644 index 3aa8a639c..000000000 --- a/skrifa/src/collections.rs +++ /dev/null @@ -1,368 +0,0 @@ -//! Internal "small" style collection types. - -use alloc::vec::Vec; -use core::hash::{Hash, Hasher}; - -/// A growable vector type with inline storage optimization. -/// -/// Note that unlike the real `SmallVec`, this only works with types that -/// are `Copy + Default` to simplify our implementation. -#[derive(Clone)] -pub(crate) struct SmallVec(Storage); - -impl SmallVec -where - T: Copy + Default, -{ - /// Creates a new, empty `SmallVec`. - pub fn new() -> Self { - Self(Storage::Inline([T::default(); N], 0)) - } - - /// Creates a new `SmallVec` of the given length with each element - /// containing a copy of `value`. - pub fn with_len(len: usize, value: T) -> Self { - if len <= N { - Self(Storage::Inline([value; N], len)) - } else { - let mut vec = Vec::new(); - vec.resize(len, value); - Self(Storage::Heap(vec)) - } - } - - /// Clears the vector, removing all values. - pub fn clear(&mut self) { - match &mut self.0 { - Storage::Inline(_buf, len) => *len = 0, - Storage::Heap(vec) => vec.clear(), - } - } - - /// Reserves capacity for at least `additional` more elements. - pub fn reserve(&mut self, additional: usize) { - match &mut self.0 { - Storage::Inline(buf, len) => { - let new_cap = len.saturating_add(additional); - if new_cap > N { - let mut vec = Vec::with_capacity(new_cap); - vec.extend_from_slice(&buf[..*len]); - self.0 = Storage::Heap(vec); - } - } - Storage::Heap(vec) => { - vec.reserve(additional); - } - } - } - - /// Appends an element to the back of the collection. - pub fn push(&mut self, value: T) { - match &mut self.0 { - Storage::Inline(buf, len) => { - if *len + 1 > N { - let mut vec = Vec::with_capacity(*len + 1); - vec.extend_from_slice(&buf[..*len]); - vec.push(value); - self.0 = Storage::Heap(vec); - } else { - buf[*len] = value; - *len += 1; - } - } - Storage::Heap(vec) => vec.push(value), - } - } - - /// Removes and returns the value at the back of the collection. - pub fn pop(&mut self) -> Option { - match &mut self.0 { - Storage::Inline(buf, len) => { - if *len > 0 { - *len -= 1; - Some(buf[*len]) - } else { - None - } - } - Storage::Heap(vec) => vec.pop(), - } - } - - /// Shortens the vector, keeping the first `len` elements. - pub fn truncate(&mut self, len: usize) { - match &mut self.0 { - Storage::Inline(_buf, inline_len) => { - *inline_len = len.min(*inline_len); - } - Storage::Heap(vec) => vec.truncate(len), - } - } - - /// Resizes the vector to `len` elements, filling with `value`. - /// Reuses existing heap allocation when possible. - pub fn resize_and_fill(&mut self, len: usize, value: T) { - match &mut self.0 { - Storage::Inline(buf, inline_len) => { - if len <= N { - buf[..len].fill(value); - *inline_len = len; - } else { - // Need to spill to heap - let mut vec = Vec::with_capacity(len); - vec.resize(len, value); - self.0 = Storage::Heap(vec); - } - } - Storage::Heap(vec) => { - vec.clear(); - vec.resize(len, value); - } - } - } -} - -impl SmallVec { - /// Extracts a slice containing the entire vector. - pub fn as_slice(&self) -> &[T] { - match &self.0 { - Storage::Inline(buf, len) => &buf[..*len], - Storage::Heap(vec) => vec.as_slice(), - } - } - - /// Extracts a mutable slice containing the entire vector. - pub fn as_mut_slice(&mut self) -> &mut [T] { - match &mut self.0 { - Storage::Inline(buf, len) => &mut buf[..*len], - Storage::Heap(vec) => vec.as_mut_slice(), - } - } -} - -impl Default for SmallVec -where - T: Copy + Default, -{ - fn default() -> Self { - Self::new() - } -} - -impl core::ops::Deref for SmallVec { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl core::ops::DerefMut for SmallVec { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_slice() - } -} - -impl Hash for SmallVec -where - T: Hash, -{ - fn hash(&self, state: &mut H) { - self.as_slice().hash(state); - } -} - -impl PartialEq for SmallVec -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl PartialEq<[T]> for SmallVec -where - T: PartialEq, -{ - fn eq(&self, other: &[T]) -> bool { - self.as_slice() == other - } -} - -impl Eq for SmallVec where T: Eq {} - -impl core::fmt::Debug for SmallVec -where - T: core::fmt::Debug, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_list().entries(self.as_slice().iter()).finish() - } -} - -impl<'a, T, const N: usize> IntoIterator for &'a SmallVec { - type IntoIter = core::slice::Iter<'a, T>; - type Item = &'a T; - - fn into_iter(self) -> Self::IntoIter { - self.as_slice().iter() - } -} - -impl<'a, T, const N: usize> IntoIterator for &'a mut SmallVec { - type IntoIter = core::slice::IterMut<'a, T>; - type Item = &'a mut T; - - fn into_iter(self) -> Self::IntoIter { - self.as_mut_slice().iter_mut() - } -} - -impl IntoIterator for SmallVec -where - T: Copy, -{ - type IntoIter = IntoIter; - type Item = T; - - fn into_iter(self) -> Self::IntoIter { - IntoIter { vec: self, pos: 0 } - } -} - -#[derive(Clone)] -pub(crate) struct IntoIter { - vec: SmallVec, - pos: usize, -} - -impl Iterator for IntoIter -where - T: Copy, -{ - type Item = T; - - fn next(&mut self) -> Option { - let value = self.vec.get(self.pos)?; - self.pos += 1; - Some(*value) - } -} - -#[derive(Clone)] -enum Storage { - Inline([T; N], usize), - Heap(Vec), -} - -#[cfg(test)] -mod test { - use super::{SmallVec, Storage}; - - #[test] - fn choose_inline() { - let vec = SmallVec::<_, 4>::with_len(4, 0); - assert!(matches!(vec.0, Storage::Inline(..))); - assert_eq!(vec.len(), 4); - } - - #[test] - fn choose_heap() { - let vec = SmallVec::<_, 4>::with_len(5, 0); - assert!(matches!(vec.0, Storage::Heap(..))); - assert_eq!(vec.len(), 5); - } - - #[test] - fn store_and_read_inline() { - let mut vec = SmallVec::<_, 8>::with_len(8, 0); - for (i, value) in vec.iter_mut().enumerate() { - *value = i * 2; - } - let expected = [0, 2, 4, 6, 8, 10, 12, 14]; - assert_eq!(vec.as_slice(), &expected); - assert_eq!(format!("{vec:?}"), format!("{expected:?}")); - } - - #[test] - fn store_and_read_heap() { - let mut vec = SmallVec::<_, 4>::with_len(8, 0); - for (i, value) in vec.iter_mut().enumerate() { - *value = i * 2; - } - let expected = [0, 2, 4, 6, 8, 10, 12, 14]; - assert_eq!(vec.as_slice(), &expected); - assert_eq!(format!("{vec:?}"), format!("{expected:?}")); - } - - #[test] - fn spill_to_heap() { - let mut vec = SmallVec::<_, 4>::new(); - for i in 0..4 { - vec.push(i); - } - assert!(matches!(vec.0, Storage::Inline(..))); - vec.push(4); - assert!(matches!(vec.0, Storage::Heap(..))); - let expected = [0, 1, 2, 3, 4]; - assert_eq!(vec.as_slice(), &expected); - } - - #[test] - fn clear_inline() { - let mut vec = SmallVec::<_, 4>::new(); - for i in 0..4 { - vec.push(i); - } - assert!(matches!(vec.0, Storage::Inline(..))); - assert_eq!(vec.len(), 4); - vec.clear(); - assert_eq!(vec.len(), 0); - } - - #[test] - fn clear_heap() { - let mut vec = SmallVec::<_, 3>::new(); - for i in 0..4 { - vec.push(i); - } - assert!(matches!(vec.0, Storage::Heap(..))); - assert_eq!(vec.len(), 4); - vec.clear(); - assert_eq!(vec.len(), 0); - } - - #[test] - fn reserve() { - let mut vec = SmallVec::<_, 3>::new(); - for i in 0..2 { - vec.push(i); - } - assert!(matches!(vec.0, Storage::Inline(..))); - vec.reserve(1); - // still inline after reserving 1 - assert!(matches!(vec.0, Storage::Inline(..))); - vec.reserve(2); - // reserving 2 spills to heap - assert!(matches!(vec.0, Storage::Heap(..))); - } - - #[test] - fn iter() { - let mut vec = SmallVec::<_, 3>::new(); - for i in 0..3 { - vec.push(i); - } - assert!(&[0, 1, 2].iter().eq(vec.iter())); - } - - #[test] - fn into_iter() { - let mut vec = SmallVec::<_, 3>::new(); - for i in 0..3 { - vec.push(i); - } - assert!([0, 1, 2].into_iter().eq(vec.into_iter())); - } -} diff --git a/skrifa/src/color/traversal.rs b/skrifa/src/color/traversal.rs index 407941e7a..114026ffe 100644 --- a/skrifa/src/color/traversal.rs +++ b/skrifa/src/color/traversal.rs @@ -30,7 +30,7 @@ pub(crate) type PaintDecycler = Decycler; // const MAX_INLINE_COLOR_STOPS: usize = 32; -pub(crate) type ColorStopVec = crate::collections::SmallVec; +pub(crate) type ColorStopVec = smallvec::SmallVec<[ColorStop; MAX_INLINE_COLOR_STOPS]>; impl From for PaintError { fn from(value: DecyclerError) -> Self { diff --git a/skrifa/src/instance.rs b/skrifa/src/instance.rs index be5a7d098..a96d556ce 100644 --- a/skrifa/src/instance.rs +++ b/skrifa/src/instance.rs @@ -2,7 +2,7 @@ use read_fonts::types::Fixed; -use crate::collections::SmallVec; +use smallvec::SmallVec; /// Type for a normalized variation coordinate. pub type NormalizedCoord = read_fonts::types::F2Dot14; @@ -165,7 +165,7 @@ const MAX_INLINE_COORDS: usize = 8; /// type for more detail. #[derive(Clone, Debug, Hash, Eq, PartialEq, Default)] pub struct Location { - coords: SmallVec, + coords: SmallVec<[NormalizedCoord; MAX_INLINE_COORDS]>, } impl Location { @@ -174,7 +174,7 @@ impl Location { /// 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()), + coords: SmallVec::from_elem(NormalizedCoord::default(), len), } } diff --git a/skrifa/src/lib.rs b/skrifa/src/lib.rs index 8fb8852c5..7f67e7de6 100644 --- a/skrifa/src/lib.rs +++ b/skrifa/src/lib.rs @@ -40,7 +40,6 @@ pub mod outline; pub mod setting; pub mod string; -mod collections; mod decycler; mod glyph_name; mod provider; diff --git a/skrifa/src/outline/autohint/metrics/blues.rs b/skrifa/src/outline/autohint/metrics/blues.rs index b1b0c8e94..d12e0a7d8 100644 --- a/skrifa/src/outline/autohint/metrics/blues.rs +++ b/skrifa/src/outline/autohint/metrics/blues.rs @@ -8,9 +8,10 @@ use super::{ }, ScaledWidth, }; -use crate::{collections::SmallVec, FontRef, MetadataProvider}; +use crate::{FontRef, MetadataProvider}; use raw::types::F2Dot14; use raw::TableProvider; +use smallvec::SmallVec; /// Maximum number of blue values. /// @@ -151,7 +152,7 @@ pub struct UnscaledBlue { pub zones: BlueZones, } -pub(crate) type UnscaledBlues = SmallVec; +pub(crate) type UnscaledBlues = SmallVec<[UnscaledBlue; MAX_BLUES]>; /// A scaled alignment zone. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] @@ -166,7 +167,7 @@ pub struct ScaledBlue { pub is_active: bool, } -pub(crate) type ScaledBlues = SmallVec; +pub(crate) type ScaledBlues = SmallVec<[ScaledBlue; MAX_BLUES]>; /// Compute unscaled blues values for each axis. pub(crate) fn compute_unscaled_blues( diff --git a/skrifa/src/outline/autohint/metrics/mod.rs b/skrifa/src/outline/autohint/metrics/mod.rs index fdffe04cb..9f8900148 100644 --- a/skrifa/src/outline/autohint/metrics/mod.rs +++ b/skrifa/src/outline/autohint/metrics/mod.rs @@ -11,9 +11,10 @@ use super::{ topo::Dimension, QuirksMode, }; -use crate::{attribute::Style, collections::SmallVec, FontRef}; +use crate::{attribute::Style, FontRef}; use alloc::vec::Vec; use raw::types::{F2Dot14, Fixed, GlyphId}; +use smallvec::SmallVec; #[cfg(feature = "std")] use std::sync::{Arc, RwLock}; @@ -233,7 +234,7 @@ pub struct WidthMetrics { pub is_extra_light: bool, } -pub(crate) type UnscaledWidths = SmallVec; +pub(crate) type UnscaledWidths = SmallVec<[i32; MAX_WIDTHS]>; /// A scaled stem width. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] @@ -244,7 +245,7 @@ pub struct ScaledWidth { pub fitted: i32, } -pub(crate) type ScaledWidths = SmallVec; +pub(crate) type ScaledWidths = SmallVec<[ScaledWidth; MAX_WIDTHS]>; /// Flags that define how scaling and hinting is applied. #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] diff --git a/skrifa/src/outline/autohint/outline.rs b/skrifa/src/outline/autohint/outline.rs index e23063db8..5b0a7f37b 100644 --- a/skrifa/src/outline/autohint/outline.rs +++ b/skrifa/src/outline/autohint/outline.rs @@ -10,12 +10,12 @@ use super::{ metrics::Scale, QuirksMode, }; -use crate::collections::SmallVec; use core::ops::Range; use raw::{ tables::glyf::{PointFlags, PointMarker}, types::{F26Dot6, F2Dot14, GlyphId}, }; +use smallvec::SmallVec; /// Hinting directions. /// @@ -157,8 +157,8 @@ const MAX_INLINE_CONTOURS: usize = 8; pub(crate) struct Outline { pub units_per_em: i32, pub orientation: Option, - pub points: SmallVec, - pub contours: SmallVec, + pub points: SmallVec<[Point; MAX_INLINE_POINTS]>, + pub contours: SmallVec<[Contour; MAX_INLINE_CONTOURS]>, pub advance: i32, } @@ -704,14 +704,14 @@ mod tests { let quirks = QuirksMode::Jit; outline .points - .resize_and_fill(u16::MAX as usize + 1, Point::default()); + .resize(u16::MAX as usize + 1, Point::default()); outline .contours - .resize_and_fill(u16::MAX as usize + 1, Contour::default()); + .resize(u16::MAX as usize + 1, Contour::default()); assert!(outline.analyze_and_validate(gid, quirks).is_ok()); outline .points - .resize_and_fill(u16::MAX as usize + 2, Point::default()); + .resize(u16::MAX as usize + 2, Point::default()); assert!(matches!( outline.analyze_and_validate(gid, quirks), Err(DrawError::TooManyPoints(err_gid)) if err_gid == gid @@ -719,7 +719,7 @@ mod tests { outline.points.clear(); outline .contours - .resize_and_fill(u16::MAX as usize + 2, Contour::default()); + .resize(u16::MAX as usize + 2, Contour::default()); assert!(matches!( outline.analyze_and_validate(gid, quirks), Err(DrawError::TooManyPoints(err_gid)) if err_gid == gid @@ -731,7 +731,7 @@ mod tests { let mut outline = Outline::default(); outline .points - .resize_and_fill(u16::MAX as usize + 1, Point::default()); + .resize(u16::MAX as usize + 1, Point::default()); outline.contours.push(Contour { first_ix: 0, last_ix: u16::MAX, diff --git a/skrifa/src/outline/autohint/shape.rs b/skrifa/src/outline/autohint/shape.rs index 9040673b9..5e1c267a9 100644 --- a/skrifa/src/outline/autohint/shape.rs +++ b/skrifa/src/outline/autohint/shape.rs @@ -1,7 +1,7 @@ //! Shaping support for autohinting. use super::style::{GlyphStyle, StyleClass}; -use crate::{charmap::Charmap, collections::SmallVec, FontRef, GlyphId, MetadataProvider}; +use crate::{charmap::Charmap, FontRef, GlyphId, MetadataProvider}; use core::ops::Range; use raw::{ tables::{ @@ -15,6 +15,7 @@ use raw::{ types::Tag, ReadError, TableProvider, }; +use smallvec::SmallVec; // To prevent infinite recursion in contextual lookups. Matches HB // @@ -61,7 +62,7 @@ const SHAPED_CLUSTER_INLINE_SIZE: usize = 16; /// Some of our input "characters" for metrics computations are actually /// multi-character [grapheme clusters](https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) /// that may expand to multiple glyphs. -pub(crate) type ShapedCluster = SmallVec; +pub(crate) type ShapedCluster = SmallVec<[ShapedGlyph; SHAPED_CLUSTER_INLINE_SIZE]>; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub(crate) enum ShaperCoverageKind { diff --git a/skrifa/src/outline/autohint/topo/mod.rs b/skrifa/src/outline/autohint/topo/mod.rs index 40aeb8c7c..9ad39a46a 100644 --- a/skrifa/src/outline/autohint/topo/mod.rs +++ b/skrifa/src/outline/autohint/topo/mod.rs @@ -7,7 +7,7 @@ use super::{ metrics::ScaledWidth, outline::{Direction, Orientation, Point}, }; -use crate::collections::SmallVec; +use smallvec::SmallVec; pub(crate) use edges::{compute_blue_edges, compute_edges}; pub(crate) use segments::{compute_segments, link_segments}; @@ -55,9 +55,9 @@ pub struct Axis { /// Depends on dimension and outline orientation. pub(crate) major_dir: Direction, /// Collection of segments for the axis. - pub(crate) segments: SmallVec, + pub(crate) segments: SmallVec<[Segment; MAX_INLINE_SEGMENTS]>, /// Collection of edges for the axis. - pub(crate) edges: SmallVec, + pub(crate) edges: SmallVec<[Edge; MAX_INLINE_EDGES]>, } impl Axis { diff --git a/skrifa/src/outline/unscaled.rs b/skrifa/src/outline/unscaled.rs index 0b7dbcb74..239ae265e 100644 --- a/skrifa/src/outline/unscaled.rs +++ b/skrifa/src/outline/unscaled.rs @@ -3,12 +3,12 @@ #![allow(dead_code)] use super::DrawError; -use crate::collections::SmallVec; use core::ops::Range; use raw::{ tables::glyf::PointFlags, types::{F26Dot6, Point}, }; +use smallvec::SmallVec; #[derive(Copy, Clone, Default, Debug)] pub(super) struct UnscaledPoint { @@ -50,7 +50,9 @@ pub(super) trait UnscaledOutlineSink { } // please can I have smallvec? -pub(super) struct UnscaledOutlineBuf(SmallVec); +pub(super) struct UnscaledOutlineBuf( + SmallVec<[UnscaledPoint; INLINE_CAP]>, +); impl UnscaledOutlineBuf { pub fn new() -> Self { diff --git a/skrifa/src/outline/varc/mod.rs b/skrifa/src/outline/varc/mod.rs index 5066947b7..d5f7a8194 100644 --- a/skrifa/src/outline/varc/mod.rs +++ b/skrifa/src/outline/varc/mod.rs @@ -12,9 +12,9 @@ use read_fonts::{ types::{F2Dot14, GlyphId, Matrix}, FontRef, ReadError, TableProvider, }; +use smallvec::SmallVec; use crate::{ - collections::SmallVec, instance::Size, outline::{cff, glyf, metrics::GlyphHMetrics, pen::PathStyle, DrawError, OutlinePen}, provider::MetadataProvider, @@ -27,12 +27,12 @@ use core_maths::CoreFloat; use super::OutlineKind; -type GlyphStack = SmallVec; -type CoordVec = SmallVec; -type AxisIndexVec = SmallVec; -type AxisValueVec = SmallVec; -type DeltaVec = SmallVec; -type ScalarCacheVec = SmallVec; +type GlyphStack = SmallVec<[GlyphId; 8]>; +type CoordVec = SmallVec<[F2Dot14; 64]>; +type AxisIndexVec = SmallVec<[u16; 64]>; +type AxisValueVec = SmallVec<[f32; 64]>; +type DeltaVec = SmallVec<[f32; 64]>; +type ScalarCacheVec = SmallVec<[f32; 128]>; type Affine = Matrix; struct Scratchpad { @@ -537,7 +537,10 @@ impl<'a> Outlines<'a> { out.clear(); return Ok(()); }; - out.resize_and_fill(count, 0.0); + // `packed` may yield fewer than `count` values, so every slot is set + // here rather than only the ones the loop below reaches. + out.clear(); + out.resize(count, 0.0); for (slot, value) in out.iter_mut().zip(packed.iter().by_ref().take(count)) { *slot = value as f32; } @@ -774,7 +777,7 @@ impl ScalarCache { fn new(count: usize) -> Self { Self { - values: ScalarCacheVec::with_len(count, Self::INVALID), + values: ScalarCacheVec::from_elem(Self::INVALID, count), } } @@ -790,7 +793,10 @@ impl ScalarCache { } fn expand_coords(out: &mut CoordVec, axis_count: usize, coords: &[F2Dot14]) { - out.resize_and_fill(axis_count, F2Dot14::ZERO); + // `coords` may be shorter than `axis_count`, so the tail has to be zeroed + // rather than left holding whatever the last call put there. + out.clear(); + out.resize(axis_count, F2Dot14::ZERO); for (slot, value) in out.iter_mut().zip(coords.iter().copied()) { *slot = value; } @@ -805,7 +811,10 @@ fn compute_tuple_deltas( cache: &mut ScalarCache, out: &mut DeltaVec, ) -> Result<(), ReadError> { - out.resize_and_fill(tuple_len, 0.0); + // Deltas accumulate into this, and it is reused across calls, so it starts + // at zero rather than at the previous call's values. + out.clear(); + out.resize(tuple_len, 0.0); if tuple_len == 0 || var_idx == NO_VARIATION_INDEX { return Ok(()); } @@ -1188,7 +1197,7 @@ mod tests { .unwrap_or(0); let mut coords = CoordVec::new(); - coords.resize_and_fill(outlines.axis_count, F2Dot14::ZERO); + coords.resize(outlines.axis_count, F2Dot14::ZERO); for (i, c) in coords.iter_mut().enumerate() { *c = match i % 4 { 0 => coord(0.5), diff --git a/skrifa/src/variation.rs b/skrifa/src/variation.rs index 3fe02baac..2550199c0 100644 --- a/skrifa/src/variation.rs +++ b/skrifa/src/variation.rs @@ -6,9 +6,9 @@ use read_fonts::{ types::{Fixed, Tag}, FontRef, TableProvider, }; +use smallvec::SmallVec; use crate::{ - collections::SmallVec, instance::{Location, NormalizedCoord}, setting::VariationSetting, string::StringId, @@ -239,7 +239,7 @@ impl<'a> AxisCollection<'a> { value: f32, present: bool, } - let mut results = SmallVec::<_, 8>::with_len(self.len(), Entry::default()); + let mut results = SmallVec::<[_; 8]>::from_elem(Entry::default(), self.len()); for (axis, result) in self.iter().zip(results.as_mut_slice()) { result.tag = axis.tag(); result.min = axis.min_value();