1
0
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:
valadaptive
2025-07-03 23:10:11 -04:00
parent 955e678577
commit 02d45d70bc
5 changed files with 308 additions and 293 deletions

View File

@@ -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)

View File

@@ -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())

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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(&section.format.font_id);
let line_height = row_height(section, font);
let font = fonts.font(&section.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(&section.format.font_id);
let line_height = row_height(section, font);
let font = fonts.font(&section.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(&section.format.font_id);
let pixels_per_point = fonts.pixels_per_point();
let font = fonts.font(&section.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(&section.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;
}
}