Use ab_glyph instead of rusttype for font rendering (#490)

* Use ab_glyph instead of rusttype for font rendering

* address review feedback
This commit is contained in:
Benjamin Bouvier 2021-06-24 12:13:57 +02:00 committed by GitHub
parent 63bddb67f8
commit e22c242d17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 54 deletions

View file

@ -19,7 +19,7 @@ Examples: `Vec2, Pos2, Rect, lerp, remap`
Example: `Shape::Circle { center, radius, fill, stroke }` Example: `Shape::Circle { center, radius, fill, stroke }`
Depends on `emath`, [`rusttype`](https://crates.io/crates/rusttype), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash). Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash).
### `epi` ### `epi`
Depends only on `egui`. Depends only on `egui`.

31
Cargo.lock generated
View file

@ -1,5 +1,15 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]]
name = "ab_glyph"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af0ac006645f86f20f6c6fa4dcaef920bf803df819123626f9440e35835e7d80"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser 0.12.0",
]
[[package]] [[package]]
name = "ab_glyph_rasterizer" name = "ab_glyph_rasterizer"
version = "0.1.4" version = "0.1.4"
@ -850,13 +860,13 @@ dependencies = [
name = "epaint" name = "epaint"
version = "0.12.0" version = "0.12.0"
dependencies = [ dependencies = [
"ab_glyph",
"ahash", "ahash",
"atomic_refcell", "atomic_refcell",
"cint", "cint",
"emath", "emath",
"ordered-float", "ordered-float",
"parking_lot", "parking_lot",
"rusttype",
"serde", "serde",
] ]
@ -1721,7 +1731,16 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3"
dependencies = [ dependencies = [
"ttf-parser", "ttf-parser 0.6.2",
]
[[package]]
name = "owned_ttf_parser"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3c7a20e3f122223e68eef6ca58e39bc1ea8a1d83418ba4c2c1ba189d2ee355"
dependencies = [
"ttf-parser 0.12.2",
] ]
[[package]] [[package]]
@ -2003,7 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59"
dependencies = [ dependencies = [
"ab_glyph_rasterizer", "ab_glyph_rasterizer",
"owned_ttf_parser", "owned_ttf_parser 0.6.0",
] ]
[[package]] [[package]]
@ -2336,6 +2355,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
[[package]]
name = "ttf-parser"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c56097738aec26a3f347edf99f5c84d9d4e3a4b8ce5513ebca85cb621fc7c50"
[[package]] [[package]]
name = "tts" name = "tts"
version = "0.16.0" version = "0.16.0"

View file

@ -79,7 +79,7 @@ ui.label(format!("Hello '{}', age {}", name, age));
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs) * Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
* Modular: You should be able to use small parts of egui and combine them in new ways * Modular: You should be able to use small parts of egui and combine them in new ways
* Safe: there is no `unsafe` code in egui * Safe: there is no `unsafe` code in egui
* Minimal dependencies: [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell) [`ordered-float`](https://crates.io/crates/ordered-float) [`rusttype`](https://crates.io/crates/rusttype). * Minimal dependencies: [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell) [`ordered-float`](https://crates.io/crates/ordered-float) [`ab_glyph`](https://crates.io/crates/ab_glyph).
egui is *not* a framework. egui is a library you call into, not an environment you program for. egui is *not* a framework. egui is a library you call into, not an environment you program for.

View file

@ -24,12 +24,12 @@ include = [
[dependencies] [dependencies]
emath = { version = "0.12.0", path = "../emath" } emath = { version = "0.12.0", path = "../emath" }
ab_glyph = "0.2.11"
ahash = { version = "0.7", features = ["std"], default-features = false } ahash = { version = "0.7", features = ["std"], default-features = false }
atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use.
cint = { version = "^0.2.2", optional = true } cint = { version = "^0.2.2", optional = true }
ordered-float = { version = "2", default-features = false } ordered-float = { version = "2", default-features = false }
parking_lot = { version = "0.11", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. parking_lot = { version = "0.11", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
rusttype = "0.9"
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
[features] [features]

View file

@ -1,10 +1,3 @@
use std::sync::Arc;
use {
ahash::AHashMap,
rusttype::{point, Scale},
};
use crate::{ use crate::{
mutex::{Mutex, RwLock}, mutex::{Mutex, RwLock},
text::{ text::{
@ -13,7 +6,9 @@ use crate::{
}, },
TextureAtlas, TextureAtlas,
}; };
use ahash::AHashMap;
use emath::{vec2, Vec2}; use emath::{vec2, Vec2};
use std::sync::Arc;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -32,7 +27,7 @@ pub struct UvRect {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct GlyphInfo { pub struct GlyphInfo {
id: rusttype::GlyphId, id: ab_glyph::GlyphId,
/// Unit: points. /// Unit: points.
pub advance_width: f32, pub advance_width: f32,
@ -44,7 +39,7 @@ pub struct GlyphInfo {
impl Default for GlyphInfo { impl Default for GlyphInfo {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: rusttype::GlyphId(0), id: ab_glyph::GlyphId(0),
advance_width: 0.0, advance_width: 0.0,
uv_rect: None, uv_rect: None,
} }
@ -56,7 +51,7 @@ impl Default for GlyphInfo {
/// A specific font with a size. /// A specific font with a size.
/// The interface uses points as the unit for everything. /// The interface uses points as the unit for everything.
pub struct FontImpl { pub struct FontImpl {
rusttype_font: Arc<rusttype::Font<'static>>, ab_glyph_font: ab_glyph::FontArc,
/// Maximum character height /// Maximum character height
scale_in_pixels: f32, scale_in_pixels: f32,
height_in_points: f32, height_in_points: f32,
@ -71,7 +66,7 @@ impl FontImpl {
pub fn new( pub fn new(
atlas: Arc<Mutex<TextureAtlas>>, atlas: Arc<Mutex<TextureAtlas>>,
pixels_per_point: f32, pixels_per_point: f32,
rusttype_font: Arc<rusttype::Font<'static>>, ab_glyph_font: ab_glyph::FontArc,
scale_in_points: f32, scale_in_points: f32,
y_offset: f32, y_offset: f32,
) -> FontImpl { ) -> FontImpl {
@ -96,7 +91,7 @@ impl FontImpl {
let y_offset = (y_offset * pixels_per_point).round() / pixels_per_point; let y_offset = (y_offset * pixels_per_point).round() / pixels_per_point;
Self { Self {
rusttype_font, ab_glyph_font,
scale_in_pixels, scale_in_pixels,
height_in_points, height_in_points,
y_offset, y_offset,
@ -115,8 +110,9 @@ impl FontImpl {
} }
// Add new character: // Add new character:
let glyph = self.rusttype_font.glyph(c); use ab_glyph::Font as _;
if glyph.id().0 == 0 { let glyph_id = self.ab_glyph_font.glyph_id(c);
if glyph_id.0 == 0 {
if invisible_char(c) { if invisible_char(c) {
// hack // hack
let glyph_info = GlyphInfo::default(); let glyph_info = GlyphInfo::default();
@ -128,7 +124,8 @@ impl FontImpl {
} else { } else {
let mut glyph_info = allocate_glyph( let mut glyph_info = allocate_glyph(
&mut self.atlas.lock(), &mut self.atlas.lock(),
glyph, &self.ab_glyph_font,
glyph_id,
self.scale_in_pixels, self.scale_in_pixels,
self.y_offset, self.y_offset,
self.pixels_per_point, self.pixels_per_point,
@ -147,12 +144,13 @@ impl FontImpl {
pub fn pair_kerning( pub fn pair_kerning(
&self, &self,
last_glyph_id: rusttype::GlyphId, last_glyph_id: ab_glyph::GlyphId,
glyph_id: rusttype::GlyphId, glyph_id: ab_glyph::GlyphId,
) -> f32 { ) -> f32 {
let scale_in_pixels = Scale::uniform(self.scale_in_pixels); use ab_glyph::{Font as _, ScaleFont};
self.rusttype_font self.ab_glyph_font
.pair_kerning(scale_in_pixels, last_glyph_id, glyph_id) .as_scaled(self.scale_in_pixels)
.kern(last_glyph_id, glyph_id)
/ self.pixels_per_point / self.pixels_per_point
} }
@ -618,20 +616,22 @@ fn invisible_char(c: char) -> bool {
fn allocate_glyph( fn allocate_glyph(
atlas: &mut TextureAtlas, atlas: &mut TextureAtlas,
glyph: rusttype::Glyph<'static>, font: &ab_glyph::FontArc,
glyph_id: ab_glyph::GlyphId,
scale_in_pixels: f32, scale_in_pixels: f32,
y_offset: f32, y_offset: f32,
pixels_per_point: f32, pixels_per_point: f32,
) -> GlyphInfo { ) -> GlyphInfo {
assert!(glyph.id().0 != 0); assert!(glyph_id.0 != 0);
use ab_glyph::{Font as _, ScaleFont};
let glyph = glyph.scaled(Scale::uniform(scale_in_pixels)); let glyph =
let glyph = glyph.positioned(point(0.0, 0.0)); glyph_id.with_scale_and_position(scale_in_pixels, ab_glyph::Point { x: 0.0, y: 0.0 });
let uv_rect = if let Some(bb) = glyph.pixel_bounding_box() { let uv_rect = font.outline_glyph(glyph).and_then(|glyph| {
let bb = glyph.px_bounds();
let glyph_width = bb.width() as usize; let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize; let glyph_height = bb.height() as usize;
if glyph_width == 0 || glyph_height == 0 { if glyph_width == 0 || glyph_height == 0 {
None None
} else { } else {
@ -658,15 +658,13 @@ fn allocate_glyph(
), ),
}) })
} }
} else { });
// No bounding box. Maybe a space?
None
};
let advance_width_in_points = glyph.unpositioned().h_metrics().advance_width / pixels_per_point; let advance_width_in_points =
font.as_scaled(scale_in_pixels).h_advance(glyph_id) / pixels_per_point;
GlyphInfo { GlyphInfo {
id: glyph.id(), id: glyph_id,
advance_width: advance_width_in_points, advance_width: advance_width_in_points,
uv_rect, uv_rect,
} }

View file

@ -61,12 +61,12 @@ pub enum FontFamily {
/// The data of a `.ttf` or `.otf` file. /// The data of a `.ttf` or `.otf` file.
pub type FontData = std::borrow::Cow<'static, [u8]>; pub type FontData = std::borrow::Cow<'static, [u8]>;
fn rusttype_font_from_font_data(name: &str, data: &FontData) -> rusttype::Font<'static> { fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontArc {
match data { match data {
std::borrow::Cow::Borrowed(bytes) => rusttype::Font::try_from_bytes(bytes), std::borrow::Cow::Borrowed(bytes) => ab_glyph::FontArc::try_from_slice(bytes),
std::borrow::Cow::Owned(bytes) => rusttype::Font::try_from_vec(bytes.clone()), std::borrow::Cow::Owned(bytes) => ab_glyph::FontArc::try_from_vec(bytes.clone()),
} }
.unwrap_or_else(|| panic!("Error parsing {:?} TTF/OTF font file", name)) .unwrap_or_else(|err| panic!("Error parsing {:?} TTF/OTF font file: {}", name, err))
} }
/// Describes the font data and the sizes to use. /// Describes the font data and the sizes to use.
@ -484,7 +484,7 @@ impl GalleyCache {
struct FontImplCache { struct FontImplCache {
atlas: Arc<Mutex<TextureAtlas>>, atlas: Arc<Mutex<TextureAtlas>>,
pixels_per_point: f32, pixels_per_point: f32,
rusttype_fonts: BTreeMap<String, Arc<rusttype::Font<'static>>>, ab_glyph_fonts: BTreeMap<String, ab_glyph::FontArc>,
/// Map font names and size to the cached `FontImpl`. /// Map font names and size to the cached `FontImpl`.
/// Can't have f32 in a HashMap or BTreeMap, so let's do a linear search /// Can't have f32 in a HashMap or BTreeMap, so let's do a linear search
@ -497,27 +497,22 @@ impl FontImplCache {
pixels_per_point: f32, pixels_per_point: f32,
definitions: &super::FontDefinitions, definitions: &super::FontDefinitions,
) -> Self { ) -> Self {
let rusttype_fonts = definitions let ab_glyph_fonts = definitions
.font_data .font_data
.iter() .iter()
.map(|(name, font_data)| { .map(|(name, font_data)| (name.clone(), ab_glyph_font_from_font_data(name, font_data)))
(
name.clone(),
Arc::new(rusttype_font_from_font_data(name, font_data)),
)
})
.collect(); .collect();
Self { Self {
atlas, atlas,
pixels_per_point, pixels_per_point,
rusttype_fonts, ab_glyph_fonts,
cache: Default::default(), cache: Default::default(),
} }
} }
pub fn rusttype_font(&self, font_name: &str) -> Arc<rusttype::Font<'static>> { pub fn ab_glyph_font(&self, font_name: &str) -> ab_glyph::FontArc {
self.rusttype_fonts self.ab_glyph_fonts
.get(font_name) .get(font_name)
.unwrap_or_else(|| panic!("No font data found for {:?}", font_name)) .unwrap_or_else(|| panic!("No font data found for {:?}", font_name))
.clone() .clone()
@ -546,7 +541,7 @@ impl FontImplCache {
let font_impl = Arc::new(FontImpl::new( let font_impl = Arc::new(FontImpl::new(
self.atlas.clone(), self.atlas.clone(),
self.pixels_per_point, self.pixels_per_point,
self.rusttype_font(font_name), self.ab_glyph_font(font_name),
scale_in_points, scale_in_points,
y_offset, y_offset,
)); ));