Skip to content

Commit

Permalink
Round text galley sizes to nearest ui point size (emilk#4578)
Browse files Browse the repository at this point in the history
Previously, many labels had non-integer widths. This lead to rounding
errors.

This was most notable for the new `Area` sizing code:

We would run the initial sizing pass, to measure the size of e.g. a
tooltip.
Say the tooltip contains text that was 100.123 ui points wide. With a
16pt border, that becomes 116.123, which is stored in the `Area` state
as the width. The next frame, we use that stored size as the wrapping
width. With perfect precision, we would then tell the label to wrap to
100.123 pts, which the text would _just_ fit in. However, due to
rounding errors we might end up asking it to wrap to 100.12**2** pts,
meaning the last word would now wrap and end up on the next line.

By rounding label sizes to perfect integers, we avoid such rounding
errors, and most ui elements will now end up on perfect integer point
coordinates (and `f32` can precisely express and do arithmetic on all
integers < 2^24).

Visually this has very little impact. Some labels move by a pixel here
and there, mostly for the better.
  • Loading branch information
emilk authored and hacknus committed Oct 30, 2024
1 parent 0f0adf4 commit 5a0d924
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 2 deletions.
27 changes: 25 additions & 2 deletions crates/epaint/src/text/text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ fn rows_from_paragraphs(
}

fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, elided: &mut bool) {
let wrap_width_margin = if job.round_output_size_to_nearest_ui_point {
0.5
} else {
0.0
};

// Keeps track of good places to insert row break if we exceed `wrap_width`.
let mut row_break_candidates = RowBreakCandidates::default();

Expand All @@ -253,7 +259,7 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, e

let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x;

if job.wrap.max_width < potential_row_width {
if job.wrap.max_width + wrap_width_margin < potential_row_width {
// Row break:

if first_row_indentation > 0.0
Expand Down Expand Up @@ -630,7 +636,24 @@ fn galley_from_rows(
num_indices += row.visuals.mesh.indices.len();
}

let rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y));
let mut rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y));

if job.round_output_size_to_nearest_ui_point {
let did_exceed_wrap_width_by_a_lot = rect.width() > job.wrap.max_width + 1.0;

// We round the size to whole ui points here (not pixels!) so that the egui layout code
// can have the advantage of working in integer units, avoiding rounding errors.
rect.min = rect.min.round();
rect.max = rect.max.round();

if did_exceed_wrap_width_by_a_lot {
// If the user picked a too aggressive wrap width (e.g. more narrow than any individual glyph),
// we should let the user know.
} else {
// Make sure we don't over the max wrap width the user picked:
rect.max.x = rect.max.x.at_most(rect.min.x + job.wrap.max_width);
}
}

Galley {
job,
Expand Down
7 changes: 7 additions & 0 deletions crates/epaint/src/text/text_layout_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ pub struct LayoutJob {

/// Justify text so that word-wrapped rows fill the whole [`TextWrapping::max_width`].
pub justify: bool,

/// Rounding to the closest ui point (not pixel!) allows the rest of the
/// layout code to run on perfect integers, avoiding rounding errors.
pub round_output_size_to_nearest_ui_point: bool,
}

impl Default for LayoutJob {
Expand All @@ -87,6 +91,7 @@ impl Default for LayoutJob {
break_on_newline: true,
halign: Align::LEFT,
justify: false,
round_output_size_to_nearest_ui_point: true,
}
}
}
Expand Down Expand Up @@ -180,6 +185,7 @@ impl std::hash::Hash for LayoutJob {
break_on_newline,
halign,
justify,
round_output_size_to_nearest_ui_point,
} = self;

text.hash(state);
Expand All @@ -189,6 +195,7 @@ impl std::hash::Hash for LayoutJob {
break_on_newline.hash(state);
halign.hash(state);
justify.hash(state);
round_output_size_to_nearest_ui_point.hash(state);
}
}

Expand Down

0 comments on commit 5a0d924

Please sign in to comment.