summaryrefslogtreecommitdiff
path: root/src/env
diff options
context:
space:
mode:
Diffstat (limited to 'src/env')
-rw-r--r--src/env/fs.rs215
-rw-r--r--src/env/image.rs47
-rw-r--r--src/env/mod.rs243
3 files changed, 0 insertions, 505 deletions
diff --git a/src/env/fs.rs b/src/env/fs.rs
deleted file mode 100644
index 969ee9e0..00000000
--- a/src/env/fs.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-use std::collections::{hash_map::Entry, HashMap};
-use std::fs::File;
-use std::io;
-use std::path::{Path, PathBuf};
-use std::rc::Rc;
-
-use memmap2::Mmap;
-use serde::{Deserialize, Serialize};
-use ttf_parser::{name_id, Face};
-use walkdir::WalkDir;
-
-use super::{Buffer, Loader};
-use crate::font::{FaceInfo, FontStretch, FontStyle, FontVariant, FontWeight};
-
-/// Loads fonts and images from the local file system.
-///
-/// _This is only available when the `fs` feature is enabled._
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct FsLoader {
- faces: Vec<FaceInfo>,
- files: Vec<PathBuf>,
- #[serde(skip)]
- cache: FileCache,
-}
-
-/// Maps from paths to loaded file buffers. When the buffer is `None` the file
-/// does not exist or couldn't be read.
-type FileCache = HashMap<PathBuf, Option<Buffer>>;
-
-impl FsLoader {
- /// Create a new loader without any fonts.
- pub fn new() -> Self {
- Self {
- faces: vec![],
- files: vec![],
- cache: HashMap::new(),
- }
- }
-
- /// Search for fonts in the operating system's font directories.
- #[cfg(all(unix, not(target_os = "macos")))]
- pub fn search_system(&mut self) {
- self.search_path("/usr/share/fonts");
- self.search_path("/usr/local/share/fonts");
-
- if let Some(dir) = dirs::font_dir() {
- self.search_path(dir);
- }
- }
-
- /// Search for fonts in the operating system's font directories.
- #[cfg(target_os = "macos")]
- pub fn search_system(&mut self) {
- self.search_path("/Library/Fonts");
- self.search_path("/Network/Library/Fonts");
- self.search_path("/System/Library/Fonts");
-
- if let Some(dir) = dirs::font_dir() {
- self.search_path(dir);
- }
- }
-
- /// Search for fonts in the operating system's font directories.
- #[cfg(windows)]
- pub fn search_system(&mut self) {
- let windir =
- std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string());
-
- self.search_path(Path::new(&windir).join("Fonts"));
-
- if let Some(roaming) = dirs::config_dir() {
- self.search_path(roaming.join("Microsoft\\Windows\\Fonts"));
- }
-
- if let Some(local) = dirs::cache_dir() {
- self.search_path(local.join("Microsoft\\Windows\\Fonts"));
- }
- }
-
- /// Search for all fonts at a path.
- ///
- /// 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)
- .follow_links(true)
- .sort_by(|a, b| a.file_name().cmp(b.file_name()))
- .into_iter()
- .filter_map(|e| e.ok());
-
- 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();
- }
- _ => {}
- }
- }
- }
- }
-
- /// Index the font faces in the file at the given path.
- ///
- /// 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<()> {
- 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()
- })
- }
-
- 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()),
- };
-
- // Merge with an existing entry for the same family name.
- self.faces.push(FaceInfo { family, variant, index });
- self.files.push(path.to_owned());
-
- Ok(())
- }
-
- /// Paths to font files, parallel to [`faces()`](Self::faces).
- pub fn files(&self) -> &[PathBuf] {
- &self.files
- }
-}
-
-impl Loader for FsLoader {
- fn faces(&self) -> &[FaceInfo] {
- &self.faces
- }
-
- fn load_face(&mut self, idx: usize) -> Option<Buffer> {
- load(&mut self.cache, &self.files[idx])
- }
-
- fn load_file(&mut self, path: &str) -> Option<Buffer> {
- load(&mut self.cache, Path::new(path))
- }
-}
-
-/// Load from the file system using a cache.
-fn load(cache: &mut FileCache, path: &Path) -> Option<Buffer> {
- match cache.entry(path.to_owned()) {
- Entry::Occupied(entry) => entry.get().clone(),
- Entry::Vacant(entry) => {
- let buffer = std::fs::read(path).ok().map(Rc::new);
- entry.insert(buffer).clone()
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_index_font_dir() {
- let mut loader = FsLoader::new();
- loader.search_path("fonts");
-
- assert_eq!(loader.files, &[
- Path::new("fonts/EBGaramond-Bold.ttf"),
- Path::new("fonts/EBGaramond-BoldItalic.ttf"),
- Path::new("fonts/EBGaramond-Italic.ttf"),
- Path::new("fonts/EBGaramond-Regular.ttf"),
- Path::new("fonts/Inconsolata-Bold.ttf"),
- Path::new("fonts/Inconsolata-Regular.ttf"),
- Path::new("fonts/LatinModernMath.otf"),
- Path::new("fonts/NotoSansArabic-Regular.ttf"),
- Path::new("fonts/NotoSerifCJKsc-Regular.otf"),
- Path::new("fonts/NotoSerifHebrew-Bold.ttf"),
- Path::new("fonts/NotoSerifHebrew-Regular.ttf"),
- Path::new("fonts/PTSans-Regular.ttf"),
- Path::new("fonts/TwitterColorEmoji.ttf"),
- ]);
- }
-}
diff --git a/src/env/image.rs b/src/env/image.rs
deleted file mode 100644
index 365ff312..00000000
--- a/src/env/image.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-use std::io::Cursor;
-
-use image::io::Reader as ImageReader;
-use image::{DynamicImage, GenericImageView, ImageFormat};
-
-/// A loaded image.
-pub struct Image {
- /// The original format the image was encoded in.
- pub format: ImageFormat,
- /// The decoded image.
- pub buf: DynamicImage,
-}
-
-impl Image {
- /// Parse an image from raw data in a supported format.
- ///
- /// The image format is determined automatically.
- pub fn parse(data: &[u8]) -> Option<Self> {
- let cursor = Cursor::new(data);
- let reader = ImageReader::new(cursor).with_guessed_format().ok()?;
- let format = reader.format()?;
- let buf = reader.decode().ok()?;
- Some(Self { format, buf })
- }
-
- /// The width of the image.
- pub fn width(&self) -> u32 {
- self.buf.width()
- }
-
- /// The height of the image.
- pub fn height(&self) -> u32 {
- self.buf.height()
- }
-}
-
-impl Debug for Image {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_struct("Image")
- .field("format", &self.format)
- .field("color", &self.buf.color())
- .field("width", &self.width())
- .field("height", &self.height())
- .finish()
- }
-}
diff --git a/src/env/mod.rs b/src/env/mod.rs
deleted file mode 100644
index 84be3e81..00000000
--- a/src/env/mod.rs
+++ /dev/null
@@ -1,243 +0,0 @@
-//! Font and image loading.
-
-#[cfg(feature = "fs")]
-mod fs;
-mod image;
-
-pub use self::image::*;
-#[cfg(feature = "fs")]
-pub use fs::*;
-
-use std::collections::{hash_map::Entry, HashMap};
-use std::rc::Rc;
-
-use serde::{Deserialize, Serialize};
-
-use crate::font::{Face, FaceInfo, FontVariant};
-
-/// Handles font and image loading.
-pub struct Env {
- /// The loader that serves the font face and file buffers.
- loader: Box<dyn Loader>,
- /// Faces indexed by [`FaceId`]. `None` if not yet loaded.
- faces: Vec<Option<Face>>,
- /// Maps a family name to the ids of all faces that are part of the family.
- families: HashMap<String, Vec<FaceId>>,
- /// Loaded images indexed by [`ImageId`].
- images: Vec<Image>,
- /// Maps from paths to loaded images.
- paths: HashMap<String, ImageId>,
- /// Callback for loaded font faces.
- on_face_load: Option<Box<dyn Fn(FaceId, &Face)>>,
- /// Callback for loaded images.
- on_image_load: Option<Box<dyn Fn(ImageId, &Image)>>,
-}
-
-impl Env {
- /// Create an environment from a `loader`.
- pub fn new(loader: impl Loader + 'static) -> Self {
- let infos = loader.faces();
-
- let mut faces = vec![];
- let mut families = HashMap::<String, Vec<FaceId>>::new();
-
- for (i, info) in infos.iter().enumerate() {
- let id = FaceId(i as u32);
- faces.push(None);
- families
- .entry(info.family.to_lowercase())
- .and_modify(|vec| vec.push(id))
- .or_insert_with(|| vec![id]);
- }
-
- Self {
- loader: Box::new(loader),
- faces,
- families,
- images: vec![],
- paths: HashMap::new(),
- on_face_load: None,
- on_image_load: None,
- }
- }
-
- /// Create an empty environment for testing purposes.
- pub fn blank() -> Self {
- struct BlankLoader;
-
- impl Loader for BlankLoader {
- fn faces(&self) -> &[FaceInfo] {
- &[]
- }
-
- fn load_face(&mut self, _: usize) -> Option<Buffer> {
- None
- }
-
- fn load_file(&mut self, _: &str) -> Option<Buffer> {
- None
- }
- }
-
- Self::new(BlankLoader)
- }
-
- /// Query for and load the font face from the given `family` that most
- /// closely matches the given `variant`.
- pub fn query_face(&mut self, family: &str, variant: FontVariant) -> Option<FaceId> {
- // Check whether a family with this name exists.
- let ids = self.families.get(family)?;
- let infos = self.loader.faces();
-
- let mut best = None;
- let mut best_key = None;
-
- // Find the best matching variant of this font.
- for &id in ids {
- let current = infos[id.0 as usize].variant;
-
- // This is a perfect match, no need to search further.
- if current == variant {
- best = Some(id);
- break;
- }
-
- // If this is not a perfect match, we compute a key that we want to
- // minimize among all variants. This key prioritizes style, then
- // stretch distance and then weight distance.
- let key = (
- current.style != variant.style,
- current.stretch.distance(variant.stretch),
- current.weight.distance(variant.weight),
- );
-
- if best_key.map_or(true, |b| key < b) {
- best = Some(id);
- best_key = Some(key);
- }
- }
-
- // Load the face if it's not already loaded.
- let id = best?;
- let idx = id.0 as usize;
- let slot = &mut self.faces[idx];
- if slot.is_none() {
- let index = infos[idx].index;
- let buffer = self.loader.load_face(idx)?;
- let face = Face::new(buffer, index)?;
- if let Some(callback) = &self.on_face_load {
- callback(id, &face);
- }
- *slot = Some(face);
- }
-
- best
- }
-
- /// Get a reference to a queried face.
- ///
- /// This panics if no face with this id was loaded. This function should
- /// only be called with ids returned by [`query_face()`](Self::query_face).
- #[track_caller]
- pub fn face(&self, id: FaceId) -> &Face {
- self.faces[id.0 as usize].as_ref().expect("font face was not loaded")
- }
-
- /// Register a callback which is invoked when a font face was loaded.
- pub fn on_face_load<F>(&mut self, f: F)
- where
- F: Fn(FaceId, &Face) + 'static,
- {
- self.on_face_load = Some(Box::new(f));
- }
-
- /// Load and decode an image file from a path.
- pub fn load_image(&mut self, path: &str) -> Option<ImageId> {
- Some(match self.paths.entry(path.to_string()) {
- Entry::Occupied(entry) => *entry.get(),
- Entry::Vacant(entry) => {
- let buffer = self.loader.load_file(path)?;
- let image = Image::parse(&buffer)?;
- let id = ImageId(self.images.len() as u32);
- if let Some(callback) = &self.on_image_load {
- callback(id, &image);
- }
- self.images.push(image);
- *entry.insert(id)
- }
- })
- }
-
- /// Get a reference to a loaded image.
- ///
- /// This panics if no image with this id was loaded. This function should
- /// only be called with ids returned by [`load_image()`](Self::load_image).
- #[track_caller]
- pub fn image(&self, id: ImageId) -> &Image {
- &self.images[id.0 as usize]
- }
-
- /// Register a callback which is invoked when an image was loaded.
- pub fn on_image_load<F>(&mut self, f: F)
- where
- F: Fn(ImageId, &Image) + 'static,
- {
- self.on_image_load = Some(Box::new(f));
- }
-}
-
-/// Loads fonts and images from a remote or local source.
-pub trait Loader {
- /// Descriptions of all font faces this loader serves.
- fn faces(&self) -> &[FaceInfo];
-
- /// Load the font face with the given index in [`faces()`](Self::faces).
- fn load_face(&mut self, idx: usize) -> Option<Buffer>;
-
- /// Load a file from a path.
- fn load_file(&mut self, path: &str) -> Option<Buffer>;
-}
-
-/// A shared byte buffer.
-pub type Buffer = Rc<Vec<u8>>;
-
-/// A unique identifier for a loaded font face.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
-pub struct FaceId(u32);
-
-impl FaceId {
- /// A blank initialization value.
- pub const MAX: Self = Self(u32::MAX);
-
- /// Create a face id from the raw underlying value.
- ///
- /// This should only be called with values returned by
- /// [`into_raw`](Self::into_raw).
- pub fn from_raw(v: u32) -> Self {
- Self(v)
- }
-
- /// Convert into the raw underlying value.
- pub fn into_raw(self) -> u32 {
- self.0
- }
-}
-
-/// A unique identifier for a loaded image.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
-pub struct ImageId(u32);
-
-impl ImageId {
- /// Create an image id from the raw underlying value.
- ///
- /// This should only be called with values returned by
- /// [`into_raw`](Self::into_raw).
- pub fn from_raw(v: u32) -> Self {
- Self(v)
- }
-
- /// Convert into the raw underlying value.
- pub fn into_raw(self) -> u32 {
- self.0
- }
-}