summaryrefslogtreecommitdiff
path: root/src/loading
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-18 14:23:48 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-18 14:25:49 +0200
commit011865ab5c8943abcb64c7b545e265d1a65db32a (patch)
treea6c45d99909e9800d825eb3bb1edc278d2a48e6b /src/loading
parent594809e35b9e768f1a50926cf5e7a9df41ba7d16 (diff)
Memory loader
Diffstat (limited to 'src/loading')
-rw-r--r--src/loading/fs.rs77
-rw-r--r--src/loading/mem.rs104
-rw-r--r--src/loading/mod.rs2
3 files changed, 122 insertions, 61 deletions
diff --git a/src/loading/fs.rs b/src/loading/fs.rs
index 8d905ed2..46e3dba1 100644
--- a/src/loading/fs.rs
+++ b/src/loading/fs.rs
@@ -5,13 +5,12 @@ use std::rc::Rc;
use memmap2::Mmap;
use same_file::Handle;
-use ttf_parser::{name_id, Face};
use walkdir::WalkDir;
use super::{FileHash, Loader};
-use crate::font::{FaceInfo, FontStretch, FontStyle, FontVariant, FontWeight};
+use crate::font::FaceInfo;
-/// Loads fonts and images from the local file system.
+/// Loads fonts and files from the local file system.
///
/// _This is only available when the `fs` feature is enabled._
#[derive(Debug, Default, Clone)]
@@ -25,13 +24,13 @@ impl FsLoader {
Self { faces: vec![] }
}
- /// Builder-style variant of `search_system`.
+ /// Builder-style variant of [`search_system`](Self::search_system).
pub fn with_system(mut self) -> Self {
self.search_system();
self
}
- /// Builder-style variant of `search_path`.
+ /// Builder-style variant of [`search_path`](Self::search_path).
pub fn with_path(mut self, dir: impl AsRef<Path>) -> Self {
self.search_path(dir);
self
@@ -89,8 +88,8 @@ impl FsLoader {
///
/// If the path is a directory, all contained fonts will be searched for
/// recursively.
- pub fn search_path(&mut self, dir: impl AsRef<Path>) {
- let walk = WalkDir::new(dir)
+ pub fn search_path(&mut self, path: impl AsRef<Path>) {
+ let walk = WalkDir::new(path)
.follow_links(true)
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
.into_iter()
@@ -99,13 +98,11 @@ impl FsLoader {
for entry in walk {
let path = entry.path();
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
- match ext {
- #[rustfmt::skip]
- "ttf" | "otf" | "TTF" | "OTF" |
- "ttc" | "otc" | "TTC" | "OTC" => {
- self.search_file(path).ok();
- }
- _ => {}
+ if matches!(
+ ext,
+ "ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC",
+ ) {
+ self.search_file(path);
}
}
}
@@ -115,56 +112,14 @@ impl FsLoader {
///
/// The file may form a font collection and contain multiple font faces,
/// which will then all be indexed.
- fn search_file(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
+ fn search_file(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
let path = path.strip_prefix(".").unwrap_or(path);
-
- let file = File::open(path)?;
- let mmap = unsafe { Mmap::map(&file)? };
-
- for i in 0 .. ttf_parser::fonts_in_collection(&mmap).unwrap_or(1) {
- let face = Face::from_slice(&mmap, i)
- .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
-
- self.parse_face(path, &face, i)?;
- }
-
- Ok(())
- }
-
- /// Parse a single face and insert it into the `families`. This either
- /// merges with an existing family entry if they have the same trimmed
- /// family name, or creates a new one.
- fn parse_face(&mut self, path: &Path, face: &Face<'_>, index: u32) -> io::Result<()> {
- fn find_name(face: &Face, name_id: u16) -> Option<String> {
- face.names().find_map(|entry| {
- (entry.name_id() == name_id).then(|| entry.to_string()).flatten()
- })
+ if let Ok(file) = File::open(path) {
+ if let Ok(mmap) = unsafe { Mmap::map(&file) } {
+ self.faces.extend(FaceInfo::parse(&path, &mmap));
+ }
}
-
- let family = find_name(face, name_id::TYPOGRAPHIC_FAMILY)
- .or_else(|| find_name(face, name_id::FAMILY))
- .ok_or("unknown font family")
- .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
-
- let variant = FontVariant {
- style: match (face.is_italic(), face.is_oblique()) {
- (false, false) => FontStyle::Normal,
- (true, _) => FontStyle::Italic,
- (_, true) => FontStyle::Oblique,
- },
- weight: FontWeight::from_number(face.weight().to_number()),
- stretch: FontStretch::from_number(face.width().to_number()),
- };
-
- self.faces.push(FaceInfo {
- path: path.to_owned(),
- index,
- family,
- variant,
- });
-
- Ok(())
}
}
diff --git a/src/loading/mem.rs b/src/loading/mem.rs
new file mode 100644
index 00000000..d7fb178f
--- /dev/null
+++ b/src/loading/mem.rs
@@ -0,0 +1,104 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+
+use super::{FileHash, Loader};
+use crate::font::FaceInfo;
+use crate::util::PathExt;
+
+/// Loads fonts and files from an in-memory storage.
+#[derive(Debug, Default, Clone)]
+pub struct MemLoader {
+ faces: Vec<FaceInfo>,
+ files: HashMap<PathBuf, Cow<'static, [u8]>>,
+}
+
+impl MemLoader {
+ /// Create a new from-memory loader.
+ pub fn new() -> Self {
+ Self { faces: vec![], files: HashMap::new() }
+ }
+
+ /// Builder-style variant of [`insert`](Self::insert).
+ pub fn with<P, D>(mut self, path: P, data: D) -> Self
+ where
+ P: AsRef<Path>,
+ D: Into<Cow<'static, [u8]>>,
+ {
+ self.insert(path, data);
+ self
+ }
+
+ /// Builder-style method to wrap the loader in an [`Rc`] to make it usable
+ /// with the [`Context`](crate::Context).
+ pub fn wrap(self) -> Rc<Self> {
+ Rc::new(self)
+ }
+
+ /// Insert a path-file mapping. If the data forms a font, then that font
+ /// will be available for layouting.
+ ///
+ /// The data can either be owned or referenced, but the latter only if its
+ /// lifetime is `'static`.
+ pub fn insert<P, D>(&mut self, path: P, data: D)
+ where
+ P: AsRef<Path>,
+ D: Into<Cow<'static, [u8]>>,
+ {
+ let path = path.as_ref().normalize();
+ let data = data.into();
+ self.faces.extend(FaceInfo::parse(&path, &data));
+ self.files.insert(path, data);
+ }
+}
+
+impl Loader for MemLoader {
+ fn faces(&self) -> &[FaceInfo] {
+ &self.faces
+ }
+
+ fn resolve(&self, path: &Path) -> io::Result<FileHash> {
+ let norm = path.normalize();
+ if self.files.contains_key(&norm) {
+ Ok(FileHash(fxhash::hash64(&norm)))
+ } else {
+ Err(io::ErrorKind::NotFound.into())
+ }
+ }
+
+ fn load(&self, path: &Path) -> io::Result<Vec<u8>> {
+ self.files
+ .get(&path.normalize())
+ .map(|cow| cow.clone().into_owned())
+ .ok_or_else(|| io::ErrorKind::NotFound.into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::font::FontVariant;
+
+ #[test]
+ fn test_recognize_and_load_font() {
+ let data = include_bytes!("../../fonts/PTSans-Regular.ttf");
+ let path = Path::new("PTSans.ttf");
+ let loader = MemLoader::new().with(path, &data[..]);
+
+ // Test that the found was found.
+ let info = &loader.faces[0];
+ assert_eq!(info.path, path);
+ assert_eq!(info.index, 0);
+ assert_eq!(info.family, "PT Sans");
+ assert_eq!(info.variant, FontVariant::default());
+ assert_eq!(loader.faces.len(), 1);
+
+ // Test that the file can be loaded.
+ assert_eq!(
+ loader.load(Path::new("directory/../PTSans.ttf")).unwrap(),
+ data
+ );
+ }
+}
diff --git a/src/loading/mod.rs b/src/loading/mod.rs
index 7d697310..e6cca012 100644
--- a/src/loading/mod.rs
+++ b/src/loading/mod.rs
@@ -2,9 +2,11 @@
#[cfg(feature = "fs")]
mod fs;
+mod mem;
#[cfg(feature = "fs")]
pub use fs::*;
+pub use mem::*;
use std::io;
use std::path::Path;