Skip to content

Commit

Permalink
Get as close as possible to full no_std support (linebender#160)
Browse files Browse the repository at this point in the history
To really get no-std support in parley, we need to land dfrg/swash#63.
But I believe I've done everything else that's necessary, in both parley
and fontique. One can test the build by patching Cargo.toml to use the
working branch for that swash PR, and modifying parley/Cargo.toml to
specify swash/std or swash/libm as appropriate. If we decide to land
dfrg/swash#63 before landing this one, then I can make the latter
modification to parley/Cargo.toml before merging this PR.

Some of my changes, e.g. unconditionally using `alloc::vec::Vec`, are
designed to minimize the amount of noise when searching the whole source
tree for "std".
  • Loading branch information
mwcampbell authored Nov 7, 2024
1 parent 1a1be17 commit 824b9ba
Show file tree
Hide file tree
Showing 24 changed files with 99 additions and 108 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions fontique/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@

//! Properties for specifying font weight, stretch and style.
// This is unused due to an issue in CI and the fact that our
// no_std support doesn't really work correctly yet.
// See https://github.com/linebender/parley/issues/86
#[cfg(not(feature = "std"))]
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use core_maths::*;

Expand Down
2 changes: 0 additions & 2 deletions fontique/src/collection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ mod query;

pub use query::{Query, QueryFamily, QueryFont, QueryStatus};

#[cfg(feature = "std")]
use super::SourceCache;

use super::{
Expand Down Expand Up @@ -166,7 +165,6 @@ impl Collection {
}

/// Returns an object for selecting fonts from this collection.
#[cfg(feature = "std")]
pub fn query<'a>(&'a mut self, source_cache: &'a mut SourceCache) -> Query<'a> {
Query::new(self, source_cache)
}
Expand Down
6 changes: 0 additions & 6 deletions fontique/src/collection/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

//! Query support.
#[cfg(feature = "std")]
use super::super::{Collection, SourceCache};

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use super::{
Expand All @@ -33,14 +31,12 @@ impl QueryState {
pub struct Query<'a> {
collection: &'a mut Inner,
state: &'a mut QueryState,
#[cfg(feature = "std")]
source_cache: &'a mut SourceCache,
attributes: Attributes,
fallbacks: Option<FallbackKey>,
}

impl<'a> Query<'a> {
#[cfg(feature = "std")]
pub(super) fn new(collection: &'a mut Collection, source_cache: &'a mut SourceCache) -> Self {
collection.query_state.clear();
Self {
Expand Down Expand Up @@ -108,7 +104,6 @@ impl<'a> Query<'a> {
///
/// Return [`QueryStatus::Stop`] to end iterating over the matching
/// fonts or [`QueryStatus::Continue`] to continue iterating.
#[cfg(feature = "std")]
pub fn matches_with(&mut self, mut f: impl FnMut(&QueryFont) -> QueryStatus) {
for family in self
.state
Expand Down Expand Up @@ -226,7 +221,6 @@ pub struct QueryFont {
pub synthesis: Synthesis,
}

#[cfg(feature = "std")]
fn load_font<'a>(
family: &FamilyInfo,
attributes: Attributes,
Expand Down
3 changes: 1 addition & 2 deletions fontique/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use super::attributes::{Stretch, Style, Weight};
use super::source::{SourceInfo, SourceKind};
#[cfg(feature = "std")]
use super::{source_cache::SourceCache, Blob};
use skrifa::raw::{types::Tag, FontRef, TableProvider as _};
use smallvec::SmallVec;
Expand Down Expand Up @@ -54,13 +53,13 @@ impl FontInfo {
}

/// Attempts to load the font, optionally from a source cache.
#[cfg(feature = "std")]
pub fn load(&self, source_cache: Option<&mut SourceCache>) -> Option<Blob<u8>> {
if let Some(source_cache) = source_cache {
source_cache.get(&self.source)
} else {
match &self.source.kind {
SourceKind::Memory(blob) => Some(blob.clone()),
#[cfg(feature = "std")]
SourceKind::Path(path) => super::source_cache::load_blob(path),
}
}
Expand Down
6 changes: 1 addition & 5 deletions fontique/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
//! Font enumeration and fallback.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
// TODO: Remove this dead code allowance and hide the offending code behind the std feature gate.
#![cfg_attr(not(feature = "std"), allow(dead_code))]
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(any(feature = "std", feature = "libm")))]
compile_error!("fontique requires either the `std` or `libm` feature to be enabled");
Expand All @@ -26,7 +24,6 @@ mod scan;
mod script;
mod source;

#[cfg(feature = "std")]
mod source_cache;

pub use icu_locid::LanguageIdentifier as Language;
Expand All @@ -41,5 +38,4 @@ pub use generic::GenericFamily;
pub use script::Script;
pub use source::{SourceId, SourceInfo, SourceKind};

#[cfg(feature = "std")]
pub use source_cache::{SourceCache, SourceCacheOptions};
1 change: 0 additions & 1 deletion fontique/src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use smallvec::SmallVec;
#[cfg(feature = "std")]
use {super::source::SourcePathMap, std::path::Path};

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

#[cfg(feature = "std")]
Expand Down
128 changes: 76 additions & 52 deletions fontique/src/source_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@

//! Cache for font data.
use super::source::{SourceId, SourceInfo, SourceKind};
#[cfg(feature = "std")]
use super::source::SourceId;
use super::source::{SourceInfo, SourceKind};
#[cfg(feature = "std")]
use hashbrown::HashMap;
use peniko::{Blob, WeakBlob};
use peniko::Blob;
#[cfg(feature = "std")]
use peniko::WeakBlob;
#[cfg(feature = "std")]
use std::{
path::Path,
sync::{Arc, Mutex},
Expand All @@ -16,6 +22,7 @@ use std::{
/// [source cache]: SourceCache
#[derive(Copy, Clone, Default, Debug)]
pub struct SourceCacheOptions {
#[cfg(feature = "std")]
/// If true, the source cache will use a secondary shared cache
/// guaranteeing that all clones will use the same backing store.
///
Expand All @@ -29,25 +36,29 @@ pub struct SourceCacheOptions {
/// Cache for font data loaded from the file system.
#[derive(Clone, Default)]
pub struct SourceCache {
#[cfg(feature = "std")]
cache: HashMap<SourceId, Entry<Blob<u8>>>,
#[cfg(feature = "std")]
serial: u64,
#[cfg(feature = "std")]
shared: Option<Arc<Mutex<Shared>>>,
}

impl SourceCache {
/// Creates an empty cache with the given [options].
///
/// [options]: SourceCacheOptions
#[cfg_attr(not(feature = "std"), allow(unused))]
pub fn new(options: SourceCacheOptions) -> Self {
#[cfg(feature = "std")]
if options.shared {
Self {
return Self {
cache: Default::default(),
serial: 0,
shared: Some(Arc::new(Mutex::new(Shared::default()))),
}
} else {
Self::default()
};
}
Self::default()
}

/// Creates an empty cache that is suitable for multi-threaded use.
Expand All @@ -57,6 +68,7 @@ impl SourceCache {
///
/// This is the same as calling [`SourceCache::new`] with an options
/// struct where `shared = true`.
#[cfg(feature = "std")]
pub fn new_shared() -> Self {
Self {
cache: Default::default(),
Expand All @@ -72,71 +84,80 @@ impl SourceCache {
///
/// [blob]: Blob
pub fn get(&mut self, source: &SourceInfo) -> Option<Blob<u8>> {
let path = match &source.kind {
SourceKind::Memory(memory) => return Some(memory.clone()),
SourceKind::Path(path) => &**path,
};
use hashbrown::hash_map::Entry as HashEntry;
match self.cache.entry(source.id()) {
HashEntry::Vacant(vacant) => {
if let Some(mut shared) = self.shared.as_ref().and_then(|shared| shared.lock().ok())
{
// If we have a backing cache, try to load it there first
// and then propagate the result here.
if let Some(blob) = shared.get(source.id(), path) {
vacant.insert(Entry::Loaded(EntryData {
font_data: blob.clone(),
serial: self.serial,
}));
Some(blob)
} else {
vacant.insert(Entry::Failed);
None
}
} else {
// Otherwise, load it ourselves.
if let Some(blob) = load_blob(path) {
vacant.insert(Entry::Loaded(EntryData {
font_data: blob.clone(),
serial: self.serial,
}));
Some(blob)
} else {
vacant.insert(Entry::Failed);
None
match &source.kind {
SourceKind::Memory(memory) => Some(memory.clone()),
#[cfg(feature = "std")]
SourceKind::Path(path) => {
use hashbrown::hash_map::Entry as HashEntry;
match self.cache.entry(source.id()) {
HashEntry::Vacant(vacant) => {
if let Some(mut shared) =
self.shared.as_ref().and_then(|shared| shared.lock().ok())
{
// If we have a backing cache, try to load it there first
// and then propagate the result here.
if let Some(blob) = shared.get(source.id(), path) {
vacant.insert(Entry::Loaded(EntryData {
font_data: blob.clone(),
serial: self.serial,
}));
Some(blob)
} else {
vacant.insert(Entry::Failed);
None
}
} else {
// Otherwise, load it ourselves.
if let Some(blob) = load_blob(path) {
vacant.insert(Entry::Loaded(EntryData {
font_data: blob.clone(),
serial: self.serial,
}));
Some(blob)
} else {
vacant.insert(Entry::Failed);
None
}
}
}
}
}
HashEntry::Occupied(mut occupied) => {
let entry = occupied.get_mut();
match entry {
Entry::Loaded(data) => {
data.serial = self.serial;
Some(data.font_data.clone())
HashEntry::Occupied(mut occupied) => {
let entry = occupied.get_mut();
match entry {
Entry::Loaded(data) => {
data.serial = self.serial;
Some(data.font_data.clone())
}
Entry::Failed => None,
}
}
Entry::Failed => None,
}
}
}
}

/// Removes all cached blobs that have not been accessed in the last
/// `max_age` times `prune` has been called.
#[cfg_attr(not(feature = "std"), allow(unused))]
pub fn prune(&mut self, max_age: u64, prune_failed: bool) {
self.cache.retain(|_, entry| match entry {
Entry::Failed => !prune_failed,
Entry::Loaded(data) => self.serial.saturating_sub(data.serial) < max_age,
});
self.serial = self.serial.saturating_add(1);
#[cfg(feature = "std")]
{
self.cache.retain(|_, entry| match entry {
Entry::Failed => !prune_failed,
Entry::Loaded(data) => self.serial.saturating_sub(data.serial) < max_age,
});
self.serial = self.serial.saturating_add(1);
}
}
}

/// Shared backing store for a font data cache.
#[cfg(feature = "std")]
#[derive(Default)]
struct Shared {
cache: HashMap<SourceId, Entry<WeakBlob<u8>>>,
}

#[cfg(feature = "std")]
impl Shared {
pub fn get(&mut self, id: SourceId, path: &Path) -> Option<Blob<u8>> {
use hashbrown::hash_map::Entry as HashEntry;
Expand Down Expand Up @@ -177,19 +198,22 @@ impl Shared {
}
}

#[cfg(feature = "std")]
#[derive(Clone, Default)]
enum Entry<T> {
Loaded(EntryData<T>),
#[default]
Failed,
}

#[cfg(feature = "std")]
#[derive(Clone)]
struct EntryData<T> {
font_data: T,
serial: u64,
}

#[cfg(feature = "std")]
pub(crate) fn load_blob(path: &Path) -> Option<Blob<u8>> {
let file = std::fs::File::open(path).ok()?;
let mapped = unsafe { memmap2::Mmap::map(&file).ok()? };
Expand Down
3 changes: 2 additions & 1 deletion parley/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ workspace = true
[features]
default = ["system"]
std = ["fontique/std", "skrifa/std", "peniko/std"]
libm = ["fontique/libm", "skrifa/libm", "peniko/libm"]
libm = ["fontique/libm", "skrifa/libm", "peniko/libm", "dep:core_maths"]
# Enables support for system font backends
system = ["std", "fontique/system"]

Expand All @@ -27,3 +27,4 @@ swash = { workspace = true }
skrifa = { workspace = true }
peniko = { workspace = true }
fontique = { workspace = true }
core_maths = { version = "0.1.0", optional = true }
1 change: 0 additions & 1 deletion parley/src/bidi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

//! Unicode bidirectional algorithm.
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use swash::text::{BidiClass, BracketType, Codepoint as _};
Expand Down
Loading

0 comments on commit 824b9ba

Please sign in to comment.