mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
Replace ab_glyph with Skrifa + vello_cpu; enable font hinting (#7694)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * Closes N/A * [x] I have followed the instructions in the PR template I'll probably come back to this and clean it up a bit. This PR reimplements ab_glyph's functionality on top of Skrifa, a somewhat lower-level font API that's being used in Chrome now. Skrifa doesn't perform rasterization itself, so I'm using [vello_cpu](https://github.com/linebender/vello) from the Linebender project for rasterization. It's still in its early days, but I believe it's already quite fast. It also supports color and gradient fills, so color emoji support will be easier. Skrifa also supports font hinting, which should make text look a bit nicer / less blurry. Here's the current ab_glyph rendering: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/2385b66e-23f8-4c6e-b8c2-ea90e0eea4e4" /> Here's Skrifa *without* hinting--it looks almost identical, but there are some subpixel differences, probably due to rasterizer behavior: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/a815f3e9-65ac-4940-bc00-571177bef53d" /> Here's Skrifa *with* hinting: <img width="1592" height="1068" alt="image" src="https://github.com/user-attachments/assets/d6cc0669-3537-4377-bba9-ed5ef09664db" /> Hinting does make the horizontal strokes look a bit bolder, which makes me wonder once again about increasing the font weight from "light" to "regular". --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -20,7 +20,7 @@ use crate::{
|
||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
||||
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
|
||||
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet,
|
||||
ViewportOutput, Widget as _, WidgetRect, WidgetText,
|
||||
ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
|
||||
animation_manager::AnimationManager,
|
||||
containers::{self, area::AreaState},
|
||||
data::output::PlatformOutput,
|
||||
@@ -34,8 +34,7 @@ use crate::{
|
||||
os::OperatingSystem,
|
||||
output::FullOutput,
|
||||
pass_state::PassState,
|
||||
plugin,
|
||||
plugin::TypedPluginHandle,
|
||||
plugin::{self, TypedPluginHandle},
|
||||
resize, response, scroll_area,
|
||||
util::IdTypeMap,
|
||||
viewport::ViewportClass,
|
||||
@@ -564,7 +563,10 @@ impl ContextImpl {
|
||||
log::trace!("Adding new fonts");
|
||||
}
|
||||
|
||||
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
let Visuals {
|
||||
mut text_options, ..
|
||||
} = self.memory.options.style().visuals;
|
||||
text_options.max_texture_side = max_texture_side;
|
||||
|
||||
let mut is_new = false;
|
||||
|
||||
@@ -573,16 +575,12 @@ impl ContextImpl {
|
||||
|
||||
is_new = true;
|
||||
profiling::scope!("Fonts::new");
|
||||
Fonts::new(
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
self.font_definitions.clone(),
|
||||
)
|
||||
Fonts::new(text_options, self.font_definitions.clone())
|
||||
});
|
||||
|
||||
{
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(max_texture_side, text_alpha_from_coverage);
|
||||
fonts.begin_pass(text_options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2006,15 +2004,12 @@ impl Context {
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
profiling::function_scope!();
|
||||
|
||||
let mut update_fonts = true;
|
||||
|
||||
self.read(|ctx| {
|
||||
if let Some(current_fonts) = ctx.fonts.as_ref() {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
if current_fonts.definitions() == &font_definitions {
|
||||
update_fonts = false; // no need to update
|
||||
}
|
||||
}
|
||||
let update_fonts = self.read(|ctx| {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
// TODO(valadaptive): add_font only checks the *names* for equality. Change this?
|
||||
ctx.fonts
|
||||
.as_ref()
|
||||
.is_none_or(|fonts| fonts.definitions() != &font_definitions)
|
||||
});
|
||||
|
||||
if update_fonts {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use emath::Align;
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak};
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
@@ -948,8 +948,11 @@ pub struct Visuals {
|
||||
/// this is more to provide a convenient summary of the rest of the settings.
|
||||
pub dark_mode: bool,
|
||||
|
||||
/// ADVANCED: Controls how we render text.
|
||||
pub text_alpha_from_coverage: AlphaFromCoverage,
|
||||
/// Controls how we render text.
|
||||
///
|
||||
/// The [`TextOptions::max_texture_side`] is ignored and overruled by
|
||||
/// [`crate::RawInput::max_texture_side`].
|
||||
pub text_options: TextOptions,
|
||||
|
||||
/// Override default text color for all text.
|
||||
///
|
||||
@@ -1407,7 +1410,10 @@ impl Visuals {
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
override_text_color: None,
|
||||
weak_text_alpha: 0.6,
|
||||
weak_text_color: None,
|
||||
@@ -1470,7 +1476,10 @@ impl Visuals {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
dark_mode: false,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
widgets: Widgets::light(),
|
||||
selection: Selection::light(),
|
||||
hyperlink_color: Color32::from_rgb(0, 155, 255),
|
||||
@@ -2107,7 +2116,7 @@ impl Visuals {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
dark_mode,
|
||||
text_alpha_from_coverage,
|
||||
text_options,
|
||||
override_text_color: _,
|
||||
weak_text_alpha,
|
||||
weak_text_color,
|
||||
@@ -2207,7 +2216,7 @@ impl Visuals {
|
||||
});
|
||||
});
|
||||
|
||||
ui.collapsing("Text color", |ui| {
|
||||
ui.collapsing("Text rendering", |ui| {
|
||||
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
|
||||
ui.label(label.into().color(*color));
|
||||
ui.color_edit_button_srgba(color);
|
||||
@@ -2259,7 +2268,15 @@ impl Visuals {
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
|
||||
let TextOptions {
|
||||
max_texture_side: _,
|
||||
alpha_from_coverage,
|
||||
font_hinting,
|
||||
} = text_options;
|
||||
|
||||
text_alpha_from_coverage_ui(ui, alpha_from_coverage);
|
||||
|
||||
ui.checkbox(font_hinting, "Enable font hinting");
|
||||
});
|
||||
|
||||
ui.collapsing("Text cursor", |ui| {
|
||||
@@ -2370,9 +2387,9 @@ impl Visuals {
|
||||
}
|
||||
}
|
||||
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
let mut dark_mode_special =
|
||||
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Text rendering:");
|
||||
@@ -2380,9 +2397,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
ui.checkbox(&mut dark_mode_special, "Dark-mode special");
|
||||
|
||||
if dark_mode_special {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
|
||||
} else {
|
||||
let mut gamma = match text_alpha_from_coverage {
|
||||
let mut gamma = match alpha_from_coverage {
|
||||
AlphaFromCoverage::Linear => 1.0,
|
||||
AlphaFromCoverage::Gamma(gamma) => *gamma,
|
||||
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
|
||||
@@ -2396,9 +2413,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
);
|
||||
|
||||
if gamma == 1.0 {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
*alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
} else {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
*alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2812,6 +2829,7 @@ impl Widget for &mut FontTweak {
|
||||
scale,
|
||||
y_offset_factor,
|
||||
y_offset,
|
||||
hinting_override,
|
||||
} = self;
|
||||
|
||||
ui.label("Scale");
|
||||
@@ -2827,6 +2845,19 @@ impl Widget for &mut FontTweak {
|
||||
ui.add(DragValue::new(y_offset).speed(-0.02));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("hinting_override");
|
||||
ComboBox::from_id_salt("hinting_override")
|
||||
.selected_text(match hinting_override {
|
||||
None => "None",
|
||||
Some(true) => "Enable",
|
||||
Some(false) => "Disable",
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(hinting_override, None, "None");
|
||||
ui.selectable_value(hinting_override, Some(true), "Enable");
|
||||
ui.selectable_value(hinting_override, Some(false), "Disable");
|
||||
});
|
||||
|
||||
if ui.button("Reset").clicked() {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user