mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Make Font+FontImpl resolution and size independent
This commit is contained in:
@@ -641,7 +641,10 @@ impl ContextImpl {
|
||||
// Preload the most common characters for the most common fonts.
|
||||
// This is not very important to do, but may save a few GPU operations.
|
||||
for font_id in self.memory.options.style().text_styles.values() {
|
||||
fonts.fonts.font(font_id).preload_common_characters();
|
||||
fonts
|
||||
.fonts
|
||||
.font(&font_id.family)
|
||||
.preload_common_characters();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1585,7 +1588,7 @@ impl Context {
|
||||
|
||||
let font_id = TextStyle::Body.resolve(&self.style());
|
||||
self.fonts_mut(|f| {
|
||||
let font = f.fonts.font(&font_id);
|
||||
let font = f.fonts.font(&font_id.family);
|
||||
font.has_glyphs(alt)
|
||||
&& font.has_glyphs(ctrl)
|
||||
&& font.has_glyphs(shift)
|
||||
|
||||
@@ -77,7 +77,7 @@ impl crate::View for FontBook {
|
||||
let available_glyphs = self
|
||||
.available_glyphs
|
||||
.entry(self.font_id.family.clone())
|
||||
.or_insert_with(|| available_characters(ui, self.font_id.family.clone()));
|
||||
.or_insert_with(|| available_characters(ui, &self.font_id.family));
|
||||
|
||||
ui.separator();
|
||||
|
||||
@@ -140,10 +140,10 @@ fn char_info_ui(ui: &mut egui::Ui, chr: char, glyph_info: &GlyphInfo, font_id: e
|
||||
});
|
||||
}
|
||||
|
||||
fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap<char, GlyphInfo> {
|
||||
fn available_characters(ui: &egui::Ui, family: &egui::FontFamily) -> BTreeMap<char, GlyphInfo> {
|
||||
ui.fonts_mut(|f| {
|
||||
f.fonts
|
||||
.font(&egui::FontId::new(10.0, family)) // size is arbitrary for getting the characters
|
||||
.font(family)
|
||||
.characters()
|
||||
.iter()
|
||||
.filter(|(chr, _fonts)| !chr.is_whitespace() && !chr.is_ascii_control())
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use emath::{GuiRounding as _, Vec2, vec2};
|
||||
use ab_glyph::{Font as _, PxScaleFont, ScaleFont as _};
|
||||
use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
|
||||
|
||||
use crate::{
|
||||
TextureAtlas,
|
||||
@@ -34,7 +35,7 @@ impl UvRect {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct GlyphInfo {
|
||||
/// Used for pair-kerning.
|
||||
///
|
||||
@@ -42,24 +43,39 @@ pub struct GlyphInfo {
|
||||
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
|
||||
pub(crate) id: ab_glyph::GlyphId,
|
||||
|
||||
/// Unscaled.
|
||||
pub advance_width_unscaled: OrderedFloat<f32>,
|
||||
|
||||
/// Whether this glyph has any outlines.
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Default for GlyphInfo {
|
||||
/// Basically a zero-width space.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: ab_glyph::GlyphId(0),
|
||||
advance_width_unscaled: 0.0.into(),
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct GlyphAllocation {
|
||||
/// Used for pair-kerning.
|
||||
///
|
||||
/// Doesn't need to be unique.
|
||||
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
|
||||
pub(crate) id: ab_glyph::GlyphId,
|
||||
|
||||
/// Unit: points.
|
||||
pub advance_width: f32,
|
||||
|
||||
/// Texture coordinates.
|
||||
/// UV rectangle for drawing.
|
||||
pub uv_rect: UvRect,
|
||||
}
|
||||
|
||||
impl Default for GlyphInfo {
|
||||
/// Basically a zero-width space.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: ab_glyph::GlyphId(0),
|
||||
advance_width: 0.0,
|
||||
uv_rect: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A specific font with a size.
|
||||
@@ -67,72 +83,51 @@ impl Default for GlyphInfo {
|
||||
pub struct FontImpl {
|
||||
name: String,
|
||||
ab_glyph_font: ab_glyph::FontArc,
|
||||
|
||||
/// Maximum character height
|
||||
scale_in_pixels: u32,
|
||||
|
||||
height_in_points: f32,
|
||||
|
||||
// move each character by this much (hack)
|
||||
y_offset_in_points: f32,
|
||||
|
||||
ascent: f32,
|
||||
pixels_per_point: f32,
|
||||
tweak: FontTweak,
|
||||
glyph_info_cache: RwLock<ahash::HashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex
|
||||
glyph_alloc_cache: RwLock<ahash::HashMap<(GlyphInfo, OrderedFloat<f32>), GlyphAllocation>>, // TODO(emilk): standard Mutex
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
}
|
||||
|
||||
trait FontExt {
|
||||
fn pt_scaled(&self, scale: f32) -> PxScaleFont<&'_ Self>;
|
||||
|
||||
fn pt_scale_factor(&self, scale: f32) -> f32;
|
||||
}
|
||||
|
||||
impl<T> FontExt for T
|
||||
where
|
||||
T: ab_glyph::Font,
|
||||
{
|
||||
fn pt_scaled(&self, scale: f32) -> PxScaleFont<&'_ Self> {
|
||||
PxScaleFont {
|
||||
font: self,
|
||||
scale: self.pt_scale_factor(scale).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pt_scale_factor(&self, scale: f32) -> f32 {
|
||||
let units_per_em = self.units_per_em().unwrap_or_else(|| {
|
||||
panic!("The font unit size exceeds the expected range (16..=16384)")
|
||||
});
|
||||
let font_scaling = self.height_unscaled() / units_per_em;
|
||||
scale * font_scaling
|
||||
}
|
||||
}
|
||||
|
||||
impl FontImpl {
|
||||
pub fn new(
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
pixels_per_point: f32,
|
||||
name: String,
|
||||
ab_glyph_font: ab_glyph::FontArc,
|
||||
scale_in_pixels: f32,
|
||||
tweak: FontTweak,
|
||||
) -> Self {
|
||||
assert!(
|
||||
scale_in_pixels > 0.0,
|
||||
"scale_in_pixels is smaller than 0, got: {scale_in_pixels:?}"
|
||||
);
|
||||
assert!(
|
||||
pixels_per_point > 0.0,
|
||||
"pixels_per_point must be greater than 0, got: {pixels_per_point:?}"
|
||||
);
|
||||
|
||||
use ab_glyph::{Font as _, ScaleFont as _};
|
||||
let scaled = ab_glyph_font.as_scaled(scale_in_pixels);
|
||||
let ascent = (scaled.ascent() / pixels_per_point).round_ui();
|
||||
let descent = (scaled.descent() / pixels_per_point).round_ui();
|
||||
let line_gap = (scaled.line_gap() / pixels_per_point).round_ui();
|
||||
|
||||
// Tweak the scale as the user desired
|
||||
let scale_in_pixels = scale_in_pixels * tweak.scale;
|
||||
let scale_in_points = scale_in_pixels / pixels_per_point;
|
||||
|
||||
let y_offset_points =
|
||||
((scale_in_points * tweak.y_offset_factor) + tweak.y_offset).round_ui();
|
||||
|
||||
// Center scaled glyphs properly:
|
||||
let height = ascent + descent;
|
||||
let y_offset_points = y_offset_points - (1.0 - tweak.scale) * 0.5 * height;
|
||||
|
||||
// Round to an even number of physical pixels to get even kerning.
|
||||
// See https://github.com/emilk/egui/issues/382
|
||||
let scale_in_pixels = scale_in_pixels.round() as u32;
|
||||
|
||||
// Round to closest pixel:
|
||||
let y_offset_in_points = (y_offset_points * pixels_per_point).round() / pixels_per_point;
|
||||
|
||||
Self {
|
||||
name,
|
||||
ab_glyph_font,
|
||||
scale_in_pixels,
|
||||
height_in_points: ascent - descent + line_gap,
|
||||
y_offset_in_points,
|
||||
ascent,
|
||||
pixels_per_point,
|
||||
tweak,
|
||||
glyph_info_cache: Default::default(),
|
||||
glyph_alloc_cache: Default::default(),
|
||||
atlas,
|
||||
}
|
||||
}
|
||||
@@ -159,7 +154,6 @@ impl FontImpl {
|
||||
|
||||
/// An un-ordered iterator over all supported characters.
|
||||
fn characters(&self) -> impl Iterator<Item = char> + '_ {
|
||||
use ab_glyph::Font as _;
|
||||
self.ab_glyph_font
|
||||
.codepoint_ids()
|
||||
.map(|(_, chr)| chr)
|
||||
@@ -181,7 +175,9 @@ impl FontImpl {
|
||||
if c == '\t' {
|
||||
if let Some(space) = self.glyph_info(' ') {
|
||||
let glyph_info = GlyphInfo {
|
||||
advance_width: crate::text::TAB_SIZE as f32 * space.advance_width,
|
||||
advance_width_unscaled: (crate::text::TAB_SIZE as f32
|
||||
* space.advance_width_unscaled.0)
|
||||
.into(),
|
||||
..space
|
||||
};
|
||||
self.glyph_info_cache.write().insert(c, glyph_info);
|
||||
@@ -195,10 +191,10 @@ impl FontImpl {
|
||||
// https://en.wikipedia.org/wiki/Thin_space
|
||||
|
||||
if let Some(space) = self.glyph_info(' ') {
|
||||
let em = self.height_in_points; // TODO(emilk): is this right?
|
||||
let advance_width = f32::min(em / 6.0, space.advance_width * 0.5);
|
||||
let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0);
|
||||
let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5);
|
||||
let glyph_info = GlyphInfo {
|
||||
advance_width,
|
||||
advance_width_unscaled: advance_width.into(),
|
||||
..space
|
||||
};
|
||||
self.glyph_info_cache.write().insert(c, glyph_info);
|
||||
@@ -213,13 +209,16 @@ impl FontImpl {
|
||||
}
|
||||
|
||||
// Add new character:
|
||||
use ab_glyph::Font as _;
|
||||
let glyph_id = self.ab_glyph_font.glyph_id(c);
|
||||
|
||||
if glyph_id.0 == 0 {
|
||||
None // unsupported character
|
||||
} else {
|
||||
let glyph_info = self.allocate_glyph(glyph_id);
|
||||
let glyph_info = GlyphInfo {
|
||||
id: glyph_id,
|
||||
advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(),
|
||||
visible: true,
|
||||
};
|
||||
self.glyph_info_cache.write().insert(c, glyph_info);
|
||||
Some(glyph_info)
|
||||
}
|
||||
@@ -230,43 +229,80 @@ impl FontImpl {
|
||||
&self,
|
||||
last_glyph_id: ab_glyph::GlyphId,
|
||||
glyph_id: ab_glyph::GlyphId,
|
||||
font_size: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> f32 {
|
||||
use ab_glyph::{Font as _, ScaleFont as _};
|
||||
// Round to an even number of physical pixels to get even kerning.
|
||||
// See https://github.com/emilk/egui/issues/382
|
||||
self.ab_glyph_font
|
||||
.as_scaled(self.scale_in_pixels as f32)
|
||||
.pt_scaled((font_size * self.tweak.scale * pixels_per_point).round())
|
||||
.kern(last_glyph_id, glyph_id)
|
||||
/ self.pixels_per_point
|
||||
/ pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one row of text in points.
|
||||
///
|
||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||
#[inline(always)]
|
||||
pub fn row_height(&self) -> f32 {
|
||||
self.height_in_points
|
||||
}
|
||||
pub fn row_height(&self, font_size: f32) -> f32 {
|
||||
let font = self.ab_glyph_font.pt_scaled(font_size);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pixels_per_point(&self) -> f32 {
|
||||
self.pixels_per_point
|
||||
font.ascent().round_ui() - font.descent().round_ui() + font.line_gap().round_ui()
|
||||
}
|
||||
|
||||
/// This is the distance from the top to the baseline.
|
||||
///
|
||||
/// Unit: points.
|
||||
#[inline(always)]
|
||||
pub fn ascent(&self) -> f32 {
|
||||
self.ascent
|
||||
pub fn ascent(&self, font_size: f32) -> f32 {
|
||||
self.ab_glyph_font.pt_scaled(font_size).ascent().round_ui()
|
||||
}
|
||||
|
||||
fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo {
|
||||
assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
|
||||
use ab_glyph::{Font as _, ScaleFont as _};
|
||||
pub fn allocate_glyph(
|
||||
&self,
|
||||
glyph_info: GlyphInfo,
|
||||
font_size: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> GlyphAllocation {
|
||||
if !glyph_info.visible {
|
||||
return GlyphAllocation::default();
|
||||
}
|
||||
// Round to an even number of physical pixels to get even kerning.
|
||||
// See https://github.com/emilk/egui/issues/382
|
||||
let scale = self
|
||||
.ab_glyph_font
|
||||
.pt_scale_factor(font_size * self.tweak.scale * pixels_per_point)
|
||||
.round();
|
||||
let mut cache = self.glyph_alloc_cache.write();
|
||||
let entry = match cache.entry((glyph_info, scale.into())) {
|
||||
std::collections::hash_map::Entry::Occupied(glyph_alloc) => {
|
||||
return *glyph_alloc.get();
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(entry) => entry,
|
||||
};
|
||||
|
||||
let glyph = glyph_id.with_scale_and_position(
|
||||
self.scale_in_pixels as f32,
|
||||
ab_glyph::Point { x: 0.0, y: 0.0 },
|
||||
);
|
||||
assert!(glyph_info.id.0 != 0, "Can't allocate glyph for id 0");
|
||||
|
||||
let glyph = glyph_info
|
||||
.id
|
||||
.with_scale_and_position(scale, ab_glyph::Point { x: 0.0, y: 0.0 });
|
||||
|
||||
// Tweak the scale as the user desired
|
||||
let y_offset_in_points = {
|
||||
let logically_scaled = self.ab_glyph_font.pt_scaled(font_size * pixels_per_point);
|
||||
let scale_in_points = scale / pixels_per_point;
|
||||
|
||||
let y_offset_points =
|
||||
((scale_in_points * self.tweak.y_offset_factor) + self.tweak.y_offset).round_ui();
|
||||
|
||||
// Center scaled glyphs properly:
|
||||
let height = (logically_scaled.ascent() / pixels_per_point).round_ui()
|
||||
+ (logically_scaled.descent() / pixels_per_point).round_ui();
|
||||
let y_offset_points = y_offset_points - (1.0 - self.tweak.scale) * 0.5 * height;
|
||||
|
||||
// Round to closest pixel:
|
||||
(y_offset_points * pixels_per_point).round() / pixels_per_point
|
||||
};
|
||||
|
||||
let uv_rect = self.ab_glyph_font.outline_glyph(glyph).map(|glyph| {
|
||||
let bb = glyph.px_bounds();
|
||||
@@ -290,11 +326,10 @@ impl FontImpl {
|
||||
};
|
||||
|
||||
let offset_in_pixels = vec2(bb.min.x, bb.min.y);
|
||||
let offset =
|
||||
offset_in_pixels / self.pixels_per_point + self.y_offset_in_points * Vec2::Y;
|
||||
let offset = offset_in_pixels / pixels_per_point + y_offset_in_points * Vec2::Y;
|
||||
UvRect {
|
||||
offset,
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / self.pixels_per_point,
|
||||
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
|
||||
min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
|
||||
max: [
|
||||
(glyph_pos.0 + glyph_width) as u16,
|
||||
@@ -305,17 +340,15 @@ impl FontImpl {
|
||||
});
|
||||
let uv_rect = uv_rect.unwrap_or_default();
|
||||
|
||||
let advance_width_in_points = self
|
||||
.ab_glyph_font
|
||||
.as_scaled(self.scale_in_pixels as f32)
|
||||
.h_advance(glyph_id)
|
||||
/ self.pixels_per_point;
|
||||
|
||||
GlyphInfo {
|
||||
id: glyph_id,
|
||||
advance_width: advance_width_in_points,
|
||||
let allocation = GlyphAllocation {
|
||||
id: glyph_info.id,
|
||||
advance_width: (glyph_info.advance_width_unscaled.0 * scale
|
||||
/ self.ab_glyph_font.height_unscaled())
|
||||
/ pixels_per_point,
|
||||
uv_rect,
|
||||
}
|
||||
};
|
||||
entry.insert(allocation);
|
||||
allocation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,8 +363,6 @@ pub struct Font {
|
||||
characters: Option<BTreeMap<char, Vec<String>>>,
|
||||
|
||||
replacement_glyph: (FontIndex, GlyphInfo),
|
||||
pixels_per_point: f32,
|
||||
row_height: f32,
|
||||
glyph_info_cache: ahash::HashMap<char, (FontIndex, GlyphInfo)>,
|
||||
}
|
||||
|
||||
@@ -342,21 +373,14 @@ impl Font {
|
||||
fonts,
|
||||
characters: None,
|
||||
replacement_glyph: Default::default(),
|
||||
pixels_per_point: 1.0,
|
||||
row_height: 0.0,
|
||||
glyph_info_cache: Default::default(),
|
||||
};
|
||||
}
|
||||
|
||||
let pixels_per_point = fonts[0].pixels_per_point();
|
||||
let row_height = fonts[0].row_height();
|
||||
|
||||
let mut slf = Self {
|
||||
fonts,
|
||||
characters: None,
|
||||
replacement_glyph: Default::default(),
|
||||
pixels_per_point,
|
||||
row_height,
|
||||
glyph_info_cache: Default::default(),
|
||||
};
|
||||
|
||||
@@ -408,29 +432,20 @@ impl Font {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Height of one row of text. In points.
|
||||
///
|
||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||
#[inline(always)]
|
||||
pub fn row_height(&self) -> f32 {
|
||||
self.row_height
|
||||
}
|
||||
|
||||
pub fn uv_rect(&self, c: char) -> UvRect {
|
||||
self.glyph_info_cache
|
||||
.get(&c)
|
||||
.map(|gi| gi.1.uv_rect)
|
||||
.unwrap_or_default()
|
||||
pub fn row_height(&self, font_size: f32) -> f32 {
|
||||
self.fonts[0].row_height(font_size)
|
||||
}
|
||||
|
||||
/// Width of this character in points.
|
||||
pub fn glyph_width(&mut self, c: char) -> f32 {
|
||||
self.glyph_info(c).1.advance_width
|
||||
pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 {
|
||||
let (font_index, glyph_info) = self.glyph_info(c);
|
||||
let font = &self.fonts[font_index].ab_glyph_font;
|
||||
glyph_info.advance_width_unscaled.0 * font.pt_scale_factor(font_size)
|
||||
/ font.height_unscaled()
|
||||
}
|
||||
|
||||
/// Can we display this glyph?
|
||||
@@ -465,11 +480,27 @@ impl Font {
|
||||
(Some(font_impl), glyph_info)
|
||||
}
|
||||
|
||||
pub(crate) fn ascent(&self) -> f32 {
|
||||
#[inline]
|
||||
pub(crate) fn font_impl_and_glyph_alloc(
|
||||
&mut self,
|
||||
c: char,
|
||||
font_size: f32,
|
||||
pixels_per_point: f32,
|
||||
) -> (Option<&FontImpl>, GlyphAllocation) {
|
||||
if self.fonts.is_empty() {
|
||||
return (None, Default::default());
|
||||
}
|
||||
let (font_index, glyph_info) = self.glyph_info(c);
|
||||
let font_impl = &self.fonts[font_index];
|
||||
let allocated_glyph = font_impl.allocate_glyph(glyph_info, font_size, pixels_per_point);
|
||||
(Some(font_impl), allocated_glyph)
|
||||
}
|
||||
|
||||
pub(crate) fn ascent(&self, font_size: f32) -> f32 {
|
||||
if let Some(first) = self.fonts.first() {
|
||||
first.ascent()
|
||||
first.ascent(font_size)
|
||||
} else {
|
||||
self.row_height
|
||||
self.row_height(font_size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
font::{Font, FontImpl},
|
||||
},
|
||||
};
|
||||
use emath::{NumExt as _, OrderedFloat};
|
||||
use emath::NumExt as _;
|
||||
|
||||
#[cfg(feature = "default_fonts")]
|
||||
use epaint_default_fonts::{EMOJI_ICON, HACK_REGULAR, NOTO_EMOJI_REGULAR, UBUNTU_LIGHT};
|
||||
@@ -619,8 +619,9 @@ pub struct FontsImpl {
|
||||
max_texture_side: usize,
|
||||
definitions: FontDefinitions,
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
font_impl_cache: FontImplCache,
|
||||
sized_family: ahash::HashMap<(OrderedFloat<f32>, FontFamily), Font>,
|
||||
//font_impl_cache: FontImplCache,
|
||||
font_impls: ahash::HashMap<String, Arc<FontImpl>>,
|
||||
family_cache: ahash::HashMap<FontFamily, Font>,
|
||||
}
|
||||
|
||||
impl FontsImpl {
|
||||
@@ -643,16 +644,24 @@ impl FontsImpl {
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
||||
let font_impl_cache =
|
||||
FontImplCache::new(atlas.clone(), pixels_per_point, &definitions.font_data);
|
||||
let font_impls = definitions
|
||||
.font_data
|
||||
.iter()
|
||||
.map(|(name, font_data)| {
|
||||
let tweak = font_data.tweak;
|
||||
let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
|
||||
let font_impl = FontImpl::new(atlas.clone(), name.clone(), ab_glyph, tweak);
|
||||
(name.clone(), Arc::new(font_impl))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
definitions,
|
||||
atlas,
|
||||
font_impl_cache,
|
||||
sized_family: Default::default(),
|
||||
font_impls,
|
||||
family_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,48 +675,47 @@ impl FontsImpl {
|
||||
&self.definitions
|
||||
}
|
||||
|
||||
/// Get the right font implementation from size and [`FontFamily`].
|
||||
pub fn font(&mut self, font_id: &FontId) -> &mut Font {
|
||||
let FontId { size, family } = font_id;
|
||||
let mut size = *size;
|
||||
size = size.at_least(0.1).at_most(2048.0);
|
||||
/// Get the right font implementation from [`FontFamily`].
|
||||
pub fn font(&mut self, family: &FontFamily) -> &mut Font {
|
||||
self.family_cache.entry(family.clone()).or_insert_with(|| {
|
||||
let fonts = &self.definitions.families.get(family);
|
||||
let fonts =
|
||||
fonts.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
|
||||
|
||||
self.sized_family
|
||||
.entry((OrderedFloat(size), family.clone()))
|
||||
.or_insert_with(|| {
|
||||
let fonts = &self.definitions.families.get(family);
|
||||
let fonts = fonts
|
||||
.unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts"));
|
||||
let fonts: Vec<Arc<FontImpl>> = fonts
|
||||
.iter()
|
||||
.map(|font_name| {
|
||||
self.font_impls
|
||||
.get(font_name)
|
||||
.unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
|
||||
.clone()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fonts: Vec<Arc<FontImpl>> = fonts
|
||||
.iter()
|
||||
.map(|font_name| self.font_impl_cache.font_impl(size, font_name))
|
||||
.collect();
|
||||
|
||||
Font::new(fonts)
|
||||
})
|
||||
Font::new(fonts)
|
||||
})
|
||||
}
|
||||
|
||||
/// Width of this character in points.
|
||||
fn glyph_width(&mut self, font_id: &FontId, c: char) -> f32 {
|
||||
self.font(font_id).glyph_width(c)
|
||||
self.font(&font_id.family).glyph_width(c, font_id.size)
|
||||
}
|
||||
|
||||
/// Can we display this glyph?
|
||||
pub fn has_glyph(&mut self, font_id: &FontId, c: char) -> bool {
|
||||
self.font(font_id).has_glyph(c)
|
||||
self.font(&font_id.family).has_glyph(c)
|
||||
}
|
||||
|
||||
/// Can we display all the glyphs in this text?
|
||||
pub fn has_glyphs(&mut self, font_id: &FontId, s: &str) -> bool {
|
||||
self.font(font_id).has_glyphs(s)
|
||||
self.font(&font_id.family).has_glyphs(s)
|
||||
}
|
||||
|
||||
/// Height of one row of text in points.
|
||||
///
|
||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||
fn row_height(&mut self, font_id: &FontId) -> f32 {
|
||||
self.font(font_id).row_height()
|
||||
self.font(&font_id.family).row_height(font_id.size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,77 +971,6 @@ fn should_cache_each_paragraph_individually(job: &LayoutJob) -> bool {
|
||||
job.break_on_newline && job.wrap.max_rows == usize::MAX && job.text.contains('\n')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct FontImplCache {
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
pixels_per_point: f32,
|
||||
ab_glyph_fonts: BTreeMap<String, (FontTweak, ab_glyph::FontArc)>,
|
||||
|
||||
/// Map font pixel sizes and names to the cached [`FontImpl`].
|
||||
cache: ahash::HashMap<(u32, String), Arc<FontImpl>>,
|
||||
}
|
||||
|
||||
impl FontImplCache {
|
||||
pub fn new(
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
pixels_per_point: f32,
|
||||
font_data: &BTreeMap<String, Arc<FontData>>,
|
||||
) -> Self {
|
||||
let ab_glyph_fonts = font_data
|
||||
.iter()
|
||||
.map(|(name, font_data)| {
|
||||
let tweak = font_data.tweak;
|
||||
let ab_glyph = ab_glyph_font_from_font_data(name, font_data);
|
||||
(name.clone(), (tweak, ab_glyph))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
atlas,
|
||||
pixels_per_point,
|
||||
ab_glyph_fonts,
|
||||
cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn font_impl(&mut self, scale_in_points: f32, font_name: &str) -> Arc<FontImpl> {
|
||||
use ab_glyph::Font as _;
|
||||
|
||||
let (tweak, ab_glyph_font) = self
|
||||
.ab_glyph_fonts
|
||||
.get(font_name)
|
||||
.unwrap_or_else(|| panic!("No font data found for {font_name:?}"))
|
||||
.clone();
|
||||
|
||||
let scale_in_pixels = self.pixels_per_point * scale_in_points;
|
||||
|
||||
// Scale the font properly (see https://github.com/emilk/egui/issues/2068).
|
||||
let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| {
|
||||
panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)")
|
||||
});
|
||||
let font_scaling = ab_glyph_font.height_unscaled() / units_per_em;
|
||||
let scale_in_pixels = scale_in_pixels * font_scaling;
|
||||
|
||||
self.cache
|
||||
.entry((
|
||||
(scale_in_pixels * tweak.scale).round() as u32,
|
||||
font_name.to_owned(),
|
||||
))
|
||||
.or_insert_with(|| {
|
||||
Arc::new(FontImpl::new(
|
||||
self.atlas.clone(),
|
||||
self.pixels_per_point,
|
||||
font_name.to_owned(),
|
||||
ab_glyph_font,
|
||||
scale_in_pixels,
|
||||
tweak,
|
||||
))
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "default_fonts")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -143,11 +143,13 @@ fn layout_section(
|
||||
byte_range,
|
||||
format,
|
||||
} = section;
|
||||
let font = fonts.font(&format.font_id);
|
||||
let pixels_per_point = fonts.pixels_per_point();
|
||||
let font = fonts.font(&format.font_id.family);
|
||||
let font_size = format.font_id.size;
|
||||
let line_height = section
|
||||
.format
|
||||
.line_height
|
||||
.unwrap_or_else(|| font.row_height());
|
||||
.unwrap_or_else(|| font.row_height(font_size));
|
||||
let extra_letter_spacing = section.format.extra_letter_spacing;
|
||||
|
||||
let mut paragraph = out_paragraphs.last_mut().unwrap();
|
||||
@@ -165,30 +167,35 @@ fn layout_section(
|
||||
paragraph = out_paragraphs.last_mut().unwrap();
|
||||
paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs?
|
||||
} else {
|
||||
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(chr);
|
||||
if let Some(font_impl) = font_impl {
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id);
|
||||
paragraph.cursor_x += extra_letter_spacing;
|
||||
}
|
||||
let (font_impl, glyph_alloc) =
|
||||
font.font_impl_and_glyph_alloc(chr, font_size, pixels_per_point);
|
||||
|
||||
if let (Some(font_impl), Some(last_glyph_id)) = (font_impl, last_glyph_id) {
|
||||
paragraph.cursor_x += font_impl.pair_kerning(
|
||||
last_glyph_id,
|
||||
glyph_alloc.id,
|
||||
font_size,
|
||||
pixels_per_point,
|
||||
);
|
||||
paragraph.cursor_x += extra_letter_spacing;
|
||||
}
|
||||
|
||||
paragraph.glyphs.push(Glyph {
|
||||
chr,
|
||||
pos: pos2(paragraph.cursor_x, f32::NAN),
|
||||
advance_width: glyph_info.advance_width,
|
||||
advance_width: glyph_alloc.advance_width,
|
||||
line_height,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
|
||||
font_height: font.row_height(),
|
||||
font_ascent: font.ascent(),
|
||||
uv_rect: glyph_info.uv_rect,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)),
|
||||
font_height: font.row_height(font_size),
|
||||
font_ascent: font.ascent(font_size),
|
||||
uv_rect: glyph_alloc.uv_rect,
|
||||
section_index,
|
||||
});
|
||||
|
||||
paragraph.cursor_x += glyph_info.advance_width;
|
||||
paragraph.cursor_x = font.round_to_pixel(paragraph.cursor_x);
|
||||
last_glyph_id = Some(glyph_info.id);
|
||||
paragraph.cursor_x += glyph_alloc.advance_width;
|
||||
paragraph.cursor_x = paragraph.cursor_x.round_to_pixels(pixels_per_point);
|
||||
last_glyph_id = Some(glyph_alloc.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,70 +416,83 @@ fn replace_last_glyph_with_overflow_character(
|
||||
}
|
||||
}
|
||||
|
||||
fn row_height(section: &LayoutSection, font: &Font) -> f32 {
|
||||
fn row_height(section: &LayoutSection, font: &Font, font_size: f32) -> f32 {
|
||||
section
|
||||
.format
|
||||
.line_height
|
||||
.unwrap_or_else(|| font.row_height())
|
||||
.unwrap_or_else(|| font.row_height(font_size))
|
||||
}
|
||||
|
||||
let Some(overflow_character) = job.wrap.overflow_character else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pixels_per_point = fonts.pixels_per_point();
|
||||
|
||||
// We always try to just append the character first:
|
||||
if let Some(last_glyph) = row.glyphs.last() {
|
||||
let section_index = last_glyph.section_index;
|
||||
let section = &job.sections[section_index as usize];
|
||||
let font = fonts.font(§ion.format.font_id);
|
||||
let line_height = row_height(section, font);
|
||||
let font = fonts.font(§ion.format.font_id.family);
|
||||
let font_size = section.format.font_id.size;
|
||||
let line_height = row_height(section, font, font_size);
|
||||
|
||||
let (_, last_glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
|
||||
|
||||
let mut x = last_glyph.pos.x + last_glyph.advance_width;
|
||||
|
||||
let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character);
|
||||
let (font_impl, replacement_glyph_alloc) =
|
||||
font.font_impl_and_glyph_alloc(overflow_character, font_size, pixels_per_point);
|
||||
|
||||
{
|
||||
// Kerning:
|
||||
x += section.format.extra_letter_spacing;
|
||||
if let Some(font_impl) = font_impl {
|
||||
x += font_impl.pair_kerning(last_glyph_info.id, replacement_glyph_info.id);
|
||||
}
|
||||
// Kerning:
|
||||
x += section.format.extra_letter_spacing;
|
||||
if let Some(font_impl) = font_impl {
|
||||
x += font_impl.pair_kerning(
|
||||
last_glyph_info.id,
|
||||
replacement_glyph_alloc.id,
|
||||
section.format.font_id.size,
|
||||
pixels_per_point,
|
||||
);
|
||||
}
|
||||
|
||||
row.glyphs.push(Glyph {
|
||||
chr: overflow_character,
|
||||
pos: pos2(x, f32::NAN),
|
||||
advance_width: replacement_glyph_info.advance_width,
|
||||
advance_width: replacement_glyph_alloc.advance_width,
|
||||
line_height,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
|
||||
font_height: font.row_height(),
|
||||
font_ascent: font.ascent(),
|
||||
uv_rect: replacement_glyph_info.uv_rect,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)),
|
||||
font_height: font.row_height(font_size),
|
||||
font_ascent: font.ascent(font_size),
|
||||
uv_rect: replacement_glyph_alloc.uv_rect,
|
||||
section_index,
|
||||
});
|
||||
} else {
|
||||
let section_index = row.section_index_at_start;
|
||||
let section = &job.sections[section_index as usize];
|
||||
let font = fonts.font(§ion.format.font_id);
|
||||
let line_height = row_height(section, font);
|
||||
let font = fonts.font(§ion.format.font_id.family);
|
||||
let font_size = section.format.font_id.size;
|
||||
let line_height = row_height(section, font, font_size);
|
||||
|
||||
let x = 0.0; // TODO(emilk): heed paragraph leading_space 😬
|
||||
|
||||
let (font_impl, replacement_glyph_info) = font.font_impl_and_glyph_info(overflow_character);
|
||||
let glyph_alloc = if let Some(font_impl) = font_impl {
|
||||
font_impl.allocate_glyph(replacement_glyph_info, font_size, pixels_per_point)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
row.glyphs.push(Glyph {
|
||||
chr: overflow_character,
|
||||
pos: pos2(x, f32::NAN),
|
||||
advance_width: replacement_glyph_info.advance_width,
|
||||
advance_width: glyph_alloc.advance_width,
|
||||
line_height,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height()),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent()),
|
||||
font_height: font.row_height(),
|
||||
font_ascent: font.ascent(),
|
||||
uv_rect: replacement_glyph_info.uv_rect,
|
||||
font_impl_height: font_impl.map_or(0.0, |f| f.row_height(font_size)),
|
||||
font_impl_ascent: font_impl.map_or(0.0, |f| f.ascent(font_size)),
|
||||
font_height: font.row_height(font_size),
|
||||
font_ascent: font.ascent(font_size),
|
||||
uv_rect: glyph_alloc.uv_rect,
|
||||
section_index,
|
||||
});
|
||||
}
|
||||
@@ -498,30 +518,47 @@ fn replace_last_glyph_with_overflow_character(
|
||||
|
||||
let section = &job.sections[last_glyph.section_index as usize];
|
||||
let extra_letter_spacing = section.format.extra_letter_spacing;
|
||||
let font = fonts.font(§ion.format.font_id);
|
||||
let pixels_per_point = fonts.pixels_per_point();
|
||||
let font = fonts.font(§ion.format.font_id.family);
|
||||
let font_size = section.format.font_id.size;
|
||||
|
||||
if let Some(prev_glyph) = prev_glyph {
|
||||
let prev_glyph_id = font.font_impl_and_glyph_info(prev_glyph.chr).1.id;
|
||||
let prev_glyph_id = font
|
||||
.font_impl_and_glyph_alloc(prev_glyph.chr, font_size, pixels_per_point)
|
||||
.1
|
||||
.id;
|
||||
|
||||
// Undo kerning with previous glyph:
|
||||
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
|
||||
let (font_impl, glyph_alloc) =
|
||||
font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point);
|
||||
last_glyph.pos.x -= extra_letter_spacing;
|
||||
if let Some(font_impl) = font_impl {
|
||||
last_glyph.pos.x -= font_impl.pair_kerning(prev_glyph_id, glyph_info.id);
|
||||
last_glyph.pos.x -= font_impl.pair_kerning(
|
||||
prev_glyph_id,
|
||||
glyph_alloc.id,
|
||||
font_size,
|
||||
pixels_per_point,
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the glyph:
|
||||
last_glyph.chr = overflow_character;
|
||||
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
|
||||
last_glyph.advance_width = glyph_info.advance_width;
|
||||
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
|
||||
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
|
||||
last_glyph.uv_rect = glyph_info.uv_rect;
|
||||
let (font_impl, glyph_alloc) =
|
||||
font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point);
|
||||
last_glyph.advance_width = glyph_alloc.advance_width;
|
||||
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent(font_size));
|
||||
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height(font_size));
|
||||
last_glyph.uv_rect = glyph_alloc.uv_rect;
|
||||
|
||||
// Reapply kerning:
|
||||
last_glyph.pos.x += extra_letter_spacing;
|
||||
if let Some(font_impl) = font_impl {
|
||||
last_glyph.pos.x += font_impl.pair_kerning(prev_glyph_id, glyph_info.id);
|
||||
last_glyph.pos.x += font_impl.pair_kerning(
|
||||
prev_glyph_id,
|
||||
glyph_alloc.id,
|
||||
font_size,
|
||||
pixels_per_point,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if we're within width budget:
|
||||
@@ -532,13 +569,20 @@ fn replace_last_glyph_with_overflow_character(
|
||||
// We didn't fit - pop the last glyph and try again.
|
||||
row.glyphs.pop();
|
||||
} else {
|
||||
let Some(section) = &job.sections.get(last_glyph.section_index as usize) else {
|
||||
return;
|
||||
};
|
||||
let pixels_per_point = fonts.pixels_per_point();
|
||||
let font = fonts.font(§ion.format.font_id.family);
|
||||
let font_size = section.format.font_id.size;
|
||||
// Just replace and be done with it.
|
||||
last_glyph.chr = overflow_character;
|
||||
let (font_impl, glyph_info) = font.font_impl_and_glyph_info(last_glyph.chr);
|
||||
last_glyph.advance_width = glyph_info.advance_width;
|
||||
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent());
|
||||
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height());
|
||||
last_glyph.uv_rect = glyph_info.uv_rect;
|
||||
let (font_impl, glyph_alloc) =
|
||||
font.font_impl_and_glyph_alloc(last_glyph.chr, font_size, pixels_per_point);
|
||||
last_glyph.advance_width = glyph_alloc.advance_width;
|
||||
last_glyph.font_impl_ascent = font_impl.map_or(0.0, |f| f.ascent(font_size));
|
||||
last_glyph.font_impl_height = font_impl.map_or(0.0, |f| f.row_height(font_size));
|
||||
last_glyph.uv_rect = glyph_alloc.uv_rect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user