Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
76 changes: 76 additions & 0 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,78 @@ pub struct Style {

/// Use a more compact style for menus.
pub compact_menu_style: bool,

/// Optional [`FontFamily`] to use for bold/strong text.
///
/// By default ([`None`]), [`RichText::strong()`] applies a color change
/// (using [`Visuals::strong_text_color`]) to convey emphasis.
/// When this is set to [`Some`], strong text will instead be rendered with the
/// specified font family, and the color change is skipped.
///
/// To use this, first register a bold font via [`crate::Context::set_fonts`],
/// then point this field at it:
///
/// ```
/// # let ctx = egui::Context::default();
/// // 1. Register the bold font data and family
/// let mut fonts = egui::FontDefinitions::default();
/// let bold_font_data: &[u8] = &[]; // bytes of a .ttf file
/// fonts.font_data.insert(
/// "my_bold_font".to_owned(),
/// egui::FontData::from_static(bold_font_data).into(),
/// );
/// fonts.families.insert(
/// egui::FontFamily::Name("Bold".into()),
/// vec!["my_bold_font".to_owned()],
/// );
/// ctx.set_fonts(fonts);
///
/// // 2. Tell egui to use it for strong text
/// ctx.global_style_mut(|style| {
/// style.strong_font = Some(egui::FontFamily::Name("Bold".into()));
/// });
/// ```
///
/// **Note:** When text is both strong and italic, and both `strong_font` and
/// [`Self::emphasis_font`] are configured, `emphasis_font` takes precedence
/// because a single [`FontId`] can only reference one font family.
pub strong_font: Option<FontFamily>,

/// Optional [`FontFamily`] to use for italic/emphasis text.
///
/// By default ([`None`]), [`RichText::italics()`] fakes italics by applying a
/// horizontal vertex skew during tessellation.
/// When this is set to [`Some`], italic text will instead be rendered with the
/// specified font family, and the vertex skew is skipped.
///
/// To use this, first register an italic font via [`crate::Context::set_fonts`],
/// then point this field at it:
///
/// ```
/// # let ctx = egui::Context::default();
/// // 1. Register the italic font data and family
/// let mut fonts = egui::FontDefinitions::default();
/// let italic_font_data: &[u8] = &[]; // bytes of a .ttf file
/// fonts.font_data.insert(
/// "my_italic_font".to_owned(),
/// egui::FontData::from_static(italic_font_data).into(),
/// );
/// fonts.families.insert(
/// egui::FontFamily::Name("Italic".into()),
/// vec!["my_italic_font".to_owned()],
/// );
/// ctx.set_fonts(fonts);
///
/// // 2. Tell egui to use it for italic text
/// ctx.global_style_mut(|style| {
/// style.emphasis_font = Some(egui::FontFamily::Name("Italic".into()));
/// });
/// ```
///
/// **Note:** When text is both strong and italic, and both [`Self::strong_font`] and
/// `emphasis_font` are configured, `emphasis_font` takes precedence
/// because a single [`FontId`] can only reference one font family.
pub emphasis_font: Option<FontFamily>,
}

#[test]
Expand Down Expand Up @@ -1370,6 +1442,8 @@ impl Default for Style {
always_scroll_the_only_direction: false,
scroll_animation: ScrollAnimation::default(),
compact_menu_style: true,
strong_font: None,
emphasis_font: None,
}
}
}
Expand Down Expand Up @@ -1684,6 +1758,8 @@ impl Style {
always_scroll_the_only_direction,
scroll_animation,
compact_menu_style,
strong_font: _,
emphasis_font: _,
} = self;

crate::Grid::new("_options").show(ui, |ui| {
Expand Down
66 changes: 59 additions & 7 deletions crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fmt::Formatter;
use std::{borrow::Cow, sync::Arc};

use crate::{
Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals,
Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui,
text::{LayoutJob, TextWrapping},
};

Expand Down Expand Up @@ -379,7 +379,28 @@ impl RichText {
fallback_font: FontSelection,
default_valign: Align,
) -> (String, crate::text::TextFormat) {
let text_color = self.get_text_color(&style.visuals);
let text_color = self.get_text_color(style);

// Resolve font family overrides for bold/italic before destructuring `self`.
//
// When `Style::strong_font` or `Style::emphasis_font` is configured, we
// swap the font family to a real bold/italic typeface instead of relying on
// the default color-change (bold) or vertex-skew (italic) approximations.
//
// If both are configured and the text is both strong and italic,
// `emphasis_font` takes precedence (a single FontId can only reference one
// family). Users who need a combined bold-italic face can register a
// dedicated BoldItalic font family and apply it via `RichText::family()`.
let bold_family = if self.strong {
style.strong_font.clone()
} else {
None
};
let italic_family = if self.italics {
style.emphasis_font.clone()
} else {
None
};

let Self {
text,
Expand Down Expand Up @@ -415,15 +436,28 @@ impl RichText {
if let Some(family) = family {
font_id.family = family;
}

// Apply bold/italic font family overrides.
// Italic is applied second so it wins when both are set.
if let Some(bold_family) = bold_family {
font_id.family = bold_family;
}
if let Some(italic_family) = italic_family {
font_id.family = italic_family;
}

font_id
};

// When a real italic font is configured, skip the vertex skew —
// the typeface itself provides proper italic letterforms.
let italics = italics && style.emphasis_font.is_none();

let background_color = if code {
style.visuals.code_bg_color
} else {
background_color
};

let underline = if underline {
crate::Stroke::new(1.0, line_color)
} else {
Expand Down Expand Up @@ -458,15 +492,33 @@ impl RichText {
)
}

fn get_text_color(&self, visuals: &Visuals) -> Option<Color32> {
/// Resolve the text color, accounting for bold font overrides.
///
/// Priority (highest first):
/// 1. Explicit color set via [`RichText::color()`]
/// 2. Strong color — but only when no [`Style::strong_font`] is configured,
/// because a real bold typeface provides visual distinction on its own
/// 3. Weak color
/// 4. Global [`crate::Visuals::override_text_color`]
fn get_text_color(&self, style: &Style) -> Option<Color32> {
if let Some(text_color) = self.text_color {
// Explicit color always wins.
Some(text_color)
} else if self.strong {
Some(visuals.strong_text_color())
if style.strong_font.is_some() {
// A real bold font provides visual weight through thicker strokes,
// so we don't need the fallback strong color. Use the default
// text color instead.
style.visuals.override_text_color
} else {
// No bold font configured — fall back to the strong color
// (brighter than normal text) to convey emphasis.
Some(style.visuals.strong_text_color())
}
} else if self.weak {
Some(visuals.weak_text_color())
Some(style.visuals.weak_text_color())
} else {
visuals.override_text_color
style.visuals.override_text_color
}
}
}
Expand Down