Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve vertical alignment of fonts #2724

Merged
merged 7 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 95 additions & 75 deletions crates/epaint/src/text/font.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
mutex::{Mutex, RwLock},
text::FontTweak,
TextureAtlas,
};
use emath::{vec2, Vec2};
Expand Down Expand Up @@ -42,6 +43,10 @@ pub struct GlyphInfo {
/// Unit: points.
pub advance_width: f32,

pub ascent: f32,

pub row_height: f32,
lictex marked this conversation as resolved.
Show resolved Hide resolved

/// Texture coordinates.
pub uv_rect: UvRect,
}
Expand All @@ -52,6 +57,8 @@ impl Default for GlyphInfo {
Self {
id: ab_glyph::GlyphId(0),
advance_width: 0.0,
ascent: 0.0,
row_height: 0.0,
uv_rect: Default::default(),
}
}
Expand All @@ -69,6 +76,7 @@ pub struct FontImpl {
height_in_points: f32,
// move each character by this much (hack)
y_offset: f32,
ascent: f32,
pixels_per_point: f32,
glyph_info_cache: RwLock<ahash::HashMap<char, GlyphInfo>>, // TODO(emilk): standard Mutex
atlas: Arc<Mutex<TextureAtlas>>,
Expand All @@ -80,20 +88,38 @@ impl FontImpl {
pixels_per_point: f32,
name: String,
ab_glyph_font: ab_glyph::FontArc,
scale_in_pixels: u32,
y_offset_points: f32,
scale_in_pixels: f32,
tweak: FontTweak,
) -> FontImpl {
assert!(scale_in_pixels > 0);
assert!(scale_in_pixels > 0.0);
assert!(pixels_per_point > 0.0);

let height_in_points = scale_in_pixels as f32 / pixels_per_point;
use ab_glyph::*;
let scaled = ab_glyph_font.as_scaled(scale_in_pixels);
let ascent = scaled.ascent() / pixels_per_point;
let descent = scaled.descent() / pixels_per_point;
let line_gap = scaled.line_gap() / pixels_per_point;

// Tweak the scale as the user desired
// this is only for visual effects and does not affect layout behaviors
let scale_in_pixels = scale_in_pixels * tweak.scale;

let baseline_offset = {
let scale_in_points = scale_in_pixels / pixels_per_point;
scale_in_points * tweak.baseline_offset_factor
};

let y_offset_points = {
let scale_in_points = scale_in_pixels / pixels_per_point;
scale_in_points * tweak.y_offset_factor
} + tweak.y_offset;

// TODO(emilk): use these font metrics?
// use ab_glyph::ScaleFont as _;
// let scaled = ab_glyph_font.as_scaled(scale_in_pixels as f32);
// dbg!(scaled.ascent());
// dbg!(scaled.descent());
// dbg!(scaled.line_gap());
// center scaled glyphs properly
let y_offset_points = y_offset_points + (tweak.scale - 1.0) * 0.5 * (ascent + descent);

// 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 = (y_offset_points * pixels_per_point).round() / pixels_per_point;
Expand All @@ -102,8 +128,9 @@ impl FontImpl {
name,
ab_glyph_font,
scale_in_pixels,
height_in_points,
height_in_points: ascent - descent + line_gap,
y_offset,
ascent: ascent + baseline_offset,
pixels_per_point,
glyph_info_cache: Default::default(),
atlas,
Expand Down Expand Up @@ -194,15 +221,7 @@ impl FontImpl {
if glyph_id.0 == 0 {
None // unsupported character
} else {
let glyph_info = allocate_glyph(
&mut self.atlas.lock(),
&self.ab_glyph_font,
glyph_id,
self.scale_in_pixels as f32,
self.y_offset,
self.pixels_per_point,
);

let glyph_info = self.allocate_glyph(glyph_id);
self.glyph_info_cache.write().insert(c, glyph_info);
Some(glyph_info)
}
Expand Down Expand Up @@ -231,6 +250,62 @@ impl FontImpl {
pub fn pixels_per_point(&self) -> f32 {
self.pixels_per_point
}

fn allocate_glyph(&self, glyph_id: ab_glyph::GlyphId) -> GlyphInfo {
assert!(glyph_id.0 != 0);
use ab_glyph::{Font as _, ScaleFont};

let glyph = glyph_id.with_scale_and_position(
self.scale_in_pixels as f32,
ab_glyph::Point { x: 0.0, y: 0.0 },
);

let uv_rect = self.ab_glyph_font.outline_glyph(glyph).map(|glyph| {
let bb = glyph.px_bounds();
let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize;
if glyph_width == 0 || glyph_height == 0 {
UvRect::default()
} else {
let atlas = &mut self.atlas.lock();
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
glyph.draw(|x, y, v| {
if v > 0.0 {
let px = glyph_pos.0 + x as usize;
let py = glyph_pos.1 + y as usize;
image[(px, py)] = v;
}
});

let offset_in_pixels = vec2(bb.min.x, bb.min.y);
let offset = offset_in_pixels / self.pixels_per_point + self.y_offset * Vec2::Y;
UvRect {
offset,
size: vec2(glyph_width as f32, glyph_height as f32) / self.pixels_per_point,
min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
max: [
(glyph_pos.0 + glyph_width) as u16,
(glyph_pos.1 + glyph_height) as u16,
],
}
}
});
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,
ascent: self.ascent,
row_height: self.row_height(),
uv_rect,
}
}
}

type FontIndex = usize;
Expand Down Expand Up @@ -429,58 +504,3 @@ fn invisible_char(c: char) -> bool {
| '\u{FEFF}' // ZERO WIDTH NO-BREAK SPACE
)
}

fn allocate_glyph(
atlas: &mut TextureAtlas,
font: &ab_glyph::FontArc,
glyph_id: ab_glyph::GlyphId,
scale_in_pixels: f32,
y_offset: f32,
pixels_per_point: f32,
) -> GlyphInfo {
assert!(glyph_id.0 != 0);
use ab_glyph::{Font as _, ScaleFont};

let glyph =
glyph_id.with_scale_and_position(scale_in_pixels, ab_glyph::Point { x: 0.0, y: 0.0 });

let uv_rect = font.outline_glyph(glyph).map(|glyph| {
let bb = glyph.px_bounds();
let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize;
if glyph_width == 0 || glyph_height == 0 {
UvRect::default()
} else {
let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
glyph.draw(|x, y, v| {
if v > 0.0 {
let px = glyph_pos.0 + x as usize;
let py = glyph_pos.1 + y as usize;
image[(px, py)] = v;
}
});

let offset_in_pixels = vec2(bb.min.x, scale_in_pixels + bb.min.y);
let offset = offset_in_pixels / pixels_per_point + y_offset * Vec2::Y;
UvRect {
offset,
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,
(glyph_pos.1 + glyph_height) as u16,
],
}
}
});
let uv_rect = uv_rect.unwrap_or_default();

let advance_width_in_points =
font.as_scaled(scale_in_pixels).h_advance(glyph_id) / pixels_per_point;

GlyphInfo {
id: glyph_id,
advance_width: advance_width_in_points,
uv_rect,
}
}
39 changes: 18 additions & 21 deletions crates/epaint/src/text/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,17 @@ pub struct FontTweak {
///
/// Example value: `2.0`.
pub y_offset: f32,

pub baseline_offset_factor: f32, // can this replace `y offset`?
}

impl Default for FontTweak {
fn default() -> Self {
Self {
scale: 1.0,
y_offset_factor: -0.2, // makes the default fonts look more centered in buttons and such
y_offset_factor: 0.0,
y_offset: 0.0,
baseline_offset_factor: -0.0333, // makes the default fonts look more centered in buttons and such
}
}
}
Expand Down Expand Up @@ -272,9 +275,8 @@ impl Default for FontDefinitions {
"NotoEmoji-Regular".to_owned(),
FontData::from_static(include_bytes!("../../fonts/NotoEmoji-Regular.ttf")).tweak(
FontTweak {
scale: 0.81, // make it smaller
y_offset_factor: -0.2, // move it up
y_offset: 0.0,
scale: 0.81, // make it smaller
..Default::default()
},
),
);
Expand All @@ -284,9 +286,13 @@ impl Default for FontDefinitions {
"emoji-icon-font".to_owned(),
FontData::from_static(include_bytes!("../../fonts/emoji-icon-font.ttf")).tweak(
FontTweak {
scale: 0.88, // make it smaller
y_offset_factor: 0.07, // move it down slightly
y_offset: 0.0,
scale: 0.88, // make it smaller

// metrics of this font seems somewhat weird
// (or i might be wrong somewhere else?)
y_offset_factor: 0.11, // move it down a bit
baseline_offset_factor: -0.11, // ...but don't do that when it is alone (mainly for submenu icons)
..Default::default()
},
),
);
Expand Down Expand Up @@ -760,28 +766,19 @@ impl FontImplCache {
let font_scaling = ab_glyph_font.height_unscaled() / units_per_em;
let scale_in_pixels = scale_in_pixels * font_scaling;

// Tweak the scale as the user desired:
let scale_in_pixels = scale_in_pixels * tweak.scale;

// 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;

let y_offset_points = {
let scale_in_points = scale_in_pixels as f32 / self.pixels_per_point;
scale_in_points * tweak.y_offset_factor
} + tweak.y_offset;

self.cache
.entry((scale_in_pixels, font_name.to_owned()))
.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,
y_offset_points,
tweak,
))
})
.clone()
Expand Down
25 changes: 20 additions & 5 deletions crates/epaint/src/text/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ fn layout_section(
paragraph.glyphs.push(Glyph {
chr,
pos: pos2(paragraph.cursor_x, f32::NAN),
size: vec2(glyph_info.advance_width, font_height),
size: vec2(glyph_info.advance_width, glyph_info.row_height),
ascent: glyph_info.ascent,
uv_rect: glyph_info.uv_rect,
section_index,
});
Expand Down Expand Up @@ -434,17 +435,31 @@ fn galley_from_rows(point_scale: PointScale, job: Arc<LayoutJob>, mut rows: Vec<
let mut max_x: f32 = 0.0;
for row in &mut rows {
let mut row_height = first_row_min_height.max(row.rect.height());
let mut row_ascent = 0.0f32;
first_row_min_height = 0.0;
for glyph in &row.glyphs {
row_height = row_height.max(glyph.size.y);

// take metrics from the highest font in this row
if let Some(glyph) = row
.glyphs
.iter()
.max_by(|a, b| a.size.y.partial_cmp(&b.size.y).unwrap())
{
row_height = glyph.size.y;
row_ascent = glyph.ascent;
}
row_height = point_scale.round_to_pixel(row_height);

// Now positions each glyph:
for glyph in &mut row.glyphs {
let format = &job.sections[glyph.section_index as usize].format;
glyph.pos.y = cursor_y + format.valign.to_factor() * (row_height - glyph.size.y);
glyph.pos.y = point_scale.round_to_pixel(glyph.pos.y);

let align_offset = match format.valign {
Align::Center | Align::Max => row_ascent,

// raised text. might not work very well..?
Align::Min => glyph.size.y,
};
glyph.pos.y = cursor_y + align_offset;
}

row.rect.min.y = cursor_y;
Expand Down
Loading