Add control of line height and letter spacing (#3302)
* Add `TextFormat::extra_letter_spacing` * Add control of line height * Add to text layout demo * Move the text layout demo to its own window in the demo app * Fix doclink * Better document points vs pixels * Better documentation and code cleanup
This commit is contained in:
parent
cf163cc954
commit
46ea72abe4
|
|
@ -92,14 +92,16 @@
|
|||
//! # });
|
||||
//! ```
|
||||
//!
|
||||
//! ## Conventions
|
||||
//! ## Coordinate system
|
||||
//! The left-top corner of the screen is `(0.0, 0.0)`,
|
||||
//! with X increasing to the right and Y increasing downwards.
|
||||
//!
|
||||
//! Conventions unless otherwise specified:
|
||||
//! `egui` uses logical _points_ as its coordinate system.
|
||||
//! Those related to physical _pixels_ by the `pixels_per_point` scale factor.
|
||||
//! For example, a high-dpi screeen can have `pixels_per_point = 2.0`,
|
||||
//! meaning there are two physical screen pixels for each logical point.
|
||||
//!
|
||||
//! * angles are in radians
|
||||
//! * `Vec2::X` is right and `Vec2::Y` is down.
|
||||
//! * `Pos2::ZERO` is left top.
|
||||
//! * Positions and sizes are measured in _points_. Each point may consist of many physical pixels.
|
||||
//! Angles are in radians, and are measured clockwise from the X-axis, which has angle=0.
|
||||
//!
|
||||
//! # Integrating with egui
|
||||
//!
|
||||
|
|
@ -352,7 +354,7 @@ pub mod text {
|
|||
pub use crate::text_edit::CCursorRange;
|
||||
pub use epaint::text::{
|
||||
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
|
||||
LayoutSection, TextFormat, TAB_SIZE,
|
||||
LayoutSection, TextFormat, TextWrapping, TAB_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -25,6 +24,8 @@ use crate::{
|
|||
pub struct RichText {
|
||||
text: String,
|
||||
size: Option<f32>,
|
||||
extra_letter_spacing: f32,
|
||||
line_height: Option<f32>,
|
||||
family: Option<FontFamily>,
|
||||
text_style: Option<TextStyle>,
|
||||
background_color: Color32,
|
||||
|
|
@ -100,6 +101,32 @@ impl RichText {
|
|||
self
|
||||
}
|
||||
|
||||
/// Extra spacing between letters, in points.
|
||||
///
|
||||
/// Default: 0.0.
|
||||
///
|
||||
/// For even text it is recommended you round this to an even number of _pixels_,
|
||||
/// e.g. using [`crate::Painter::round_to_pixel`].
|
||||
#[inline]
|
||||
pub fn extra_letter_spacing(mut self, extra_letter_spacing: f32) -> Self {
|
||||
self.extra_letter_spacing = extra_letter_spacing;
|
||||
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.
|
||||
///
|
||||
/// For even text it is recommended you round this to an even number of _pixels_,
|
||||
/// e.g. using [`crate::Painter::round_to_pixel`].
|
||||
#[inline]
|
||||
pub fn line_height(mut self, line_height: Option<f32>) -> Self {
|
||||
self.line_height = line_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Select the font family.
|
||||
///
|
||||
/// This overrides the value from [`Self::text_style`].
|
||||
|
|
@ -253,6 +280,8 @@ impl RichText {
|
|||
let Self {
|
||||
text,
|
||||
size,
|
||||
extra_letter_spacing,
|
||||
line_height,
|
||||
family,
|
||||
text_style,
|
||||
background_color,
|
||||
|
|
@ -309,6 +338,8 @@ impl RichText {
|
|||
|
||||
let text_format = crate::text::TextFormat {
|
||||
font_id,
|
||||
extra_letter_spacing,
|
||||
line_height,
|
||||
color: text_color,
|
||||
background: background_color,
|
||||
italics,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ impl Default for Demos {
|
|||
Box::<super::sliders::Sliders>::default(),
|
||||
Box::<super::strip_demo::StripDemo>::default(),
|
||||
Box::<super::table_demo::TableDemo>::default(),
|
||||
Box::<super::text_edit::TextEdit>::default(),
|
||||
Box::<super::text_edit::TextEditDemo>::default(),
|
||||
Box::<super::text_layout::TextLayoutDemo>::default(),
|
||||
Box::<super::widget_gallery::WidgetGallery>::default(),
|
||||
Box::<super::window_options::WindowOptions>::default(),
|
||||
Box::<super::tests::WindowResizeTest>::default(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::*;
|
||||
use egui::{epaint::text::TextWrapping, *};
|
||||
use egui::*;
|
||||
|
||||
/// Showcase some ui code
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
|
@ -7,8 +7,6 @@ use egui::{epaint::text::TextWrapping, *};
|
|||
pub struct MiscDemoWindow {
|
||||
num_columns: usize,
|
||||
|
||||
text_break: TextBreakDemo,
|
||||
|
||||
widgets: Widgets,
|
||||
colors: ColorWidgets,
|
||||
custom_collapsing_header: CustomCollapsingHeader,
|
||||
|
|
@ -24,8 +22,6 @@ impl Default for MiscDemoWindow {
|
|||
MiscDemoWindow {
|
||||
num_columns: 2,
|
||||
|
||||
text_break: Default::default(),
|
||||
|
||||
widgets: Default::default(),
|
||||
colors: Default::default(),
|
||||
custom_collapsing_header: Default::default(),
|
||||
|
|
@ -72,8 +68,6 @@ impl View for MiscDemoWindow {
|
|||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
text_layout_demo(ui);
|
||||
ui.separator();
|
||||
self.text_break.ui(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file_line!());
|
||||
});
|
||||
|
|
@ -644,63 +638,3 @@ fn text_layout_demo(ui: &mut Ui) {
|
|||
|
||||
ui.label(job);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct TextBreakDemo {
|
||||
break_anywhere: bool,
|
||||
max_rows: usize,
|
||||
overflow_character: Option<char>,
|
||||
}
|
||||
|
||||
impl Default for TextBreakDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_rows: 1,
|
||||
break_anywhere: true,
|
||||
overflow_character: Some('…'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextBreakDemo {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
break_anywhere,
|
||||
max_rows,
|
||||
overflow_character,
|
||||
} = self;
|
||||
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(max_rows));
|
||||
ui.label("Max rows");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Line-break:");
|
||||
ui.radio_value(break_anywhere, false, "word boundaries");
|
||||
ui.radio_value(break_anywhere, true, "anywhere");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(overflow_character, None, "None");
|
||||
ui.selectable_value(overflow_character, Some('…'), "…");
|
||||
ui.selectable_value(overflow_character, Some('—'), "—");
|
||||
ui.selectable_value(overflow_character, Some('-'), " - ");
|
||||
ui.label("Overflow character");
|
||||
});
|
||||
|
||||
let mut job =
|
||||
LayoutJob::single_section(crate::LOREM_IPSUM_LONG.to_owned(), TextFormat::default());
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: *max_rows,
|
||||
break_anywhere: *break_anywhere,
|
||||
overflow_character: *overflow_character,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
ui.label(job); // `Label` overrides some of the wrapping settings, e.g. wrap width
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ pub mod strip_demo;
|
|||
pub mod table_demo;
|
||||
pub mod tests;
|
||||
pub mod text_edit;
|
||||
pub mod text_layout;
|
||||
pub mod toggle_switch;
|
||||
pub mod widget_gallery;
|
||||
pub mod window_options;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/// Showcase [`TextEdit`].
|
||||
/// Showcase [`egui::TextEdit`].
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct TextEdit {
|
||||
pub struct TextEditDemo {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl Default for TextEdit {
|
||||
impl Default for TextEditDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: "Edit this text".to_owned(),
|
||||
|
|
@ -14,7 +14,7 @@ impl Default for TextEdit {
|
|||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TextEdit {
|
||||
impl super::Demo for TextEditDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"🖹 TextEdit"
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ impl super::Demo for TextEdit {
|
|||
}
|
||||
}
|
||||
|
||||
impl super::View for TextEdit {
|
||||
impl super::View for TextEditDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self { text } = self;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
/// Showcase text layout
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct TextLayoutDemo {
|
||||
break_anywhere: bool,
|
||||
max_rows: usize,
|
||||
overflow_character: Option<char>,
|
||||
extra_letter_spacing_pixels: i32,
|
||||
line_height_pixels: u32,
|
||||
}
|
||||
|
||||
impl Default for TextLayoutDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_rows: 3,
|
||||
break_anywhere: true,
|
||||
overflow_character: Some('…'),
|
||||
extra_letter_spacing_pixels: 0,
|
||||
line_height_pixels: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TextLayoutDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"🖹 Text Layout"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(true)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for TextLayoutDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self {
|
||||
break_anywhere,
|
||||
max_rows,
|
||||
overflow_character,
|
||||
extra_letter_spacing_pixels,
|
||||
line_height_pixels,
|
||||
} = self;
|
||||
|
||||
use egui::text::LayoutJob;
|
||||
|
||||
let pixels_per_point = ui.ctx().pixels_per_point();
|
||||
let points_per_pixel = 1.0 / pixels_per_point;
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file_line!());
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
egui::Grid::new("TextLayoutDemo")
|
||||
.num_columns(2)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Max rows:");
|
||||
ui.add(egui::DragValue::new(max_rows));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Line-break:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(break_anywhere, false, "word boundaries");
|
||||
ui.radio_value(break_anywhere, true, "anywhere");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Overflow character:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(overflow_character, None, "None");
|
||||
ui.selectable_value(overflow_character, Some('…'), "…");
|
||||
ui.selectable_value(overflow_character, Some('—'), "—");
|
||||
ui.selectable_value(overflow_character, Some('-'), " - ");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Extra letter spacing:");
|
||||
ui.add(egui::DragValue::new(extra_letter_spacing_pixels).suffix(" pixels"));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Line height:");
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.selectable_label(*line_height_pixels == 0, "Default")
|
||||
.clicked()
|
||||
{
|
||||
*line_height_pixels = 0;
|
||||
}
|
||||
if ui
|
||||
.selectable_label(*line_height_pixels != 0, "Custom")
|
||||
.clicked()
|
||||
{
|
||||
*line_height_pixels = (pixels_per_point * 20.0).round() as _;
|
||||
}
|
||||
if *line_height_pixels != 0 {
|
||||
ui.add(egui::DragValue::new(line_height_pixels).suffix(" pixels"));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
let extra_letter_spacing = points_per_pixel * *extra_letter_spacing_pixels as f32;
|
||||
let line_height =
|
||||
(*line_height_pixels != 0).then_some(points_per_pixel * *line_height_pixels as f32);
|
||||
|
||||
let mut job = LayoutJob::single_section(
|
||||
crate::LOREM_IPSUM_LONG.to_owned(),
|
||||
egui::TextFormat {
|
||||
extra_letter_spacing,
|
||||
line_height,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
job.wrap = egui::text::TextWrapping {
|
||||
max_rows: *max_rows,
|
||||
break_anywhere: *break_anywhere,
|
||||
overflow_character: *overflow_character,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// NOTE: `Label` overrides some of the wrapping settings, e.g. wrap width
|
||||
ui.label(job);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -188,5 +188,6 @@ fn format_from_style(
|
|||
underline,
|
||||
strikethrough,
|
||||
valign,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,17 @@
|
|||
//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es
|
||||
//! that you can then paint using some graphics API of your choice (e.g. OpenGL).
|
||||
//!
|
||||
//! ## Coordinate system
|
||||
//! The left-top corner of the screen is `(0.0, 0.0)`,
|
||||
//! with X increasing to the right and Y increasing downwards.
|
||||
//!
|
||||
//! `epaint` uses logical _points_ as its coordinate system.
|
||||
//! Those related to physical _pixels_ by the `pixels_per_point` scale factor.
|
||||
//! For example, a high-dpi screeen can have `pixels_per_point = 2.0`,
|
||||
//! meaning there are two physical screen pixels for each logical point.
|
||||
//!
|
||||
//! Angles are in radians, and are measured clockwise from the X-axis, which has angle=0.
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,11 +113,15 @@ 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 extra_letter_spacing = section.format.extra_letter_spacing;
|
||||
|
||||
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,19 +132,20 @@ 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 {
|
||||
if let Some(last_glyph_id) = last_glyph_id {
|
||||
paragraph.cursor_x += font_impl.pair_kerning(last_glyph_id, glyph_info.id);
|
||||
paragraph.cursor_x += extra_letter_spacing;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -328,8 +333,12 @@ fn replace_last_glyph_with_overflow_character(
|
|||
};
|
||||
|
||||
let section = &job.sections[last_glyph.section_index as usize];
|
||||
let extra_letter_spacing = section.format.extra_letter_spacing;
|
||||
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);
|
||||
|
|
@ -338,23 +347,29 @@ 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 -= 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;
|
||||
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;
|
||||
|
||||
// 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 += 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();
|
||||
|
||||
|
|
@ -474,7 +489,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;
|
||||
|
||||
|
|
@ -484,10 +499,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 {
|
||||
|
|
@ -503,11 +518,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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -221,11 +221,28 @@ 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.
|
||||
///
|
||||
/// For even text it is recommended you round this to an even number of _pixels_.
|
||||
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.
|
||||
///
|
||||
/// For even text it is recommended you round this to an even number of _pixels_.
|
||||
pub line_height: Option<f32>,
|
||||
|
||||
/// Text color
|
||||
pub color: Color32,
|
||||
|
||||
|
|
@ -248,6 +265,8 @@ impl Default for TextFormat {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
font_id: FontId::default(),
|
||||
extra_letter_spacing: 0.0,
|
||||
line_height: None,
|
||||
color: Color32::GRAY,
|
||||
background: Color32::TRANSPARENT,
|
||||
italics: false,
|
||||
|
|
@ -258,6 +277,34 @@ impl Default for TextFormat {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for TextFormat {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
let Self {
|
||||
font_id,
|
||||
extra_letter_spacing,
|
||||
line_height,
|
||||
color,
|
||||
background,
|
||||
italics,
|
||||
underline,
|
||||
strikethrough,
|
||||
valign,
|
||||
} = 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);
|
||||
underline.hash(state);
|
||||
strikethrough.hash(state);
|
||||
valign.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextFormat {
|
||||
#[inline]
|
||||
pub fn simple(font_id: FontId, color: Color32) -> Self {
|
||||
|
|
@ -486,10 +533,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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue