use crate::text::{Font, FontCollection};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Id(usize);
#[derive(Debug)]
pub struct Map {
next_index: usize,
map: HashMap<Id, Font>,
}
pub struct NewIds {
index_range: std::ops::Range<usize>,
}
#[derive(Clone)]
pub struct Ids<'a> {
keys: std::collections::hash_map::Keys<'a, Id, Font>,
}
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
NoFont,
}
pub const DEFAULT_DIRECTORY_NAME: &str = "fonts";
impl Id {
pub fn index(self) -> usize {
self.0
}
}
impl Map {
pub fn new() -> Self {
Map {
next_index: 0,
map: HashMap::default(),
}
}
pub fn get(&self, id: Id) -> Option<&Font> {
self.map.get(&id)
}
pub fn insert(&mut self, font: Font) -> Id {
let index = self.next_index;
self.next_index = index.wrapping_add(1);
let id = Id(index);
self.map.insert(id, font);
id
}
pub fn insert_from_file<P>(&mut self, path: P) -> Result<Id, Error>
where
P: AsRef<std::path::Path>,
{
let font = from_file(path)?;
Ok(self.insert(font))
}
pub fn ids(&self) -> Ids {
Ids {
keys: self.map.keys(),
}
}
}
pub fn id(font: &Font) -> Id {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
for name in font.font_name_strings() {
name.hash(&mut hasher);
}
Id((hasher.finish() % std::usize::MAX as u64) as usize)
}
pub fn collection_from_file<P>(path: P) -> Result<FontCollection, std::io::Error>
where
P: AsRef<std::path::Path>,
{
use std::io::Read;
let path = path.as_ref();
let mut file = std::fs::File::open(path)?;
let mut file_buffer = Vec::new();
file.read_to_end(&mut file_buffer)?;
Ok(FontCollection::from_bytes(file_buffer)?)
}
pub fn from_file<P>(path: P) -> Result<Font, Error>
where
P: AsRef<std::path::Path>,
{
let collection = collection_from_file(path)?;
collection.into_font().or(Err(Error::NoFont))
}
#[cfg(feature = "notosans")]
pub fn default_notosans() -> Font {
let collection = FontCollection::from_bytes(notosans::REGULAR_TTF)
.expect("failed to load the `notosans::REGULAR_TTF` font collection");
collection
.into_font()
.expect("the `notosans::REGULAR_TTF` font collection contained no fonts")
}
pub fn default_directory(assets: &Path) -> PathBuf {
assets.join(DEFAULT_DIRECTORY_NAME)
}
#[allow(unreachable_code, unused_variables)]
pub fn default(assets: &Path) -> Result<Font, Error> {
#[cfg(feature = "notosans")]
{
return Ok(default_notosans());
}
let fonts_dir = default_directory(assets);
if fonts_dir.exists() && fonts_dir.is_dir() {
for res in crate::io::walk_dir(&fonts_dir) {
let entry = match res {
Ok(e) => e,
Err(_) => continue,
};
match from_file(entry.path()) {
Err(_) => continue,
Ok(font) => return Ok(font),
}
}
}
Err(Error::NoFont)
}
impl Iterator for NewIds {
type Item = Id;
fn next(&mut self) -> Option<Self::Item> {
self.index_range.next().map(|i| Id(i))
}
}
impl<'a> Iterator for Ids<'a> {
type Item = Id;
fn next(&mut self) -> Option<Self::Item> {
self.keys.next().map(|&id| id)
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
impl std::error::Error for Error {
fn cause(&self) -> Option<&dyn std::error::Error> {
match *self {
Error::Io(ref e) => Some(e),
Error::NoFont => None,
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match *self {
Error::Io(ref e) => std::fmt::Display::fmt(e, f),
Error::NoFont => write!(f, "No `Font` found in the loaded `FontCollection`."),
}
}
}