slint/internal/core/textlayout/fragments.rs
Simon Hausmann d69b4b81ba Simplify glyph handling in the software renderer
Remove platform glyph abstraction, just use non-zero u16 as glyph index
like everyone else.  As a bonus, this reduces the memory consumption of
the glyph buffers per glyph from a size of a pointer to the size of the
u16.
2022-12-16 18:20:12 +01:00

301 lines
10 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
use core::ops::Range;
use euclid::num::Zero;
use super::glyphclusters::GlyphClusterIterator;
use super::{BreakOpportunity, LineBreakIterator, ShapeBuffer};
#[derive(Debug, PartialEq, Eq, Default)]
pub struct TextFragment<Length> {
pub byte_range: Range<usize>,
pub glyph_range: Range<usize>,
pub width: Length,
pub trailing_whitespace_width: Length,
pub trailing_mandatory_break: bool,
}
#[derive(Clone)]
pub struct TextFragmentIterator<'a, Length> {
line_breaks: LineBreakIterator<'a>,
glyph_clusters: GlyphClusterIterator<'a, Length>,
text_len: usize,
pub break_anywhere: bool,
}
impl<'a, Length> TextFragmentIterator<'a, Length> {
pub fn new(text: &'a str, shape_buffer: &'a ShapeBuffer<Length>) -> Self {
Self {
line_breaks: LineBreakIterator::new(text),
glyph_clusters: GlyphClusterIterator::new(text, shape_buffer),
text_len: text.len(),
break_anywhere: false,
}
}
}
impl<'a, Length: Clone + Default + core::ops::AddAssign + Zero + Copy> Iterator
for TextFragmentIterator<'a, Length>
{
type Item = TextFragment<Length>;
fn next(&mut self) -> Option<Self::Item> {
let first_glyph_cluster = self.glyph_clusters.next()?;
let mut fragment = Self::Item::default();
let next_break_offset = if self.break_anywhere {
0
} else if let Some((next_break_offset, break_type)) = self.line_breaks.next() {
if matches!(break_type, BreakOpportunity::Mandatory) {
fragment.trailing_mandatory_break = true;
}
next_break_offset
} else {
self.text_len
};
if first_glyph_cluster.is_whitespace {
fragment.trailing_whitespace_width = first_glyph_cluster.width;
} else {
fragment.width = first_glyph_cluster.width;
fragment.byte_range = first_glyph_cluster.byte_range.clone();
}
let mut last_glyph_cluster = first_glyph_cluster.clone();
while last_glyph_cluster.byte_range.end < next_break_offset {
let next_glyph_cluster = match self.glyph_clusters.next() {
Some(cluster) => cluster,
None => break,
};
if next_glyph_cluster.is_line_or_paragraph_separator {
break;
}
if next_glyph_cluster.is_whitespace {
fragment.trailing_whitespace_width += next_glyph_cluster.width;
} else {
// transition from whitespace to characters by treating previous trailing whitespace
// as regular characters
if last_glyph_cluster.is_whitespace {
fragment.width += core::mem::take(&mut fragment.trailing_whitespace_width);
fragment.width += next_glyph_cluster.width;
fragment.byte_range.end = next_glyph_cluster.byte_range.end;
} else {
fragment.width += next_glyph_cluster.width;
fragment.byte_range.end = next_glyph_cluster.byte_range.end;
}
}
last_glyph_cluster = next_glyph_cluster.clone();
}
fragment.glyph_range = Range {
start: first_glyph_cluster.glyph_range.start,
end: last_glyph_cluster.glyph_range.end,
};
Some(fragment)
}
}
#[cfg(test)]
use super::{FixedTestFont, TextLayout};
#[test]
fn fragment_iterator_simple() {
let font = FixedTestFont;
let text = "H WX";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
let expected = vec![
TextFragment {
byte_range: Range { start: 0, end: 1 },
glyph_range: Range { start: 0, end: 2 },
width: 10.,
trailing_whitespace_width: 10.,
trailing_mandatory_break: false,
},
TextFragment {
byte_range: Range { start: 2, end: text.len() },
glyph_range: Range { start: 2, end: text.len() },
width: 20.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
];
assert_eq!(fragments, expected);
}
#[test]
fn fragment_iterator_simple_v2() {
let font = FixedTestFont;
let text = "Hello World";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
let expected = vec![
TextFragment {
byte_range: Range { start: 0, end: 5 },
glyph_range: Range { start: 0, end: 6 },
width: 50.,
trailing_whitespace_width: 10.,
trailing_mandatory_break: false,
},
TextFragment {
byte_range: Range { start: 6, end: text.len() },
glyph_range: Range { start: 6, end: text.len() },
width: 10. * (text.len() - 6) as f32,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
];
assert_eq!(fragments, expected);
}
#[test]
fn fragment_iterator_forced_break() {
let font = FixedTestFont;
let text = "H\nW";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
assert_eq!(
fragments,
vec![
TextFragment {
byte_range: Range { start: 0, end: 1 },
glyph_range: Range { start: 0, end: 1 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: true,
},
TextFragment {
byte_range: Range { start: 2, end: 3 },
glyph_range: Range { start: 2, end: 3 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
]
);
}
#[test]
fn fragment_iterator_forced_break_multi() {
let font = FixedTestFont;
let text = "H\n\n\nW";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
assert_eq!(
fragments,
vec![
TextFragment {
byte_range: Range { start: 0, end: 1 },
glyph_range: Range { start: 0, end: 1 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: true,
},
TextFragment {
byte_range: Range { start: 0, end: 0 },
glyph_range: Range { start: 2, end: 3 },
width: 0.,
trailing_whitespace_width: 10.,
trailing_mandatory_break: true,
},
TextFragment {
byte_range: Range { start: 0, end: 0 },
glyph_range: Range { start: 3, end: 4 },
width: 0.,
trailing_whitespace_width: 10.,
trailing_mandatory_break: true,
},
TextFragment {
byte_range: Range { start: 4, end: 5 },
glyph_range: Range { start: 4, end: 5 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
]
);
}
#[test]
fn fragment_iterator_nbsp() {
let font = FixedTestFont;
let text = "X H\u{00a0}W";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let fragments = TextFragmentIterator::new(text, &shape_buffer).collect::<Vec<_>>();
assert_eq!(
fragments,
vec![
TextFragment {
byte_range: Range { start: 0, end: 1 },
glyph_range: Range { start: 0, end: 2 },
width: 10.,
trailing_whitespace_width: 10.,
trailing_mandatory_break: false,
},
TextFragment {
byte_range: Range { start: 2, end: 6 },
glyph_range: Range { start: 2, end: 5 },
width: 30.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
}
]
);
}
#[test]
fn fragment_iterator_break_anywhere() {
let font = FixedTestFont;
let text = "AB\nCD\nEF";
let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
let mut fragments = TextFragmentIterator::new(text, &shape_buffer);
assert_eq!(
fragments.next(),
Some(TextFragment {
byte_range: Range { start: 0, end: 2 },
glyph_range: Range { start: 0, end: 2 },
width: 20.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: true,
})
);
assert_eq!(
fragments.next(),
Some(TextFragment {
byte_range: Range { start: 3, end: 5 },
glyph_range: Range { start: 3, end: 5 },
width: 20.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: true,
},)
);
fragments.break_anywhere = true;
let last_two = fragments.by_ref().take(2).collect::<Vec<_>>();
assert_eq!(
last_two,
vec![
TextFragment {
byte_range: Range { start: 6, end: 7 },
glyph_range: Range { start: 6, end: 7 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
TextFragment {
byte_range: Range { start: 7, end: 8 },
glyph_range: Range { start: 7, end: 8 },
width: 10.,
trailing_whitespace_width: 0.,
trailing_mandatory_break: false,
},
]
);
}