From fc8be2535acb125d4056200b16d2908a26bf72c7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 22:18:32 +0200 Subject: [PATCH] Add control of line height --- crates/egui/src/widget_text.rs | 21 +++++++-- crates/epaint/src/text/font.rs | 9 +--- crates/epaint/src/text/fonts.rs | 2 +- crates/epaint/src/text/text_layout.rs | 28 ++++++----- crates/epaint/src/text/text_layout_types.rs | 52 ++++++++++++++------- 5 files changed, 71 insertions(+), 41 deletions(-) diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 807afac39bd..666fa2d5343 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,5 +1,4 @@ -use std::borrow::Cow; -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use crate::{ style::WidgetVisuals, text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Pos2, @@ -26,6 +25,7 @@ pub struct RichText { text: String, size: Option, extra_letter_spacing: f32, + line_height: Option, family: Option, text_style: Option, background_color: Color32, @@ -110,6 +110,19 @@ impl RichText { self } + /// Explicit line height of the text in points. + /// + /// This is the distance between the bottom row of two subsequent lines of text. + /// + /// If `None` (the default), the line height is determined by the font. + /// + /// Round to whole _pixels_ for crisp text. + #[inline] + pub fn line_height(mut self, line_height: Option) -> Self { + self.line_height = line_height; + self + } + /// Select the font family. /// /// This overrides the value from [`Self::text_style`]. @@ -264,6 +277,7 @@ impl RichText { text, size, extra_letter_spacing, + line_height, family, text_style, background_color, @@ -320,13 +334,14 @@ impl RichText { let text_format = crate::text::TextFormat { font_id, + extra_letter_spacing, + line_height, color: text_color, background: background_color, italics, underline, strikethrough, valign, - extra_letter_spacing, }; let job = LayoutJob::single_section(text, text_format); diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index dfcb52a11be..e6b495c23a2 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -49,11 +49,6 @@ pub struct GlyphInfo { /// Unit: points. pub ascent: f32, - /// row height computed from the font metrics. - /// - /// Unit: points. - pub row_height: f32, - /// Texture coordinates. pub uv_rect: UvRect, } @@ -65,7 +60,6 @@ impl Default for GlyphInfo { id: ab_glyph::GlyphId(0), advance_width: 0.0, ascent: 0.0, - row_height: 0.0, uv_rect: Default::default(), } } @@ -250,7 +244,7 @@ impl FontImpl { / self.pixels_per_point } - /// Height of one row of text. In points + /// Height of one row of text in points. #[inline(always)] pub fn row_height(&self) -> f32 { self.height_in_points @@ -312,7 +306,6 @@ impl FontImpl { id: glyph_id, advance_width: advance_width_in_points, ascent: self.ascent, - row_height: self.row_height(), uv_rect, } } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 474d6e70e0a..ab08fbe8302 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -653,7 +653,7 @@ impl FontsImpl { self.font(font_id).has_glyphs(s) } - /// Height of one row of text. In points + /// Height of one row of text in points. fn row_height(&mut self, font_id: &FontId) -> f32 { self.font(font_id).row_height() } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index d7248b57f6f..1d07a5ecf33 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -113,11 +113,14 @@ fn layout_section( format, } = section; let font = fonts.font(&format.font_id); - let font_height = font.row_height(); + let line_height = section + .format + .line_height + .unwrap_or_else(|| font.row_height()); let mut paragraph = out_paragraphs.last_mut().unwrap(); if paragraph.glyphs.is_empty() { - paragraph.empty_paragraph_height = font_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? + paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } paragraph.cursor_x += leading_space; @@ -128,7 +131,7 @@ fn layout_section( if job.break_on_newline && chr == '\n' { out_paragraphs.push(Paragraph::default()); paragraph = out_paragraphs.last_mut().unwrap(); - paragraph.empty_paragraph_height = font_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? + paragraph.empty_paragraph_height = line_height; // TODO(emilk): replace this hack with actually including `\n` in the glyphs? } else { let (font_impl, glyph_info) = font.glyph_info_and_font_impl(chr); if let Some(font_impl) = font_impl { @@ -141,7 +144,7 @@ fn layout_section( paragraph.glyphs.push(Glyph { chr, pos: pos2(paragraph.cursor_x, f32::NAN), - size: vec2(glyph_info.advance_width, glyph_info.row_height), + size: vec2(glyph_info.advance_width, line_height), ascent: glyph_info.ascent, uv_rect: glyph_info.uv_rect, section_index, @@ -330,7 +333,10 @@ fn replace_last_glyph_with_overflow_character( let section = &job.sections[last_glyph.section_index as usize]; let font = fonts.font(§ion.format.font_id); - let font_height = font.row_height(); + let line_height = section + .format + .line_height + .unwrap_or_else(|| font.row_height()); let prev_glyph_id = prev_glyph.map(|prev_glyph| { let (_, prev_glyph_info) = font.glyph_info_and_font_impl(prev_glyph.chr); @@ -350,7 +356,7 @@ fn replace_last_glyph_with_overflow_character( // replace the glyph last_glyph.chr = overflow_character; let (font_impl, glyph_info) = font.glyph_info_and_font_impl(last_glyph.chr); - last_glyph.size = vec2(glyph_info.advance_width, font_height); + last_glyph.size = vec2(glyph_info.advance_width, line_height); last_glyph.uv_rect = glyph_info.uv_rect; last_glyph.ascent = glyph_info.ascent; @@ -481,7 +487,7 @@ fn galley_from_rows( let mut min_x: f32 = 0.0; let mut max_x: f32 = 0.0; for row in &mut rows { - let mut row_height = first_row_min_height.max(row.rect.height()); + let mut line_height = first_row_min_height.max(row.rect.height()); let mut row_ascent = 0.0f32; first_row_min_height = 0.0; @@ -491,10 +497,10 @@ fn galley_from_rows( .iter() .max_by(|a, b| a.size.y.partial_cmp(&b.size.y).unwrap()) { - row_height = glyph.size.y; + line_height = glyph.size.y; row_ascent = glyph.ascent; } - row_height = point_scale.round_to_pixel(row_height); + line_height = point_scale.round_to_pixel(line_height); // Now positions each glyph: for glyph in &mut row.glyphs { @@ -510,11 +516,11 @@ fn galley_from_rows( } row.rect.min.y = cursor_y; - row.rect.max.y = cursor_y + row_height; + row.rect.max.y = cursor_y + line_height; min_x = min_x.min(row.rect.min.x); max_x = max_x.max(row.rect.max.x); - cursor_y += row_height; + cursor_y += line_height; cursor_y = point_scale.round_to_pixel(cursor_y); } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index b19806aa5e4..64c8d3c0fa5 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -232,6 +232,15 @@ pub struct TextFormat { /// Default: 0.0. Round to whole _pixels_ for crisp text. pub extra_letter_spacing: f32, + /// Explicit line height of the text in points. + /// + /// This is the distance between the bottom row of two subsequent lines of text. + /// + /// If `None` (the default), the line height is determined by the font. + /// + /// Round to whole _pixels_ for crisp text. + pub line_height: Option, + /// Text color pub color: Color32, @@ -249,12 +258,30 @@ pub struct TextFormat { // TODO(emilk): lowered } +impl Default for TextFormat { + #[inline] + fn default() -> Self { + Self { + font_id: FontId::default(), + extra_letter_spacing: 0.0, + line_height: None, + color: Color32::GRAY, + background: Color32::TRANSPARENT, + italics: false, + underline: Stroke::NONE, + strikethrough: Stroke::NONE, + valign: Align::BOTTOM, + } + } +} + impl std::hash::Hash for TextFormat { #[inline] fn hash(&self, state: &mut H) { let Self { font_id, extra_letter_spacing, + line_height, color, background, italics, @@ -264,6 +291,9 @@ impl std::hash::Hash for TextFormat { } = self; font_id.hash(state); crate::f32_hash(state, *extra_letter_spacing); + if let Some(line_height) = *line_height { + crate::f32_hash(state, line_height); + } color.hash(state); background.hash(state); italics.hash(state); @@ -273,22 +303,6 @@ impl std::hash::Hash for TextFormat { } } -impl Default for TextFormat { - #[inline] - fn default() -> Self { - Self { - font_id: FontId::default(), - color: Color32::GRAY, - background: Color32::TRANSPARENT, - italics: false, - underline: Stroke::NONE, - strikethrough: Stroke::NONE, - valign: Align::BOTTOM, - extra_letter_spacing: 0.0, - } - } -} - impl TextFormat { #[inline] pub fn simple(font_id: FontId, color: Color32) -> Self { @@ -517,10 +531,12 @@ pub struct Glyph { /// `ascent` value from the font pub ascent: f32, - /// Advance width and font row height. + /// Advance width and line height. + /// + /// Does not control the visual size of the glyph (see [`Self::uv_rect`] for that). pub size: Vec2, - /// Position of the glyph in the font texture, in texels. + /// Position and size of the glyph in the font texture, in texels. pub uv_rect: UvRect, /// Index into [`LayoutJob::sections`]. Decides color etc.