mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add font variations API (#7859)
<!-- 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 This was mostly from last month, but I never got around to submitting it. This PR adds font variation coordinates to the `TextFormat` struct, and uses them when rendering text. The coordinates are stored in a `SmallVec`; I've chosen to store up to 2 inline, which makes it take up 24 bytes (the minimum possible for a `SmallVec`). The variation axis tags are stored as the `font_types::Tag` type, which I've chosen to re-export from `epaint::text`. The variation coordinates are resolved to a `skrifa::Location` during font rendering/scaling, and are cached in the same way as all the other scaled metrics. I've renamed the `ScaledMetrics` struct to `StyledMetrics`, since it now also contains the resolved variation coordinates. I haven't benchmarked the performance of text layout with variation coordinates, but the existing text layout performance is unchanged. I've replaced the API for manually overriding a font's weight (https://github.com/emilk/egui/pull/7790) with an API for manually overriding any variation coordinates via `FontTweak`. This should support the same use case as #7790 while being substantially more flexible. I have *not* yet added any higher-level API for mapping style attributes (weight, width, slant, etc) to variation coordinates or to different font faces within a single family. That's a pretty huge can of worms, and it'd involve rethinking the split between `FontId` and `TextFormat` (and whether `FontId` is so big that we should provide a way to reuse it). This API is intentionally pretty low-level for now. Likewise, I've intentionally not used variation coordinates when computing a font's row height. I can't think of any fonts that change their vertical metrics depending on variation axes, so this should be fine for now. --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -12,7 +12,7 @@ use vello_cpu::{color, kurbo};
|
||||
use crate::{
|
||||
TextOptions, TextureAtlas,
|
||||
text::{
|
||||
FontTweak,
|
||||
FontTweak, VariationCoords,
|
||||
fonts::{Blob, CachedFamily, FontFaceKey},
|
||||
},
|
||||
};
|
||||
@@ -145,8 +145,8 @@ struct GlyphCacheKey(u64);
|
||||
impl nohash_hasher::IsEnabled for GlyphCacheKey {}
|
||||
|
||||
impl GlyphCacheKey {
|
||||
fn new(glyph_id: skrifa::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self {
|
||||
let ScaledMetrics {
|
||||
fn new(glyph_id: skrifa::GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self {
|
||||
let StyledMetrics {
|
||||
pixels_per_point,
|
||||
px_scale_factor,
|
||||
..
|
||||
@@ -197,10 +197,10 @@ impl FontCell {
|
||||
fn allocate_glyph_uncached(
|
||||
&mut self,
|
||||
atlas: &mut TextureAtlas,
|
||||
metrics: &ScaledMetrics,
|
||||
metrics: &StyledMetrics,
|
||||
glyph_info: &GlyphInfo,
|
||||
bin: SubpixelBin,
|
||||
location: &skrifa::instance::Location,
|
||||
location: skrifa::instance::LocationRef<'_>,
|
||||
) -> Option<GlyphAllocation> {
|
||||
let glyph_id = glyph_info.id?;
|
||||
|
||||
@@ -337,8 +337,6 @@ pub struct FontFace {
|
||||
font: FontCell,
|
||||
tweak: FontTweak,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
@@ -350,7 +348,6 @@ 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 =
|
||||
@@ -396,44 +393,10 @@ impl FontFace {
|
||||
})
|
||||
})?;
|
||||
|
||||
// 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,
|
||||
location,
|
||||
glyph_info_cache: Default::default(),
|
||||
glyph_alloc_cache: Default::default(),
|
||||
})
|
||||
@@ -537,7 +500,7 @@ impl FontFace {
|
||||
#[inline]
|
||||
pub(super) fn pair_kerning_pixels(
|
||||
&self,
|
||||
metrics: &ScaledMetrics,
|
||||
metrics: &StyledMetrics,
|
||||
last_glyph_id: skrifa::GlyphId,
|
||||
glyph_id: skrifa::GlyphId,
|
||||
) -> f32 {
|
||||
@@ -559,7 +522,7 @@ impl FontFace {
|
||||
#[inline]
|
||||
pub fn pair_kerning(
|
||||
&self,
|
||||
metrics: &ScaledMetrics,
|
||||
metrics: &StyledMetrics,
|
||||
last_glyph_id: skrifa::GlyphId,
|
||||
glyph_id: skrifa::GlyphId,
|
||||
) -> f32 {
|
||||
@@ -567,7 +530,12 @@ impl FontFace {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
|
||||
pub fn styled_metrics(
|
||||
&self,
|
||||
pixels_per_point: f32,
|
||||
font_size: f32,
|
||||
coords: &VariationCoords,
|
||||
) -> StyledMetrics {
|
||||
let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale);
|
||||
let font_data = self.font.borrow_dependent();
|
||||
let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui();
|
||||
@@ -581,20 +549,32 @@ impl FontFace {
|
||||
+ self.tweak.y_offset)
|
||||
.round_ui();
|
||||
|
||||
ScaledMetrics {
|
||||
let axes = font_data.skrifa.axes();
|
||||
// Override the default coordinates with ones specified via FontTweak, then the ones specified directly via the
|
||||
// argument (probably from TextFormat).
|
||||
let settings = self
|
||||
.tweak
|
||||
.coords
|
||||
.as_ref()
|
||||
.iter()
|
||||
.chain(coords.as_ref().iter());
|
||||
let location = axes.location(settings);
|
||||
|
||||
StyledMetrics {
|
||||
pixels_per_point,
|
||||
px_scale_factor,
|
||||
scale,
|
||||
y_offset_in_points,
|
||||
ascent,
|
||||
row_height: ascent - descent + line_gap,
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate_glyph(
|
||||
&mut self,
|
||||
atlas: &mut TextureAtlas,
|
||||
metrics: &ScaledMetrics,
|
||||
metrics: &StyledMetrics,
|
||||
glyph_info: GlyphInfo,
|
||||
chr: char,
|
||||
h_pos: f32,
|
||||
@@ -628,7 +608,7 @@ impl FontFace {
|
||||
|
||||
let allocation = self
|
||||
.font
|
||||
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, &self.location)
|
||||
.allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, (&metrics.location).into())
|
||||
.unwrap_or_default();
|
||||
|
||||
entry.insert(allocation);
|
||||
@@ -665,12 +645,17 @@ impl Font<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
|
||||
pub fn styled_metrics(
|
||||
&self,
|
||||
pixels_per_point: f32,
|
||||
font_size: f32,
|
||||
coords: &VariationCoords,
|
||||
) -> StyledMetrics {
|
||||
self.cached_family
|
||||
.fonts
|
||||
.first()
|
||||
.and_then(|key| self.fonts_by_id.get(key))
|
||||
.map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size))
|
||||
.map(|font_face| font_face.styled_metrics(pixels_per_point, font_size, coords))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -713,8 +698,8 @@ impl Font<'_> {
|
||||
}
|
||||
|
||||
/// Metrics for a font at a specific screen-space scale.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct ScaledMetrics {
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct StyledMetrics {
|
||||
/// The DPI part of the screen-space scale.
|
||||
pub pixels_per_point: f32,
|
||||
|
||||
@@ -738,6 +723,9 @@ pub struct ScaledMetrics {
|
||||
///
|
||||
/// Returns a value rounded to [`emath::GUI_ROUNDING`].
|
||||
pub row_height: f32,
|
||||
|
||||
/// Resolved variation coordinates.
|
||||
pub location: skrifa::instance::Location,
|
||||
}
|
||||
|
||||
/// Code points that will always be invisible (zero width).
|
||||
|
||||
Reference in New Issue
Block a user