use crate::ShapedLine;
use crate::types::{Affinity, Direction, RunSpan};
pub(super) fn run_width(line: &ShapedLine, range: &std::ops::Range<usize>) -> f32 {
line.glyphs
.iter()
.filter(|g| range.contains(&(g.cluster as usize)))
.map(|g| g.x_advance_lpx)
.sum()
}
pub(super) fn run_x_start(line: &ShapedLine, vi: usize) -> f32 {
line.runs[..vi]
.iter()
.map(|r| run_width(line, &r.byte_range))
.sum()
}
pub(super) fn within_run_caret_x(line: &ShapedLine, run: &RunSpan, byte: usize) -> f32 {
line.glyphs
.iter()
.filter(|g| run.byte_range.contains(&(g.cluster as usize)))
.filter(|g| match run.direction {
Direction::Ltr => (g.cluster as usize) < byte,
Direction::Rtl => (g.cluster as usize) >= byte,
})
.map(|g| g.x_advance_lpx)
.sum()
}
fn owning_run(runs: &[RunSpan], byte: usize) -> Option<usize> {
runs.iter()
.position(|r| r.byte_range.start <= byte && byte < r.byte_range.end)
.or_else(|| runs.iter().position(|r| r.byte_range.end == byte))
}
pub(super) fn owning_run_with_affinity(
runs: &[RunSpan],
byte: usize,
affinity: Affinity,
) -> Option<usize> {
match affinity {
Affinity::Downstream => owning_run(runs, byte),
Affinity::Upstream => runs
.iter()
.position(|r| r.byte_range.end == byte)
.or_else(|| owning_run(runs, byte)),
}
}
pub(super) fn run_cluster_starts(line: &ShapedLine, range: &std::ops::Range<usize>) -> Vec<usize> {
let mut starts: Vec<usize> = line
.glyphs
.iter()
.map(|g| g.cluster as usize)
.filter(|c| range.contains(c))
.collect();
starts.sort_unstable();
starts.dedup();
starts
}
pub(super) fn visual_caret_stops(line: &ShapedLine) -> Vec<(usize, Affinity, f32)> {
let mut stops: Vec<(usize, Affinity, f32)> = Vec::new();
for (vi, run) in line.runs.iter().enumerate() {
let base = run_x_start(line, vi);
let mut bytes = run_cluster_starts(line, &run.byte_range);
bytes.push(run.byte_range.end);
bytes.dedup();
if run.direction == Direction::Rtl {
bytes.reverse();
}
for b in bytes {
let affinity = if b == run.byte_range.end {
Affinity::Upstream
} else {
Affinity::Downstream
};
stops.push((b, affinity, base + within_run_caret_x(line, run, b)));
}
}
let mut collapsed: Vec<(usize, Affinity, f32)> = Vec::with_capacity(stops.len());
for s in stops {
if let Some(last) = collapsed.last()
&& (last.2 - s.2).abs() < SEAM_COLLAPSE_TOLERANCE_LPX
{
collapsed.pop();
}
collapsed.push(s);
}
collapsed
}
const SEAM_COLLAPSE_TOLERANCE_LPX: f32 = 0.05;
#[cfg(test)]
mod tests {
use super::super::caret::visual_caret_step;
use super::super::test_support::*;
use super::*;
#[test]
fn seam_collapse_threshold_holds_at_realistic_dpi() {
let mixed = run_line(
vec![
dglyph(0, 150.0, Direction::Ltr),
dglyph(1, 150.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
],
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
assert_eq!(visual_caret_stops(&mixed).len(), 5);
assert_eq!(
visual_caret_step(&mixed, 1, Affinity::Downstream, true),
Some((6, Affinity::Upstream))
);
let near = run_line(
vec![
dglyph(0, 150.0, Direction::Ltr),
dglyph(1, 150.0, Direction::Ltr),
dglyph(2, 0.03, Direction::Ltr),
],
vec![run(0..3, Direction::Ltr)],
);
assert_eq!(visual_caret_stops(&near).len(), 3);
let distinct = run_line(
vec![
dglyph(0, 150.0, Direction::Ltr),
dglyph(1, 150.0, Direction::Ltr),
dglyph(2, 7.0, Direction::Ltr),
],
vec![run(0..3, Direction::Ltr)],
);
assert_eq!(visual_caret_stops(&distinct).len(), 4);
}
}