Skip to content

Commit cc3b362

Browse files
authored
Round text galley sizes to nearest ui point size (#4578)
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.
1 parent 66f40de commit cc3b362

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

crates/epaint/src/text/text_layout.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ fn rows_from_paragraphs(
238238
}
239239

240240
fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec<Row>, elided: &mut bool) {
241+
let wrap_width_margin = if job.round_output_size_to_nearest_ui_point {
242+
0.5
243+
} else {
244+
0.0
245+
};
246+
241247
// Keeps track of good places to insert row break if we exceed `wrap_width`.
242248
let mut row_break_candidates = RowBreakCandidates::default();
243249

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

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

256-
if job.wrap.max_width < potential_row_width {
262+
if job.wrap.max_width + wrap_width_margin < potential_row_width {
257263
// Row break:
258264

259265
if first_row_indentation > 0.0
@@ -630,7 +636,24 @@ fn galley_from_rows(
630636
num_indices += row.visuals.mesh.indices.len();
631637
}
632638

633-
let rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y));
639+
let mut rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y));
640+
641+
if job.round_output_size_to_nearest_ui_point {
642+
let did_exceed_wrap_width_by_a_lot = rect.width() > job.wrap.max_width + 1.0;
643+
644+
// We round the size to whole ui points here (not pixels!) so that the egui layout code
645+
// can have the advantage of working in integer units, avoiding rounding errors.
646+
rect.min = rect.min.round();
647+
rect.max = rect.max.round();
648+
649+
if did_exceed_wrap_width_by_a_lot {
650+
// If the user picked a too aggressive wrap width (e.g. more narrow than any individual glyph),
651+
// we should let the user know.
652+
} else {
653+
// Make sure we don't over the max wrap width the user picked:
654+
rect.max.x = rect.max.x.at_most(rect.min.x + job.wrap.max_width);
655+
}
656+
}
634657

635658
Galley {
636659
job,

crates/epaint/src/text/text_layout_types.rs

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ pub struct LayoutJob {
7474

7575
/// Justify text so that word-wrapped rows fill the whole [`TextWrapping::max_width`].
7676
pub justify: bool,
77+
78+
/// Rounding to the closest ui point (not pixel!) allows the rest of the
79+
/// layout code to run on perfect integers, avoiding rounding errors.
80+
pub round_output_size_to_nearest_ui_point: bool,
7781
}
7882

7983
impl Default for LayoutJob {
@@ -87,6 +91,7 @@ impl Default for LayoutJob {
8791
break_on_newline: true,
8892
halign: Align::LEFT,
8993
justify: false,
94+
round_output_size_to_nearest_ui_point: true,
9095
}
9196
}
9297
}
@@ -180,6 +185,7 @@ impl std::hash::Hash for LayoutJob {
180185
break_on_newline,
181186
halign,
182187
justify,
188+
round_output_size_to_nearest_ui_point,
183189
} = self;
184190

185191
text.hash(state);
@@ -189,6 +195,7 @@ impl std::hash::Hash for LayoutJob {
189195
break_on_newline.hash(state);
190196
halign.hash(state);
191197
justify.hash(state);
198+
round_output_size_to_nearest_ui_point.hash(state);
192199
}
193200
}
194201

0 commit comments

Comments
 (0)