Add FreeType face cache
This commit is contained in:
parent
14dc170caa
commit
84f57ac836
|
@ -113,7 +113,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
|
|||
let face = font.create_font_face();
|
||||
self.fonts.push(face);
|
||||
|
||||
Ok(FontKey { token: (self.fonts.len() - 1) as u16 })
|
||||
Ok(FontKey { token: (self.fonts.len() - 1) as u32 })
|
||||
}
|
||||
|
||||
fn get_glyph(&mut self, glyph: GlyphKey) -> Result<RasterizedGlyph, Error> {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
use std::cmp::{min, Ordering};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use freetype::freetype_sys;
|
||||
use freetype::tt_os2::TrueTypeOS2Table;
|
||||
use freetype::{self, Library};
|
||||
use freetype::{freetype_sys, Face as FTFace};
|
||||
use libc::c_uint;
|
||||
use log::{debug, trace};
|
||||
|
||||
|
@ -32,28 +34,21 @@ use super::{
|
|||
Style, Weight,
|
||||
};
|
||||
|
||||
struct FixedSize {
|
||||
pixelsize: f64,
|
||||
}
|
||||
|
||||
struct FallbackFont {
|
||||
pattern: Pattern,
|
||||
id: FontID,
|
||||
key: FontKey,
|
||||
}
|
||||
|
||||
impl FallbackFont {
|
||||
fn new(pattern: Pattern, id: FontID) -> FallbackFont {
|
||||
Self { pattern, id }
|
||||
fn new(pattern: Pattern, key: FontKey) -> FallbackFont {
|
||||
Self { pattern, key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
|
||||
struct FontID(u32);
|
||||
|
||||
impl FontID {
|
||||
fn new(lhs: PatternHash, rhs: PatternHash) -> Self {
|
||||
impl FontKey {
|
||||
fn from_pattern_hashes(lhs: PatternHash, rhs: PatternHash) -> Self {
|
||||
// XOR two hashes to get a font ID
|
||||
Self(lhs.0.rotate_left(1) ^ rhs.0)
|
||||
Self { token: lhs.0.rotate_left(1) ^ rhs.0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,22 +58,20 @@ struct FallbackList {
|
|||
coverage: CharSet,
|
||||
}
|
||||
|
||||
struct Face {
|
||||
ft_face: freetype::Face,
|
||||
key: FontKey,
|
||||
struct FaceLoadingProperties {
|
||||
load_flags: freetype::face::LoadFlag,
|
||||
render_mode: freetype::RenderMode,
|
||||
lcd_filter: c_uint,
|
||||
non_scalable: Option<FixedSize>,
|
||||
has_color: bool,
|
||||
pixelsize: f64,
|
||||
colored: bool,
|
||||
pixelsize_fixup_factor: Option<f64>,
|
||||
ft_face: Rc<FTFace>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Face {
|
||||
impl fmt::Debug for FaceLoadingProperties {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("Face")
|
||||
.field("ft_face", &self.ft_face)
|
||||
.field("key", &self.key)
|
||||
.field("load_flags", &self.load_flags)
|
||||
.field("render_mode", &match self.render_mode {
|
||||
freetype::RenderMode::Normal => "Normal",
|
||||
|
@ -95,9 +88,9 @@ impl fmt::Debug for Face {
|
|||
|
||||
/// Rasterizes glyphs for a single font face.
|
||||
pub struct FreeTypeRasterizer {
|
||||
faces: HashMap<FontKey, Face>,
|
||||
library: Library,
|
||||
keys: HashMap<FontID, FontKey>,
|
||||
faces: HashMap<FontKey, FaceLoadingProperties>,
|
||||
ft_faces: HashMap<PathBuf, Rc<FTFace>>,
|
||||
fallback_lists: HashMap<FontKey, FallbackList>,
|
||||
device_pixel_ratio: f32,
|
||||
}
|
||||
|
@ -115,7 +108,7 @@ impl Rasterize for FreeTypeRasterizer {
|
|||
|
||||
Ok(FreeTypeRasterizer {
|
||||
faces: HashMap::new(),
|
||||
keys: HashMap::new(),
|
||||
ft_faces: HashMap::new(),
|
||||
fallback_lists: HashMap::new(),
|
||||
library,
|
||||
device_pixel_ratio,
|
||||
|
@ -123,8 +116,8 @@ impl Rasterize for FreeTypeRasterizer {
|
|||
}
|
||||
|
||||
fn metrics(&self, key: FontKey, _size: Size) -> Result<Metrics, Error> {
|
||||
let face = self.faces.get(&key).ok_or(Error::FontNotLoaded)?;
|
||||
let full = self.full_metrics(key)?;
|
||||
let face = &mut self.faces.get(&key).ok_or(Error::FontNotLoaded)?;
|
||||
let full = self.full_metrics(&face)?;
|
||||
|
||||
let height = (full.size_metrics.height / 64) as f64;
|
||||
let descent = (full.size_metrics.descender / 64) as f32;
|
||||
|
@ -142,7 +135,7 @@ impl Rasterize for FreeTypeRasterizer {
|
|||
|
||||
// Get strikeout position and thickness in device pixels
|
||||
let (strikeout_position, strikeout_thickness) =
|
||||
match TrueTypeOS2Table::from_face(&mut face.ft_face.clone()) {
|
||||
match TrueTypeOS2Table::from_face(&mut (*face.ft_face).clone()) {
|
||||
Some(os2) => {
|
||||
let strikeout_position = f32::from(os2.y_strikeout_position()) * x_scale / 64.;
|
||||
let strikeout_thickness = f32::from(os2.y_strikeout_size()) * x_scale / 64.;
|
||||
|
@ -253,123 +246,107 @@ impl FreeTypeRasterizer {
|
|||
let primary_font = pattern.render_prepare(config, primary_font);
|
||||
|
||||
// Hash pattern together with request pattern to include requested font size in the hash
|
||||
let primary_font_id = FontID::new(hash, primary_font.hash());
|
||||
let primary_font_key = FontKey::from_pattern_hashes(hash, primary_font.hash());
|
||||
|
||||
// Reload already loaded faces and drop their fallback faces
|
||||
let font_key = if let Some(font_key) = self.keys.remove(&primary_font_id) {
|
||||
let fallback_list = self.fallback_lists.remove(&font_key).unwrap_or_default();
|
||||
// Return if we already have the same primary font
|
||||
if self.fallback_lists.contains_key(&primary_font_key) {
|
||||
return Ok(primary_font_key);
|
||||
}
|
||||
|
||||
for fallback_font in &fallback_list.list {
|
||||
if let Some(ff_key) = self.keys.get(&fallback_font.id) {
|
||||
// Skip primary fonts, since these are all reloaded later
|
||||
if !self.fallback_lists.contains_key(&ff_key) {
|
||||
self.faces.remove(ff_key);
|
||||
self.keys.remove(&fallback_font.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.faces.remove(&font_key);
|
||||
Some(font_key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Reuse the font_key, since changing it can break library users
|
||||
let font_key = self
|
||||
.face_from_pattern(&primary_font, primary_font_id, font_key)
|
||||
.and_then(|pattern| pattern.ok_or_else(|| Error::MissingFont(desc.to_owned())))?;
|
||||
// Load font if we haven't loaded it yet
|
||||
if !self.faces.contains_key(&primary_font_key) {
|
||||
self.face_from_pattern(&primary_font, primary_font_key)
|
||||
.and_then(|pattern| pattern.ok_or_else(|| Error::MissingFont(desc.to_owned())))?;
|
||||
}
|
||||
|
||||
// Coverage for fallback fonts
|
||||
let coverage = CharSet::new();
|
||||
let empty_charset = CharSet::new();
|
||||
|
||||
// Build fallback list
|
||||
let list: Vec<FallbackFont> = matched_fonts
|
||||
.map(|fallback_font| {
|
||||
let charset = fallback_font.get_charset().unwrap_or(&empty_charset);
|
||||
|
||||
// Use original pattern to preserve loading flags
|
||||
let fallback_font = pattern.render_prepare(config, fallback_font);
|
||||
let fallback_font_id = FontID::new(hash, fallback_font.hash());
|
||||
let fallback_font_key = FontKey::from_pattern_hashes(hash, fallback_font.hash());
|
||||
|
||||
let _ = coverage.merge(&charset);
|
||||
|
||||
FallbackFont::new(fallback_font, fallback_font_id)
|
||||
FallbackFont::new(fallback_font, fallback_font_key)
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.fallback_lists.insert(font_key, FallbackList { list, coverage });
|
||||
self.fallback_lists.insert(primary_font_key, FallbackList { list, coverage });
|
||||
|
||||
Ok(font_key)
|
||||
Ok(primary_font_key)
|
||||
}
|
||||
|
||||
fn full_metrics(&self, key: FontKey) -> Result<FullMetrics, Error> {
|
||||
let face = self.faces.get(&key).ok_or(Error::FontNotLoaded)?;
|
||||
fn full_metrics(&self, face_load_props: &FaceLoadingProperties) -> Result<FullMetrics, Error> {
|
||||
let ft_face = &face_load_props.ft_face;
|
||||
let size_metrics = ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?;
|
||||
|
||||
let size_metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?;
|
||||
|
||||
let width = match face.ft_face.load_char('0' as usize, face.load_flags) {
|
||||
Ok(_) => face.ft_face.glyph().metrics().horiAdvance / 64,
|
||||
let width = match ft_face.load_char('0' as usize, face_load_props.load_flags) {
|
||||
Ok(_) => ft_face.glyph().metrics().horiAdvance / 64,
|
||||
Err(_) => size_metrics.max_advance / 64,
|
||||
} as f64;
|
||||
|
||||
Ok(FullMetrics { size_metrics, cell_width: width })
|
||||
}
|
||||
|
||||
fn load_ft_face(&mut self, path: PathBuf, index: isize) -> Result<Rc<FTFace>, Error> {
|
||||
let mut ft_face = self.library.new_face(&path, index)?;
|
||||
if ft_face.has_color() {
|
||||
unsafe {
|
||||
// Select the colored bitmap size to use from the array of available sizes
|
||||
freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
let ft_face = Rc::new(ft_face);
|
||||
self.ft_faces.insert(path, Rc::clone(&ft_face));
|
||||
|
||||
Ok(ft_face)
|
||||
}
|
||||
|
||||
fn face_from_pattern(
|
||||
&mut self,
|
||||
pattern: &PatternRef,
|
||||
font_id: FontID,
|
||||
key: Option<FontKey>,
|
||||
font_key: FontKey,
|
||||
) -> Result<Option<FontKey>, Error> {
|
||||
if let (Some(path), Some(index)) = (pattern.file(0), pattern.index().next()) {
|
||||
if let Some(key) = self.keys.get(&font_id) {
|
||||
return Ok(Some(*key));
|
||||
if self.faces.get(&font_key).is_some() {
|
||||
return Ok(Some(font_key));
|
||||
}
|
||||
|
||||
trace!("Got font path={:?}", path);
|
||||
let mut ft_face = self.library.new_face(&path, index)?;
|
||||
|
||||
let ft_face = match self.ft_faces.get(&path) {
|
||||
Some(ft_face) => Rc::clone(ft_face),
|
||||
None => self.load_ft_face(path, index)?,
|
||||
};
|
||||
|
||||
// Get available pixel sizes if font isn't scalable.
|
||||
let non_scalable = if pattern.scalable().next().unwrap_or(true) {
|
||||
None
|
||||
} else {
|
||||
let mut pixelsize = pattern.pixelsize();
|
||||
debug!("pixelsizes: {:?}", pixelsize);
|
||||
|
||||
Some(FixedSize { pixelsize: pixelsize.next().expect("has 1+ pixelsize") })
|
||||
};
|
||||
let pixelsize =
|
||||
pattern.pixelsize().next().expect("Font is missing pixelsize information.");
|
||||
|
||||
let pixelsize_fixup_factor = pattern.pixelsizefixupfactor().next();
|
||||
|
||||
let has_color = ft_face.has_color();
|
||||
if has_color {
|
||||
unsafe {
|
||||
// Select the colored bitmap size to use from the array of available sizes
|
||||
freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the original fontkey if you're reloading the font
|
||||
let key = if let Some(key) = key { key } else { FontKey::next() };
|
||||
|
||||
let face = Face {
|
||||
ft_face,
|
||||
key,
|
||||
let face = FaceLoadingProperties {
|
||||
load_flags: Self::ft_load_flags(pattern),
|
||||
render_mode: Self::ft_render_mode(pattern),
|
||||
lcd_filter: Self::ft_lcd_filter(pattern),
|
||||
non_scalable,
|
||||
has_color,
|
||||
pixelsize,
|
||||
colored: ft_face.has_color(),
|
||||
pixelsize_fixup_factor,
|
||||
ft_face,
|
||||
};
|
||||
|
||||
debug!("Loaded Face {:?}", face);
|
||||
|
||||
let key = face.key;
|
||||
self.faces.insert(key, face);
|
||||
self.keys.insert(font_id, key);
|
||||
Ok(Some(key))
|
||||
self.faces.insert(font_key, face);
|
||||
|
||||
Ok(Some(font_key))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -396,20 +373,15 @@ impl FreeTypeRasterizer {
|
|||
}
|
||||
|
||||
for fallback_font in &fallback_list.list {
|
||||
let font_id = fallback_font.id;
|
||||
let font_key = fallback_font.key;
|
||||
let font_pattern = &fallback_font.pattern;
|
||||
match self.keys.get(&font_id) {
|
||||
Some(&key) => {
|
||||
let face = match self.faces.get(&key) {
|
||||
Some(face) => face,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
match self.faces.get(&font_key) {
|
||||
Some(face) => {
|
||||
let index = face.ft_face.get_char_index(glyph.c as usize);
|
||||
|
||||
// We found something in a current face, so let's use it
|
||||
if index != 0 {
|
||||
return Ok(key);
|
||||
return Ok(font_key);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
@ -418,7 +390,8 @@ impl FreeTypeRasterizer {
|
|||
}
|
||||
|
||||
let pattern = font_pattern.clone();
|
||||
let key = self.face_from_pattern(&pattern, font_id, None)?.unwrap();
|
||||
let key = self.face_from_pattern(&pattern, font_key)?.unwrap();
|
||||
|
||||
return Ok(key);
|
||||
},
|
||||
}
|
||||
|
@ -434,13 +407,8 @@ impl FreeTypeRasterizer {
|
|||
let face = &self.faces[&font_key];
|
||||
let index = face.ft_face.get_char_index(glyph_key.c as usize);
|
||||
|
||||
let size =
|
||||
face.non_scalable.as_ref().map(|v| v.pixelsize as f32).unwrap_or_else(|| {
|
||||
glyph_key.size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.
|
||||
});
|
||||
|
||||
if !face.has_color {
|
||||
face.ft_face.set_char_size(to_freetype_26_6(size), 0, 0, 0)?;
|
||||
if !face.colored {
|
||||
face.ft_face.set_char_size(to_freetype_26_6(face.pixelsize as f32), 0, 0, 0)?;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
|
@ -464,13 +432,13 @@ impl FreeTypeRasterizer {
|
|||
buf,
|
||||
};
|
||||
|
||||
if face.has_color {
|
||||
if face.colored {
|
||||
let fixup_factor = if let Some(pixelsize_fixup_factor) = face.pixelsize_fixup_factor {
|
||||
pixelsize_fixup_factor
|
||||
} else {
|
||||
// Fallback if user has bitmap scaling disabled
|
||||
let metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?;
|
||||
size as f64 / metrics.y_ppem as f64
|
||||
face.pixelsize / metrics.y_ppem as f64
|
||||
};
|
||||
Ok(downsample_bitmap(rasterized_glyph, fixup_factor))
|
||||
} else {
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
|
||||
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
|
@ -97,7 +96,7 @@ impl fmt::Display for FontDesc {
|
|||
/// Identifier for a Font for use in maps/etc
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct FontKey {
|
||||
token: u16,
|
||||
token: u32,
|
||||
}
|
||||
|
||||
impl FontKey {
|
||||
|
@ -111,39 +110,13 @@ impl FontKey {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct GlyphKey {
|
||||
pub c: char,
|
||||
pub font_key: FontKey,
|
||||
pub size: Size,
|
||||
}
|
||||
|
||||
impl Hash for GlyphKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
unsafe {
|
||||
// This transmute is fine:
|
||||
//
|
||||
// - If GlyphKey ever becomes a different size, this will fail to compile
|
||||
// - Result is being used for hashing and has no fields (it's a u64)
|
||||
::std::mem::transmute::<GlyphKey, u64>(*self)
|
||||
}
|
||||
.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for GlyphKey {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
// This transmute is fine:
|
||||
//
|
||||
// - If GlyphKey ever becomes a different size, this will fail to compile
|
||||
// - Result is being used for equality checking and has no fields (it's a u64)
|
||||
let other = ::std::mem::transmute::<GlyphKey, u64>(*other);
|
||||
::std::mem::transmute::<GlyphKey, u64>(*self).eq(&other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Font size stored as integer
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Size(i16);
|
||||
|
|
Loading…
Reference in New Issue