Exclude `\n` when splitting `Galley`s (#7316)
* Follow up to #7146 Previously when galleys were splitted, each exept the last had an extra empty row that had to be removed when they were concated. This changes it to remove the `\n` from the layout jobs when splitting.
This commit is contained in:
parent
a7f14ca176
commit
207e71c2ae
|
|
@ -84,7 +84,7 @@ impl<'a> AtomKind<'a> {
|
||||||
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
||||||
let galley =
|
let galley =
|
||||||
text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button);
|
text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button);
|
||||||
(galley.intrinsic_size, SizedAtomKind::Text(galley))
|
(galley.intrinsic_size(), SizedAtomKind::Text(galley))
|
||||||
}
|
}
|
||||||
AtomKind::Image(image) => {
|
AtomKind::Image(image) => {
|
||||||
let size = image.load_and_calc_size(ui, available_size);
|
let size = image.load_and_calc_size(ui, available_size);
|
||||||
|
|
|
||||||
|
|
@ -825,7 +825,7 @@ impl GalleyCache {
|
||||||
let job = Arc::new(job);
|
let job = Arc::new(job);
|
||||||
if allow_split_paragraphs && should_cache_each_paragraph_individually(&job) {
|
if allow_split_paragraphs && should_cache_each_paragraph_individually(&job) {
|
||||||
let (child_galleys, child_hashes) =
|
let (child_galleys, child_hashes) =
|
||||||
self.layout_each_paragraph_individuallly(fonts, &job);
|
self.layout_each_paragraph_individually(fonts, &job);
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
child_hashes.len(),
|
child_hashes.len(),
|
||||||
child_galleys.len(),
|
child_galleys.len(),
|
||||||
|
|
@ -869,7 +869,7 @@ impl GalleyCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split on `\n` and lay out (and cache) each paragraph individually.
|
/// Split on `\n` and lay out (and cache) each paragraph individually.
|
||||||
fn layout_each_paragraph_individuallly(
|
fn layout_each_paragraph_individually(
|
||||||
&mut self,
|
&mut self,
|
||||||
fonts: &mut FontsImpl,
|
fonts: &mut FontsImpl,
|
||||||
job: &LayoutJob,
|
job: &LayoutJob,
|
||||||
|
|
@ -884,9 +884,11 @@ impl GalleyCache {
|
||||||
|
|
||||||
while start < job.text.len() {
|
while start < job.text.len() {
|
||||||
let is_first_paragraph = start == 0;
|
let is_first_paragraph = start == 0;
|
||||||
|
// `end` will not include the `\n` since we don't want to create an empty row in our
|
||||||
|
// split galley
|
||||||
let end = job.text[start..]
|
let end = job.text[start..]
|
||||||
.find('\n')
|
.find('\n')
|
||||||
.map_or(job.text.len(), |i| start + i + 1);
|
.map_or(job.text.len(), |i| start + i);
|
||||||
|
|
||||||
let mut paragraph_job = LayoutJob {
|
let mut paragraph_job = LayoutJob {
|
||||||
text: job.text[start..end].to_owned(),
|
text: job.text[start..end].to_owned(),
|
||||||
|
|
@ -920,7 +922,7 @@ impl GalleyCache {
|
||||||
if section_range.end <= start {
|
if section_range.end <= start {
|
||||||
// The section is behind us
|
// The section is behind us
|
||||||
current_section += 1;
|
current_section += 1;
|
||||||
} else if end <= section_range.start {
|
} else if end < section_range.start {
|
||||||
break; // Haven't reached this one yet.
|
break; // Haven't reached this one yet.
|
||||||
} else {
|
} else {
|
||||||
// Section range overlaps with paragraph range
|
// Section range overlaps with paragraph range
|
||||||
|
|
@ -953,10 +955,6 @@ impl GalleyCache {
|
||||||
// This will prevent us from invalidating cache entries unnecessarily:
|
// This will prevent us from invalidating cache entries unnecessarily:
|
||||||
if max_rows_remaining != usize::MAX {
|
if max_rows_remaining != usize::MAX {
|
||||||
max_rows_remaining -= galley.rows.len();
|
max_rows_remaining -= galley.rows.len();
|
||||||
// Ignore extra trailing row, see merging `Galley::concat` for more details.
|
|
||||||
if end < job.text.len() && !galley.elided {
|
|
||||||
max_rows_remaining += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let elided = galley.elided;
|
let elided = galley.elided;
|
||||||
|
|
@ -965,7 +963,7 @@ impl GalleyCache {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = end;
|
start = end + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
(child_galleys, child_hashes)
|
(child_galleys, child_hashes)
|
||||||
|
|
@ -1091,6 +1089,29 @@ mod tests {
|
||||||
Color32::WHITE,
|
Color32::WHITE,
|
||||||
f32::INFINITY,
|
f32::INFINITY,
|
||||||
),
|
),
|
||||||
|
{
|
||||||
|
let mut job = LayoutJob::simple(
|
||||||
|
"hi".to_owned(),
|
||||||
|
FontId::default(),
|
||||||
|
Color32::WHITE,
|
||||||
|
f32::INFINITY,
|
||||||
|
);
|
||||||
|
job.append("\n", 0.0, TextFormat::default());
|
||||||
|
job.append("\n", 0.0, TextFormat::default());
|
||||||
|
job.append("world", 0.0, TextFormat::default());
|
||||||
|
job.wrap.max_rows = 2;
|
||||||
|
job
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let mut job = LayoutJob::simple(
|
||||||
|
"Test text with a lot of words\n and a newline.".to_owned(),
|
||||||
|
FontId::new(14.0, FontFamily::Monospace),
|
||||||
|
Color32::WHITE,
|
||||||
|
40.0,
|
||||||
|
);
|
||||||
|
job.first_row_min_height = 30.0;
|
||||||
|
job
|
||||||
|
},
|
||||||
LayoutJob::simple(
|
LayoutJob::simple(
|
||||||
"This some text that may be long.\nDet kanske också finns lite ÅÄÖ här.".to_owned(),
|
"This some text that may be long.\nDet kanske också finns lite ÅÄÖ här.".to_owned(),
|
||||||
FontId::new(14.0, FontFamily::Proportional),
|
FontId::new(14.0, FontFamily::Proportional),
|
||||||
|
|
@ -1213,7 +1234,7 @@ mod tests {
|
||||||
let text = job.text.clone();
|
let text = job.text.clone();
|
||||||
let galley_unwrapped = layout(&mut fonts, job.into());
|
let galley_unwrapped = layout(&mut fonts, job.into());
|
||||||
|
|
||||||
let intrinsic_size = galley_wrapped.intrinsic_size;
|
let intrinsic_size = galley_wrapped.intrinsic_size();
|
||||||
let unwrapped_size = galley_unwrapped.size();
|
let unwrapped_size = galley_unwrapped.size();
|
||||||
|
|
||||||
let difference = (intrinsic_size - unwrapped_size).length().abs();
|
let difference = (intrinsic_size - unwrapped_size).length().abs();
|
||||||
|
|
@ -1232,7 +1253,7 @@ mod tests {
|
||||||
format!("{unwrapped_size:.4?}"),
|
format!("{unwrapped_size:.4?}"),
|
||||||
"Unwrapped galley intrinsic size should exactly match its size. \
|
"Unwrapped galley intrinsic size should exactly match its size. \
|
||||||
{:.8?} vs {:8?}",
|
{:.8?} vs {:8?}",
|
||||||
galley_unwrapped.intrinsic_size,
|
galley_unwrapped.intrinsic_size(),
|
||||||
galley_unwrapped.size(),
|
galley_unwrapped.size(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,20 +204,12 @@ fn calculate_intrinsic_size(
|
||||||
) -> Vec2 {
|
) -> Vec2 {
|
||||||
let mut intrinsic_size = Vec2::ZERO;
|
let mut intrinsic_size = Vec2::ZERO;
|
||||||
for (idx, paragraph) in paragraphs.iter().enumerate() {
|
for (idx, paragraph) in paragraphs.iter().enumerate() {
|
||||||
if paragraph.glyphs.is_empty() {
|
let width = paragraph
|
||||||
if idx == 0 {
|
.glyphs
|
||||||
intrinsic_size.y += point_scale.round_to_pixel(paragraph.empty_paragraph_height);
|
.last()
|
||||||
}
|
.map(|l| l.max_x())
|
||||||
continue;
|
.unwrap_or_default();
|
||||||
}
|
intrinsic_size.x = f32::max(intrinsic_size.x, width);
|
||||||
intrinsic_size.x = f32::max(
|
|
||||||
paragraph
|
|
||||||
.glyphs
|
|
||||||
.last()
|
|
||||||
.map(|l| l.max_x())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
intrinsic_size.x,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut height = paragraph
|
let mut height = paragraph
|
||||||
.glyphs
|
.glyphs
|
||||||
|
|
@ -253,7 +245,7 @@ fn rows_from_paragraphs(
|
||||||
|
|
||||||
if paragraph.glyphs.is_empty() {
|
if paragraph.glyphs.is_empty() {
|
||||||
rows.push(PlacedRow {
|
rows.push(PlacedRow {
|
||||||
pos: Pos2::ZERO,
|
pos: pos2(0.0, f32::NAN),
|
||||||
row: Arc::new(Row {
|
row: Arc::new(Row {
|
||||||
section_index_at_start: paragraph.section_index_at_start,
|
section_index_at_start: paragraph.section_index_at_start,
|
||||||
glyphs: vec![],
|
glyphs: vec![],
|
||||||
|
|
@ -659,12 +651,12 @@ fn galley_from_rows(
|
||||||
let mut cursor_y = 0.0;
|
let mut cursor_y = 0.0;
|
||||||
|
|
||||||
for placed_row in &mut rows {
|
for placed_row in &mut rows {
|
||||||
let mut max_row_height = first_row_min_height.max(placed_row.rect().height());
|
let mut max_row_height = first_row_min_height.at_least(placed_row.height());
|
||||||
let row = Arc::make_mut(&mut placed_row.row);
|
let row = Arc::make_mut(&mut placed_row.row);
|
||||||
|
|
||||||
first_row_min_height = 0.0;
|
first_row_min_height = 0.0;
|
||||||
for glyph in &row.glyphs {
|
for glyph in &row.glyphs {
|
||||||
max_row_height = max_row_height.max(glyph.line_height);
|
max_row_height = max_row_height.at_least(glyph.line_height);
|
||||||
}
|
}
|
||||||
max_row_height = point_scale.round_to_pixel(max_row_height);
|
max_row_height = point_scale.round_to_pixel(max_row_height);
|
||||||
|
|
||||||
|
|
@ -1212,4 +1204,72 @@ mod tests {
|
||||||
assert_eq!(row.pos, Pos2::ZERO);
|
assert_eq!(row.pos, Pos2::ZERO);
|
||||||
assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x());
|
assert_eq!(row.rect().max.x, row.glyphs.last().unwrap().max_x());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_row() {
|
||||||
|
let mut fonts = FontsImpl::new(
|
||||||
|
1.0,
|
||||||
|
1024,
|
||||||
|
AlphaFromCoverage::default(),
|
||||||
|
FontDefinitions::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let font_id = FontId::default();
|
||||||
|
let font_height = fonts.font(&font_id).row_height();
|
||||||
|
|
||||||
|
let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY);
|
||||||
|
|
||||||
|
let galley = layout(&mut fonts, job.into());
|
||||||
|
|
||||||
|
assert_eq!(galley.rows.len(), 1, "Expected one row");
|
||||||
|
assert_eq!(
|
||||||
|
galley.rows[0].row.glyphs.len(),
|
||||||
|
0,
|
||||||
|
"Expected no glyphs in the empty row"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
galley.size(),
|
||||||
|
Vec2::new(0.0, font_height.round()),
|
||||||
|
"Unexpected galley size"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
galley.intrinsic_size(),
|
||||||
|
Vec2::new(0.0, font_height.round()),
|
||||||
|
"Unexpected intrinsic size"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_end_with_newline() {
|
||||||
|
let mut fonts = FontsImpl::new(
|
||||||
|
1.0,
|
||||||
|
1024,
|
||||||
|
AlphaFromCoverage::default(),
|
||||||
|
FontDefinitions::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let font_id = FontId::default();
|
||||||
|
let font_height = fonts.font(&font_id).row_height();
|
||||||
|
|
||||||
|
let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY);
|
||||||
|
|
||||||
|
let galley = layout(&mut fonts, job.into());
|
||||||
|
|
||||||
|
assert_eq!(galley.rows.len(), 2, "Expected two rows");
|
||||||
|
assert_eq!(
|
||||||
|
galley.rows[1].row.glyphs.len(),
|
||||||
|
0,
|
||||||
|
"Expected no glyphs in the empty row"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
galley.size().round(),
|
||||||
|
Vec2::new(17.0, font_height.round() * 2.0),
|
||||||
|
"Unexpected galley size"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
galley.intrinsic_size().round(),
|
||||||
|
Vec2::new(17.0, font_height.round() * 2.0),
|
||||||
|
"Unexpected intrinsic size"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -561,11 +561,7 @@ pub struct Galley {
|
||||||
/// tessellation.
|
/// tessellation.
|
||||||
pub pixels_per_point: f32,
|
pub pixels_per_point: f32,
|
||||||
|
|
||||||
/// This is the size that a non-wrapped, non-truncated, non-justified version of the text
|
pub(crate) intrinsic_size: Vec2,
|
||||||
/// would have.
|
|
||||||
///
|
|
||||||
/// Useful for advanced layouting.
|
|
||||||
pub intrinsic_size: Vec2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
|
@ -801,6 +797,21 @@ impl Galley {
|
||||||
self.rect.size()
|
self.rect.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the size that a non-wrapped, non-truncated, non-justified version of the text
|
||||||
|
/// would have.
|
||||||
|
///
|
||||||
|
/// Useful for advanced layouting.
|
||||||
|
#[inline]
|
||||||
|
pub fn intrinsic_size(&self) -> Vec2 {
|
||||||
|
// We do the rounding here instead of in `round_output_to_gui` so that rounding
|
||||||
|
// errors don't accumulate when concatenating multiple galleys.
|
||||||
|
if self.job.round_output_to_gui {
|
||||||
|
self.intrinsic_size.round_ui()
|
||||||
|
} else {
|
||||||
|
self.intrinsic_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn round_output_to_gui(&mut self) {
|
pub(crate) fn round_output_to_gui(&mut self) {
|
||||||
for placed_row in &mut self.rows {
|
for placed_row in &mut self.rows {
|
||||||
// Optimization: only call `make_mut` if necessary (can cause a deep clone)
|
// Optimization: only call `make_mut` if necessary (can cause a deep clone)
|
||||||
|
|
@ -827,8 +838,6 @@ impl Galley {
|
||||||
.at_most(rect.min.x + self.job.wrap.max_width)
|
.at_most(rect.min.x + self.job.wrap.max_width)
|
||||||
.floor_ui();
|
.floor_ui();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.intrinsic_size = self.intrinsic_size.round_ui();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append each galley under the previous one.
|
/// Append each galley under the previous one.
|
||||||
|
|
@ -849,32 +858,28 @@ impl Galley {
|
||||||
|
|
||||||
for (i, galley) in galleys.iter().enumerate() {
|
for (i, galley) in galleys.iter().enumerate() {
|
||||||
let current_y_offset = merged_galley.rect.height();
|
let current_y_offset = merged_galley.rect.height();
|
||||||
|
let is_last_galley = i + 1 == galleys.len();
|
||||||
|
|
||||||
let mut rows = galley.rows.iter();
|
merged_galley
|
||||||
// As documented in `Row::ends_with_newline`, a '\n' will always create a
|
.rows
|
||||||
// new `Row` immediately below the current one. Here it doesn't make sense
|
.extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| {
|
||||||
// for us to append this new row so we just ignore it.
|
let new_pos = placed_row.pos + current_y_offset * Vec2::Y;
|
||||||
let is_last_row = i + 1 == galleys.len();
|
let new_pos = new_pos.round_to_pixels(pixels_per_point);
|
||||||
if !is_last_row && !galley.elided {
|
merged_galley.mesh_bounds = merged_galley
|
||||||
let popped = rows.next_back();
|
.mesh_bounds
|
||||||
debug_assert_eq!(popped.unwrap().row.glyphs.len(), 0, "Bug in Galley::concat");
|
.union(placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2()));
|
||||||
}
|
merged_galley.rect = merged_galley
|
||||||
|
.rect
|
||||||
|
.union(Rect::from_min_size(new_pos, placed_row.size));
|
||||||
|
|
||||||
merged_galley.rows.extend(rows.map(|placed_row| {
|
let mut row = placed_row.row.clone();
|
||||||
let new_pos = placed_row.pos + current_y_offset * Vec2::Y;
|
let is_last_row_in_galley = row_idx + 1 == galley.rows.len();
|
||||||
let new_pos = new_pos.round_to_pixels(pixels_per_point);
|
if !is_last_galley && is_last_row_in_galley {
|
||||||
merged_galley.mesh_bounds = merged_galley
|
// Since we remove the `\n` when splitting rows, we need to add it back here
|
||||||
.mesh_bounds
|
Arc::make_mut(&mut row).ends_with_newline = true;
|
||||||
.union(placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2()));
|
}
|
||||||
merged_galley.rect = merged_galley
|
super::PlacedRow { pos: new_pos, row }
|
||||||
.rect
|
}));
|
||||||
.union(Rect::from_min_size(new_pos, placed_row.size));
|
|
||||||
|
|
||||||
super::PlacedRow {
|
|
||||||
pos: new_pos,
|
|
||||||
row: placed_row.row.clone(),
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
merged_galley.num_vertices += galley.num_vertices;
|
merged_galley.num_vertices += galley.num_vertices;
|
||||||
merged_galley.num_indices += galley.num_indices;
|
merged_galley.num_indices += galley.num_indices;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue