From 646fea2133b4793ff077905fa4bacd8c636f52eb Mon Sep 17 00:00:00 2001 From: Jinwoo Park Date: Sat, 20 Dec 2025 04:24:48 +0900 Subject: [PATCH] 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) --- crates/epaint/src/text/font.rs | 62 ++++++++++++++++++++++++++--- crates/epaint/src/text/fonts.rs | 69 ++++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 7 deletions(-) diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 0dbc00ddd..bce98d924 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -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 { 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, + + /// Variable font location (for weight axis, etc.) + location: skrifa::instance::Location, glyph_info_cache: ahash::HashMap, glyph_alloc_cache: ahash::HashMap, } @@ -347,6 +353,7 @@ impl FontFace { font_data: Blob, index: u32, tweak: FontTweak, + preferred_weight: Option, ) -> Result> { 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 { + 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); diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index f91a549d4..8ca78064d 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -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, } 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 { + let key = self.fonts_by_name.get(font_name)?; + let font_face = self.fonts_by_id.get(key)?; + font_face.weight() + } } // ----------------------------------------------------------------------------