mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-12 21:32:52 +00:00
Text layout: a wrapped line finds its glyphs and end caret by bisection
* Text layout: a wrapped line finds its glyphs and end caret by bisection * Fix merge of layout tests
This commit is contained in:
1 parent
6e1dd08147
commit
9f93ac03fa
2 files changed
+52
-4
No files matched your search
@@ -378,6 +378,34 @@ struct Item {
|
||||
range: Range<usize>,
|
||||
rtl: bool,
|
||||
glyphs: Vec<ShapedGlyph>,
|
||||
/// Whether the glyphs' clusters never decrease (`Some(true)`) or never increase
|
||||
/// (`Some(false)`) along `glyphs`: the glyphs of a cluster range are then contiguous.
|
||||
cluster_order: Option<bool>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
fn new(range: Range<usize>, rtl: bool, glyphs: Vec<ShapedGlyph>) -> Self {
|
||||
let cluster_order = if glyphs.windows(2).all(|w| w[0].cluster <= w[1].cluster) {
|
||||
Some(true)
|
||||
} else if glyphs.windows(2).all(|w| w[0].cluster >= w[1].cluster) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self { range, rtl, glyphs, cluster_order }
|
||||
}
|
||||
|
||||
/// The glyphs whose cluster lies in `run`, in glyph order, found by bisection when the clusters
|
||||
/// are ordered (a line of a long paragraph otherwise scanned the whole item).
|
||||
fn glyphs_in(&self, run: &Range<usize>) -> impl Iterator<Item = &ShapedGlyph> {
|
||||
let g = &self.glyphs;
|
||||
let (lo, hi) = match self.cluster_order {
|
||||
Some(true) => (g.partition_point(|x| x.cluster < run.start), g.partition_point(|x| x.cluster < run.end)),
|
||||
Some(false) => (g.partition_point(|x| x.cluster >= run.end), g.partition_point(|x| x.cluster >= run.start)),
|
||||
None => (0, g.len()),
|
||||
};
|
||||
g[lo..hi].iter().filter(|x| run.contains(&x.cluster))
|
||||
}
|
||||
}
|
||||
|
||||
fn upper_single(c: char) -> char {
|
||||
@@ -929,7 +957,7 @@ impl<'a> Para<'a> {
|
||||
idx.reverse();
|
||||
}
|
||||
for k in idx {
|
||||
for g in self.items[k].glyphs.iter().filter(|g| run.contains(&g.cluster)) {
|
||||
for g in self.items[k].glyphs_in(&run) {
|
||||
let st = &self.runs[g.run].1;
|
||||
let x0 = pen;
|
||||
pen += g.pre;
|
||||
@@ -990,9 +1018,10 @@ impl<'a> Para<'a> {
|
||||
}
|
||||
}
|
||||
// end of line caret
|
||||
let end_x = match self.chars.iter().rev().find(|c| c.0 < r.end && c.0 >= r.start) {
|
||||
let last_char = self.chars.partition_point(|c| c.0 < r.end).checked_sub(1).and_then(|i| self.chars.get(i)).filter(|c| c.0 >= r.start);
|
||||
let end_x = match last_char {
|
||||
Some(c) => {
|
||||
let cs = cluster_starts.iter().rev().find(|s| **s <= c.0).copied();
|
||||
let cs = cluster_starts.partition_point(|s| *s <= c.0).checked_sub(1).and_then(|i| cluster_starts.get(i)).copied();
|
||||
match cs.and_then(|s| extents.get(&s)) {
|
||||
Some(&(x0, x1, rtl)) => {
|
||||
if rtl {
|
||||
@@ -1095,7 +1124,7 @@ fn build_items(text: &str, chars: &[CharInfo], runs: &[(Range<usize>, TextStyle)
|
||||
let mut glyphs = shape_item(&sub, rtl, face, size, style, si, form);
|
||||
space_item(&mut glyphs, style, text, true);
|
||||
let end = if j < chars.len() { chars[j].0 } else { text.len() };
|
||||
items.push(Item { range: chars[i].0..end, rtl, glyphs });
|
||||
items.push(Item::new(chars[i].0..end, rtl, glyphs));
|
||||
i = j;
|
||||
}
|
||||
items
|
||||
|
||||
@@ -506,6 +506,25 @@ fn a_later_vertical_run_keeps_the_first_line_baseline() {
|
||||
assert!((mixed.lines[0].baseline - plain.lines[0].baseline).abs() < 1e-3, "{} vs {}", mixed.lines[0].baseline, plain.lines[0].baseline);
|
||||
}
|
||||
|
||||
/// `Item::glyphs_in` finds a cluster range by bisection when the clusters are ordered, and agrees
|
||||
/// with a full scan for ascending, descending and unordered clusters.
|
||||
#[test]
|
||||
fn item_glyphs_in_matches_a_full_scan() {
|
||||
let glyph = |cluster| ShapedGlyph { face: 0, id: cluster as u32, cluster, adv: 1.0, pre: 0.0, dx: 0.0, dy: 0.0, size: 10.0, run: 0, rise: 0.0 };
|
||||
let asc: Vec<usize> = vec![0, 0, 1, 3, 3, 3, 5, 8];
|
||||
let desc: Vec<usize> = asc.iter().rev().copied().collect();
|
||||
let mixed: Vec<usize> = vec![3, 0, 5, 1, 8, 3, 0, 3];
|
||||
for (clusters, order) in [(&asc, Some(true)), (&desc, Some(false)), (&mixed, None)] {
|
||||
let item = Item::new(0..9, false, clusters.iter().map(|&c| glyph(c)).collect());
|
||||
assert_eq!(item.cluster_order, order);
|
||||
for (a, b) in [(0, 9), (0, 1), (1, 4), (3, 4), (4, 9), (6, 8), (9, 9), (2, 3)] {
|
||||
let got: Vec<usize> = item.glyphs_in(&(a..b)).map(|g| g.cluster).collect();
|
||||
let want: Vec<usize> = clusters.iter().copied().filter(|c| (a..b).contains(c)).collect();
|
||||
assert_eq!(got, want, "{clusters:?} {a}..{b}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_lookup_by_bisection_matches_the_scan() {
|
||||
let scan = |runs: &[(Range<usize>, TextStyle)], b: usize| {
|
||||
|
||||
Reference in new issue
Block a user