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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ This release has an [MSRV] of 1.88.
#### Fontique

- File system font scanning (used for system font enumeration on macOS and Android) now parses font files in parallel and reads only the metadata it needs instead of memory-mapping whole files. ([#3][] by [@nicoburns][])
- System font scans on macOS are now cached in `~/Library/Caches/fontique`, keyed by file modification time and size, so unchanged font files aren't re-parsed on subsequent runs. ([#4][] by [@nicoburns][])

### Fixed

Expand Down Expand Up @@ -599,6 +600,7 @@ This release has an [MSRV][] of 1.70.
[#212]: https://github.com/linebender/parley/pull/212
[#213]: https://github.com/linebender/parley/pull/213
[#3]: https://github.com/DioxusLabs/parley/pull/3
[#4]: https://github.com/DioxusLabs/parley/pull/4
[#215]: https://github.com/linebender/parley/pull/215
[#223]: https://github.com/linebender/parley/pull/223
[#224]: https://github.com/linebender/parley/pull/224
Expand Down
15 changes: 14 additions & 1 deletion fontique/src/backend/coretext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,20 @@ fn scan_system_fonts() -> Option<scan::ScannedCollection> {
return None;
}

Some(scan::ScannedCollection::from_paths(paths.iter(), 0))
Some(match scan_cache_path() {
Some(cache_path) => {
scan::ScannedCollection::from_paths_cached(paths.iter(), 0, &cache_path)
}
None => scan::ScannedCollection::from_paths(paths.iter(), 0),
})
}

/// Returns the path of the persistent font scan cache, which lets us avoid
/// re-parsing font files that haven't changed since the last scan.
fn scan_cache_path() -> Option<PathBuf> {
let mut path = std::env::home_dir()?;
path.push("Library/Caches/fontique/font-scan-cache.bin");
Some(path)
}

fn library_font_files() -> Vec<PathBuf> {
Expand Down
18 changes: 18 additions & 0 deletions fontique/src/charmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ impl CharmapIndex {
})
}

/// Decomposes the index into raw parts for serialization.
#[cfg(feature = "std")]
pub(crate) fn to_parts(self) -> (u32, bool, bool) {
(self.subtable_offset, self.is_symbol, self.is_mac_roman)
}

/// Recreates an index from raw parts produced by [`Self::to_parts`].
#[cfg(feature = "std")]
pub(crate) fn from_parts(
(subtable_offset, is_symbol, is_mac_roman): (u32, bool, bool),
) -> Self {
Self {
subtable_offset,
is_symbol,
is_mac_roman,
}
}

/// Creates a character map from the given font data.
pub fn charmap<'a>(&self, font_data: &'a [u8]) -> Option<Charmap<'a>> {
let subtable_data = font_data.get(self.subtable_offset as usize..)?;
Expand Down
66 changes: 49 additions & 17 deletions fontique/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use core::fmt;
use read_fonts::{FontRef, TableProvider as _, types::Tag};
use smallvec::SmallVec;

type AxisVec = SmallVec<[AxisInfo; 1]>;
pub(crate) type AxisVec = SmallVec<[AxisInfo; 1]>;

/// Representation of a single font in a family.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -213,30 +213,21 @@ impl FontInfo {
// a valid cmap so just bail here if we fail.
let charmap_index = CharmapIndex::new(font)?;
let (width, style, weight) = read_attributes(font);
let (axes, attr_axes) = if let Ok(fvar_axes) = font.fvar().and_then(|fvar| fvar.axes()) {
let mut axes = SmallVec::<[AxisInfo; 1]>::with_capacity(fvar_axes.len());
let mut attrs_axes = 0_u8;
let axes = if let Ok(fvar_axes) = font.fvar().and_then(|fvar| fvar.axes()) {
let mut axes = AxisVec::with_capacity(fvar_axes.len());
for fvar_axis in fvar_axes {
let axis = AxisInfo {
axes.push(AxisInfo {
tag: fvar_axis.axis_tag(),
min: fvar_axis.min_value().to_f32(),
max: fvar_axis.max_value().to_f32(),
default: fvar_axis.default_value().to_f32(),
};
axes.push(axis);
match &axis.tag.to_be_bytes() {
b"wght" => attrs_axes |= WEIGHT_AXIS,
b"wdth" => attrs_axes |= WIDTH_AXIS,
b"slnt" => attrs_axes |= SLANT_AXIS,
b"ital" => attrs_axes |= ITALIC_AXIS,
b"opsz" => attrs_axes |= OPTICAL_SIZE_AXIS,
_ => {}
}
});
}
(axes, attrs_axes)
axes
} else {
(SmallVec::default(), 0)
AxisVec::default()
};
let attr_axes = attr_axes_from_axes(&axes);
Some(Self {
source,
index,
Expand All @@ -249,6 +240,32 @@ impl FontInfo {
})
}

/// Recreates a font from parts previously obtained via the public
/// accessors, for example when loading from a scan cache.
#[cfg(feature = "std")]
pub(crate) fn from_parts(
source: SourceInfo,
index: u32,
width: FontWidth,
style: FontStyle,
weight: FontWeight,
axes: impl Into<AxisVec>,
charmap_index: CharmapIndex,
) -> Self {
let axes = axes.into();
let attr_axes = attr_axes_from_axes(&axes);
Self {
source,
index,
width,
style,
weight,
axes,
attr_axes,
charmap_index,
}
}

#[allow(unused)]
pub(crate) fn maybe_override_attributes(
&mut self,
Expand Down Expand Up @@ -289,6 +306,21 @@ impl FontInfo {
}
}

fn attr_axes_from_axes(axes: &[AxisInfo]) -> u8 {
let mut attr_axes = 0_u8;
for axis in axes {
match &axis.tag.to_be_bytes() {
b"wght" => attr_axes |= WEIGHT_AXIS,
b"wdth" => attr_axes |= WIDTH_AXIS,
b"slnt" => attr_axes |= SLANT_AXIS,
b"ital" => attr_axes |= ITALIC_AXIS,
b"opsz" => attr_axes |= OPTICAL_SIZE_AXIS,
_ => {}
}
}
attr_axes
}

const WEIGHT_AXIS: u8 = 0x01;
const WIDTH_AXIS: u8 = 0x02;
const SLANT_AXIS: u8 = 0x04;
Expand Down
2 changes: 2 additions & 0 deletions fontique/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ mod generic;
mod impl_fontconfig;
mod matching;
mod scan;
#[cfg(feature = "std")]
mod scan_cache;
mod script;
mod source;

Expand Down
Loading