diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 3d252cb21bba..807afac39bdd 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -25,6 +25,7 @@ use crate::{ pub struct RichText { text: String, size: Option, + extra_letter_spacing: f32, family: Option, text_style: Option, background_color: Color32, @@ -100,6 +101,15 @@ impl RichText { self } + /// Extra spacing between letters, in points. + /// + /// Default: 0.0. Round to whole _pixels_ for crisp text. + #[inline] + pub fn extra_letter_spacing(mut self, extra_letter_spacing: f32) -> Self { + self.extra_letter_spacing = extra_letter_spacing; + self + } + /// Select the font family. /// /// This overrides the value from [`Self::text_style`]. @@ -253,6 +263,7 @@ impl RichText { let Self { text, size, + extra_letter_spacing, family, text_style, background_color, @@ -315,6 +326,7 @@ impl RichText { underline, strikethrough, valign, + extra_letter_spacing, }; let job = LayoutJob::single_section(text, text_format); diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs index c15a285b9c66..7431accc5045 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs @@ -188,5 +188,6 @@ fn format_from_style( underline, strikethrough, valign, + ..Default::default() } } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 5aab349e2cd9..726abc0e3435 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -136,6 +136,7 @@ fn layout_section( paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id); } } + paragraph.cursor_x += section.format.extra_letter_spacing; paragraph.glyphs.push(Glyph { chr, @@ -338,10 +339,13 @@ fn replace_last_glyph_with_overflow_character( // undo kerning with previous glyph let (font_impl, glyph_info) = font.glyph_info_and_font_impl(last_glyph.chr); - last_glyph.pos.x -= font_impl - .zip(prev_glyph_id) - .map(|(font_impl, prev_glyph_id)| font_impl.pair_kerning(prev_glyph_id, glyph_info.id)) - .unwrap_or_default(); + last_glyph.pos.x -= section.format.extra_letter_spacing + + font_impl + .zip(prev_glyph_id) + .map(|(font_impl, prev_glyph_id)| { + font_impl.pair_kerning(prev_glyph_id, glyph_info.id) + }) + .unwrap_or_default(); // replace the glyph last_glyph.chr = overflow_character; @@ -351,10 +355,13 @@ fn replace_last_glyph_with_overflow_character( last_glyph.ascent = glyph_info.ascent; // reapply kerning - last_glyph.pos.x += font_impl - .zip(prev_glyph_id) - .map(|(font_impl, prev_glyph_id)| font_impl.pair_kerning(prev_glyph_id, glyph_info.id)) - .unwrap_or_default(); + last_glyph.pos.x += section.format.extra_letter_spacing + + font_impl + .zip(prev_glyph_id) + .map(|(font_impl, prev_glyph_id)| { + font_impl.pair_kerning(prev_glyph_id, glyph_info.id) + }) + .unwrap_or_default(); row.rect.max.x = last_glyph.max_x(); diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 2a73bd60f8b2..b19806aa5e4b 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -221,11 +221,17 @@ impl std::hash::Hash for LayoutSection { // ---------------------------------------------------------------------------- -#[derive(Clone, Debug, Hash, PartialEq)] +/// Formatting option for a section of text. +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TextFormat { pub font_id: FontId, + /// Extra spacing between letters, in points. + /// + /// Default: 0.0. Round to whole _pixels_ for crisp text. + pub extra_letter_spacing: f32, + /// Text color pub color: Color32, @@ -243,6 +249,30 @@ pub struct TextFormat { // TODO(emilk): lowered } +impl std::hash::Hash for TextFormat { + #[inline] + fn hash(&self, state: &mut H) { + let Self { + font_id, + extra_letter_spacing, + color, + background, + italics, + underline, + strikethrough, + valign, + } = self; + font_id.hash(state); + crate::f32_hash(state, *extra_letter_spacing); + color.hash(state); + background.hash(state); + italics.hash(state); + underline.hash(state); + strikethrough.hash(state); + valign.hash(state); + } +} + impl Default for TextFormat { #[inline] fn default() -> Self { @@ -254,6 +284,7 @@ impl Default for TextFormat { underline: Stroke::NONE, strikethrough: Stroke::NONE, valign: Align::BOTTOM, + extra_letter_spacing: 0.0, } } }