1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -04:00

Apply preferred font weight when loading variable fonts (#7790)

Previously, when loading a variable font (e.g. via
`egui::FontData::from_static`),
the font was rendered using the default (often the lightest) weight,
ignoring any preferred weight configuration.

This change applies the specified weight to skrifa's `Location` for the
`wght` axis,
ensuring that variable fonts are rendered with the intended font weight.

## Summary
Fixes variable font weight not being applied during rendering. The
`FontData::weight()` method now properly configures the font variation
axis.

## Changes
- Add `location: Location` field to `FontFace` to store variation
coordinates
- Pass `location` parameter through to glyph rendering functions
- Apply weight to skrifa's `LocationRef` in `DrawSettings` and
`HintingInstance`

## Weight Priority
1. `preferred_weight` from `FontData::weight()`
2. OS/2 table's `us_weight_class` 
3. Variable font's fvar default value
4. `Location::default()`

## Related Issue
- #3218 : Not follow font id, but goal would be same

## Todo
* [x] Apply preferred font weight when loading variable fonts
* [ ] Add small size variable fonts for docs and egui (need discussion)
This commit is contained in:
Jinwoo Park
2025-12-20 04:24:48 +09:00
committed by GitHub
parent 011c59c2ad
commit 646fea2133
2 changed files with 124 additions and 7 deletions

View File

@@ -1,13 +1,12 @@
#![allow(clippy::mem_forget)]
use std::collections::BTreeMap;
use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
use self_cell::self_cell;
use skrifa::{
MetadataProvider as _,
raw::{TableProvider as _, tables::kern::SubtableKind},
};
use std::collections::BTreeMap;
use vello_cpu::{color, kurbo};
use crate::{
@@ -201,6 +200,7 @@ impl FontCell {
metrics: &ScaledMetrics,
glyph_info: &GlyphInfo,
bin: SubpixelBin,
location: &skrifa::instance::Location,
) -> Option<GlyphAllocation> {
let glyph_id = glyph_info.id?;
@@ -225,7 +225,7 @@ impl FontCell {
.reconfigure(
&font_data.outline_glyphs,
size,
skrifa::instance::LocationRef::default(),
location,
skrifa::outline::Target::Smooth {
mode: skrifa::outline::SmoothMode::Normal,
symmetric_rendering: true,
@@ -239,7 +239,7 @@ impl FontCell {
} else {
let draw_settings = skrifa::outline::DrawSettings::unhinted(
skrifa::instance::Size::new(metrics.scale),
skrifa::instance::LocationRef::default(),
location,
);
outline.draw(draw_settings, &mut pen).ok()?;
}
@@ -336,6 +336,12 @@ pub struct FontFace {
name: String,
font: FontCell,
tweak: FontTweak,
/// The font weight (100-900) if available from the font file.
weight: Option<u16>,
/// Variable font location (for weight axis, etc.)
location: skrifa::instance::Location,
glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
}
@@ -347,6 +353,7 @@ impl FontFace {
font_data: Blob,
index: u32,
tweak: FontTweak,
preferred_weight: Option<u16>,
) -> Result<Self, Box<dyn std::error::Error>> {
let font = FontCell::try_new(font_data, |font_data| {
let skrifa_font =
@@ -354,6 +361,10 @@ impl FontFace {
let charmap = skrifa_font.charmap();
let glyphs = skrifa_font.outline_glyphs();
// Note: We use default location here during initialization because
// the actual weight will be applied via the stored location during rendering.
// The metrics won't be significantly different at this unscaled size.
let metrics = skrifa_font.metrics(
skrifa::instance::Size::unscaled(),
skrifa::instance::LocationRef::default(),
@@ -387,15 +398,56 @@ impl FontFace {
hinting_instance,
})
})?;
// Use preferred_weight if provided, otherwise try to read from the OS/2 table or fvar default
let weight = preferred_weight.or_else(|| {
// First try OS/2 table
if let Some(w) = font
.borrow_dependent()
.skrifa
.os2()
.ok()
.map(|os2| os2.us_weight_class())
{
return Some(w);
}
// If no OS/2 or preferred_weight, try to get default from variable font's fvar table
font.borrow_dependent()
.skrifa
.axes()
.iter()
.find(|axis| axis.tag() == skrifa::raw::types::Tag::new(b"wght"))
.map(|axis| axis.default_value() as u16)
});
// Create location for variable font with weight axis
// If weight is provided (either from preferred_weight, OS/2, or fvar default), use it
// Otherwise fall back to Location::default() which uses all axis defaults
let location = if let Some(w) = weight {
font.borrow_dependent()
.skrifa
.axes()
.location([("wght", w as f32)])
} else {
skrifa::instance::Location::default()
};
Ok(Self {
name,
font,
tweak,
weight,
location,
glyph_info_cache: Default::default(),
glyph_alloc_cache: Default::default(),
})
}
/// Get the font weight (100-900) if available from the font file.
pub fn weight(&self) -> Option<u16> {
self.weight
}
/// Code points that will always be replaced by the replacement character.
///
/// See also [`invisible_char`].
@@ -585,7 +637,7 @@ impl FontFace {
let allocation = self
.font
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin)
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, &self.location)
.unwrap_or_default();
entry.insert(allocation);

View File

@@ -125,6 +125,12 @@ pub struct FontData {
/// Extra scale and vertical tweak to apply to all text of this font.
pub tweak: FontTweak,
/// The font weight (100-900), if available.
/// Standard values: 100 (Thin), 200 (Extra Light), 300 (Light), 400 (Regular),
/// 500 (Medium), 600 (Semi Bold), 700 (Bold), 800 (Extra Bold), 900 (Black).
/// `None` if the weight could not be determined.
pub weight: Option<u16>,
}
impl FontData {
@@ -133,6 +139,7 @@ impl FontData {
font: Cow::Borrowed(font),
index: 0,
tweak: Default::default(),
weight: None,
}
}
@@ -141,12 +148,43 @@ impl FontData {
font: Cow::Owned(font),
index: 0,
tweak: Default::default(),
weight: None,
}
}
pub fn tweak(self, tweak: FontTweak) -> Self {
Self { tweak, ..self }
}
/// Set the font weight (100-900).
///
/// This is typically read automatically from the font file when loaded,
/// but can be overridden manually if needed.
///
/// Standard weight values:
/// - 100: Thin
/// - 200: Extra Light
/// - 300: Light
/// - 400: Regular/Normal
/// - 500: Medium
/// - 600: Semi Bold
/// - 700: Bold
/// - 800: Extra Bold
/// - 900: Black
///
/// # Example
/// ```
/// # use epaint::text::FontData;
/// let font_data = FontData::from_static(include_bytes!("../../../epaint_default_fonts/fonts/Ubuntu-Light.ttf"))
/// .weight(300); // Override to Light weight
/// assert_eq!(font_data.weight, Some(300));
/// ```
pub fn weight(self, weight: u16) -> Self {
Self {
weight: Some(weight),
..self
}
}
}
impl AsRef<[u8]> for FontData {
@@ -771,8 +809,15 @@ impl FontsImpl {
for (name, font_data) in &definitions.font_data {
let tweak = font_data.tweak;
let blob = blob_from_font_data(font_data);
let font_face = FontFace::new(options, name.clone(), blob, font_data.index, tweak)
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"));
let font_face = FontFace::new(
options,
name.clone(),
blob,
font_data.index,
tweak,
font_data.weight,
)
.unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}"));
let key = FontFaceKey::new();
fonts_by_id.insert(key, font_face);
fonts_by_name.insert(name.clone(), key);
@@ -816,6 +861,26 @@ impl FontsImpl {
atlas: &mut self.atlas,
}
}
/// Get the weight of a font by name, if available.
///
/// Returns the weight value (100-900) read from the font file's OS/2 table,
/// or `None` if the font is not found or doesn't contain weight information.
///
/// # Example
/// ```
/// # use epaint::text::{FontDefinitions, FontsImpl};
/// # use epaint::TextOptions;
/// let fonts_impl = FontsImpl::new(TextOptions::default(), FontDefinitions::default());
/// if let Some(weight) = fonts_impl.font_weight("Hack") {
/// println!("Hack font weight: {}", weight);
/// }
/// ```
pub fn font_weight(&self, font_name: &str) -> Option<u16> {
let key = self.fonts_by_name.get(font_name)?;
let font_face = self.fonts_by_id.get(key)?;
font_face.weight()
}
}
// ----------------------------------------------------------------------------