1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 12:50:04 -04:00

Add Galley::intrinsic_size and use it in AtomLayout (#7146)

- part of https://github.com/emilk/egui/issues/5762
- also allows me to simplify sizing logic in egui_flex
This commit is contained in:
Lucas Meurer
2025-07-09 08:19:04 +02:00
committed by GitHub
parent f46926aaf1
commit 508c60b2e2
9 changed files with 166 additions and 19 deletions

View File

@@ -130,10 +130,12 @@ impl TextShape {
num_vertices: _,
num_indices: _,
pixels_per_point: _,
intrinsic_size,
} = Arc::make_mut(galley);
*rect = transform.scaling * *rect;
*mesh_bounds = transform.scaling * *mesh_bounds;
*intrinsic_size = transform.scaling * *intrinsic_size;
for text::PlacedRow { pos, row } in rows {
*pos *= transform.scaling;

View File

@@ -1072,6 +1072,7 @@ mod tests {
use core::f32;
use super::*;
use crate::text::{TextWrapping, layout};
use crate::{Stroke, text::TextFormat};
use ecolor::Color32;
use emath::Align;
@@ -1183,4 +1184,60 @@ mod tests {
}
}
}
#[test]
fn test_intrinsic_size() {
let pixels_per_point = [1.0, 1.3, 2.0, 0.867];
let max_widths = [40.0, 80.0, 133.0, 200.0];
let rounded_output_to_gui = [false, true];
for pixels_per_point in pixels_per_point {
let mut fonts = FontsImpl::new(
pixels_per_point,
1024,
AlphaFromCoverage::default(),
FontDefinitions::default(),
);
for &max_width in &max_widths {
for round_output_to_gui in rounded_output_to_gui {
for mut job in jobs() {
job.wrap = TextWrapping::wrap_at_width(max_width);
job.round_output_to_gui = round_output_to_gui;
let galley_wrapped = layout(&mut fonts, job.clone().into());
job.wrap = TextWrapping::no_max_width();
let text = job.text.clone();
let galley_unwrapped = layout(&mut fonts, job.into());
let intrinsic_size = galley_wrapped.intrinsic_size;
let unwrapped_size = galley_unwrapped.size();
let difference = (intrinsic_size - unwrapped_size).length().abs();
similar_asserts::assert_eq!(
format!("{intrinsic_size:.4?}"),
format!("{unwrapped_size:.4?}"),
"Wrapped intrinsic size should almost match unwrapped size. Intrinsic: {intrinsic_size:.8?} vs unwrapped: {unwrapped_size:.8?}
Difference: {difference:.8?}
wrapped rows: {}, unwrapped rows: {}
pixels_per_point: {pixels_per_point}, text: {text:?}, max_width: {max_width}, round_output_to_gui: {round_output_to_gui}",
galley_wrapped.rows.len(),
galley_unwrapped.rows.len()
);
similar_asserts::assert_eq!(
format!("{intrinsic_size:.4?}"),
format!("{unwrapped_size:.4?}"),
"Unwrapped galley intrinsic size should exactly match its size. \
{:.8?} vs {:8?}",
galley_unwrapped.intrinsic_size,
galley_unwrapped.size(),
);
}
}
}
}
}
}

View File

@@ -82,6 +82,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
num_indices: 0,
pixels_per_point: fonts.pixels_per_point(),
elided: true,
intrinsic_size: Vec2::ZERO,
};
}
@@ -94,6 +95,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
let point_scale = PointScale::new(fonts.pixels_per_point());
let intrinsic_size = calculate_intrinsic_size(point_scale, &job, &paragraphs);
let mut elided = false;
let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided);
if elided {
@@ -124,7 +127,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
}
// Calculate the Y positions and tessellate the text:
galley_from_rows(point_scale, job, rows, elided)
galley_from_rows(point_scale, job, rows, elided, intrinsic_size)
}
// Ignores the Y coordinate.
@@ -190,6 +193,46 @@ fn layout_section(
}
}
/// Calculate the intrinsic size of the text.
///
/// The result is eventually passed to `Response::intrinsic_size`.
/// This works by calculating the size of each `Paragraph` (instead of each `Row`).
fn calculate_intrinsic_size(
point_scale: PointScale,
job: &LayoutJob,
paragraphs: &[Paragraph],
) -> Vec2 {
let mut intrinsic_size = Vec2::ZERO;
for (idx, paragraph) in paragraphs.iter().enumerate() {
if paragraph.glyphs.is_empty() {
if idx == 0 {
intrinsic_size.y += point_scale.round_to_pixel(paragraph.empty_paragraph_height);
}
continue;
}
intrinsic_size.x = f32::max(
paragraph
.glyphs
.last()
.map(|l| l.max_x())
.unwrap_or_default(),
intrinsic_size.x,
);
let mut height = paragraph
.glyphs
.iter()
.map(|g| g.line_height)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(paragraph.empty_paragraph_height);
if idx == 0 {
height = f32::max(height, job.first_row_min_height);
}
intrinsic_size.y += point_scale.round_to_pixel(height);
}
intrinsic_size
}
// Ignores the Y coordinate.
fn rows_from_paragraphs(
paragraphs: Vec<Paragraph>,
@@ -610,6 +653,7 @@ fn galley_from_rows(
job: Arc<LayoutJob>,
mut rows: Vec<PlacedRow>,
elided: bool,
intrinsic_size: Vec2,
) -> Galley {
let mut first_row_min_height = job.first_row_min_height;
let mut cursor_y = 0.0;
@@ -680,6 +724,7 @@ fn galley_from_rows(
num_vertices,
num_indices,
pixels_per_point: point_scale.pixels_per_point,
intrinsic_size,
};
if galley.job.round_output_to_gui {

View File

@@ -560,6 +560,12 @@ pub struct Galley {
/// so that we can warn if this has changed once we get to
/// tessellation.
pub pixels_per_point: f32,
/// This is the size that a non-wrapped, non-truncated, non-justified version of the text
/// would have.
///
/// Useful for advanced layouting.
pub intrinsic_size: Vec2,
}
#[derive(Clone, Debug, PartialEq)]
@@ -821,6 +827,8 @@ impl Galley {
.at_most(rect.min.x + self.job.wrap.max_width)
.floor_ui();
}
self.intrinsic_size = self.intrinsic_size.round_ui();
}
/// Append each galley under the previous one.
@@ -836,6 +844,7 @@ impl Galley {
num_vertices: 0,
num_indices: 0,
pixels_per_point,
intrinsic_size: Vec2::ZERO,
};
for (i, galley) in galleys.iter().enumerate() {
@@ -872,6 +881,9 @@ impl Galley {
// Note that if `galley.elided` is true this will be the last `Galley` in
// the vector and the loop will end.
merged_galley.elided |= galley.elided;
merged_galley.intrinsic_size.x =
f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x);
merged_galley.intrinsic_size.y += galley.intrinsic_size.y;
}
if merged_galley.job.round_output_to_gui {