use super::bidi_runs::{
owning_run_with_affinity, run_x_start, visual_caret_stops, within_run_caret_x,
};
use super::shape_utils::snap_grapheme_floor;
use crate::ShapedLine;
use crate::types::Affinity;
pub fn pixel_x_at_byte(line: &ShapedLine, text: &str, byte: usize) -> f32 {
if line.glyphs.is_empty() {
return 0.0;
}
if !line.runs.is_empty() {
return run_caret_x(line, text, byte);
}
let snapped = snap_grapheme_floor(text, byte);
if snapped == 0 {
return 0.0;
}
if snapped >= text.len() {
return line.width_lpx;
}
let mut pen = 0.0f32;
for g in &line.glyphs {
if (g.cluster as usize) >= snapped {
return pen;
}
pen += g.x_advance_lpx;
}
line.width_lpx
}
pub fn run_caret_x(line: &ShapedLine, text: &str, byte: usize) -> f32 {
run_caret_x_at(line, snap_grapheme_floor(text, byte))
}
pub fn run_caret_x_at(line: &ShapedLine, byte: usize) -> f32 {
run_caret_x_at_affinity(line, byte, Affinity::Downstream)
}
pub fn run_caret_x_at_affinity(line: &ShapedLine, byte: usize, affinity: Affinity) -> f32 {
match owning_run_with_affinity(&line.runs, byte, affinity) {
Some(vi) => run_x_start(line, vi) + within_run_caret_x(line, &line.runs[vi], byte),
None => 0.0,
}
}
pub fn run_selection_rects(line: &ShapedLine, lo: usize, hi: usize) -> Vec<(f32, f32)> {
let mut rects = Vec::new();
if lo >= hi || line.runs.is_empty() {
return rects;
}
for (vi, run) in line.runs.iter().enumerate() {
let a = lo.max(run.byte_range.start);
let b = hi.min(run.byte_range.end);
if a >= b {
continue;
}
let base = run_x_start(line, vi);
let xa = base + within_run_caret_x(line, run, a);
let xb = base + within_run_caret_x(line, run, b);
let (x_start, x_end) = if xa <= xb { (xa, xb) } else { (xb, xa) };
let width = x_end - x_start;
if width > 0.0 {
rects.push((x_start, width));
}
}
rects
}
pub fn visual_caret_step(
line: &ShapedLine,
byte: usize,
affinity: Affinity,
move_right: bool,
) -> Option<(usize, Affinity)> {
let stops = visual_caret_stops(line);
if stops.is_empty() {
return None;
}
let cur = stops
.iter()
.position(|&(b, a, _)| b == byte && a == affinity)
.or_else(|| stops.iter().position(|&(b, _, _)| b == byte))?;
let next = if move_right {
cur.checked_add(1).filter(|&i| i < stops.len())
} else {
cur.checked_sub(1)
}?;
let (b, a, _) = stops[next];
Some((b, a))
}
pub fn visual_line_edge(line: &ShapedLine, to_end: bool) -> Option<(usize, Affinity)> {
let stops = visual_caret_stops(line);
if to_end {
stops.last().map(|&(b, a, _)| (b, a))
} else {
stops.first().map(|&(b, a, _)| (b, a))
}
}
#[cfg(test)]
mod tests {
use super::super::hit_test::byte_at_pixel_x;
use super::super::test_support::*;
use super::*;
use crate::types::Direction;
#[test]
fn empty_line_returns_zero() {
let l = line(vec![]);
assert_eq!(pixel_x_at_byte(&l, "", 0), 0.0);
assert_eq!(byte_at_pixel_x(&l, "", 0.0), 0);
}
#[test]
fn ascii_boundaries_roundtrip() {
let l = line(vec![glyph(0, 5.0), glyph(1, 6.0), glyph(2, 7.0)]);
assert_eq!(pixel_x_at_byte(&l, "abc", 0), 0.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 1), 5.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 2), 11.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 3), 18.0);
assert_eq!(byte_at_pixel_x(&l, "abc", 0.0), 0);
assert_eq!(byte_at_pixel_x(&l, "abc", 18.0), 3);
}
#[test]
fn multi_glyph_cluster_decomposition() {
let s = "e\u{0301}";
let l = line(vec![glyph(0, 8.0), glyph(0, 0.0)]);
assert_eq!(pixel_x_at_byte(&l, s, 0), 0.0);
assert_eq!(pixel_x_at_byte(&l, s, 1), 0.0);
assert_eq!(pixel_x_at_byte(&l, s, 3), 8.0);
assert_eq!(byte_at_pixel_x(&l, s, 0.0), 0);
assert_eq!(byte_at_pixel_x(&l, s, 8.0), 3);
}
#[test]
fn rtl_run_caret_walks_in_reverse() {
let glyphs = vec![
dglyph(6, 7.0, Direction::Rtl),
dglyph(3, 8.0, Direction::Rtl),
dglyph(0, 9.0, Direction::Rtl),
];
let l = run_line(glyphs, vec![run(0..9, Direction::Rtl)]);
let text = "日本語";
assert_eq!(pixel_x_at_byte(&l, text, 0), 24.0);
assert_eq!(pixel_x_at_byte(&l, text, 3), 15.0);
assert_eq!(pixel_x_at_byte(&l, text, 6), 7.0);
assert_eq!(pixel_x_at_byte(&l, text, 9), 0.0);
}
#[test]
fn mixed_ltr_rtl_caret_positions() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
];
let l = run_line(
glyphs,
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
let text = "abאב";
assert_eq!(pixel_x_at_byte(&l, text, 0), 0.0);
assert_eq!(pixel_x_at_byte(&l, text, 1), 5.0);
assert_eq!(pixel_x_at_byte(&l, text, 2), 26.0);
assert_eq!(pixel_x_at_byte(&l, text, 4), 18.0);
assert_eq!(pixel_x_at_byte(&l, text, 6), 11.0);
}
#[test]
fn visual_motion_pure_ltr_matches_logical() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(2, 7.0, Direction::Ltr),
];
let l = run_line(glyphs, vec![run(0..3, Direction::Ltr)]);
assert_eq!(
visual_caret_step(&l, 0, Affinity::Downstream, true),
Some((1, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 1, Affinity::Downstream, true),
Some((2, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 2, Affinity::Downstream, true),
Some((3, Affinity::Upstream))
);
assert_eq!(visual_caret_step(&l, 3, Affinity::Upstream, true), None);
assert_eq!(
visual_caret_step(&l, 3, Affinity::Upstream, false),
Some((2, Affinity::Downstream))
);
assert_eq!(visual_caret_step(&l, 0, Affinity::Downstream, false), None);
}
#[test]
fn visual_motion_pure_rtl_reverses_logical() {
let glyphs = vec![
dglyph(6, 7.0, Direction::Rtl),
dglyph(3, 8.0, Direction::Rtl),
dglyph(0, 9.0, Direction::Rtl),
];
let l = run_line(glyphs, vec![run(0..9, Direction::Rtl)]);
assert_eq!(
visual_caret_step(&l, 9, Affinity::Upstream, true),
Some((6, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 6, Affinity::Downstream, true),
Some((3, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 3, Affinity::Downstream, true),
Some((0, Affinity::Downstream))
);
assert_eq!(visual_caret_step(&l, 0, Affinity::Downstream, true), None);
assert_eq!(
visual_caret_step(&l, 0, Affinity::Downstream, false),
Some((3, Affinity::Downstream))
);
assert_eq!(visual_caret_step(&l, 9, Affinity::Upstream, false), None);
}
#[test]
fn visual_motion_collapses_seam_mac_style() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
];
let l = run_line(
glyphs,
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
assert_eq!(
visual_caret_step(&l, 1, Affinity::Downstream, true),
Some((6, Affinity::Upstream))
);
assert_eq!(
visual_caret_step(&l, 6, Affinity::Upstream, true),
Some((4, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 4, Affinity::Downstream, true),
Some((2, Affinity::Downstream))
);
assert_eq!(visual_caret_step(&l, 2, Affinity::Downstream, true), None);
assert_eq!(
visual_caret_step(&l, 2, Affinity::Downstream, false),
Some((4, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 6, Affinity::Upstream, false),
Some((1, Affinity::Downstream))
);
assert_eq!(
visual_caret_step(&l, 2, Affinity::Upstream, false),
Some((4, Affinity::Downstream))
);
}
#[test]
fn selection_rects_split_at_direction_boundary() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
];
let l = run_line(
glyphs,
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
let rects = run_selection_rects(&l, 1, 4);
assert_eq!(rects.len(), 2);
assert_eq!(rects[0], (5.0, 6.0));
assert_eq!(rects[1], (18.0, 8.0));
assert!(run_selection_rects(&l, 3, 3).is_empty());
}
#[test]
fn boundary_byte_resolves_per_affinity() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
];
let l = run_line(
glyphs,
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
assert_eq!(run_caret_x_at_affinity(&l, 2, Affinity::Upstream), 11.0);
assert_eq!(run_caret_x_at_affinity(&l, 2, Affinity::Downstream), 26.0);
assert_eq!(run_caret_x_at(&l, 2), 26.0);
assert_eq!(
run_caret_x_at_affinity(&l, 4, Affinity::Upstream),
run_caret_x_at_affinity(&l, 4, Affinity::Downstream),
);
}
#[test]
fn visual_line_edges_are_screen_leftmost_and_rightmost() {
let glyphs = vec![
dglyph(0, 5.0, Direction::Ltr),
dglyph(1, 6.0, Direction::Ltr),
dglyph(4, 7.0, Direction::Rtl),
dglyph(2, 8.0, Direction::Rtl),
];
let l = run_line(
glyphs,
vec![run(0..2, Direction::Ltr), run(2..6, Direction::Rtl)],
);
assert_eq!(visual_line_edge(&l, false), Some((0, Affinity::Downstream)));
assert_eq!(visual_line_edge(&l, true), Some((2, Affinity::Downstream)));
let rtl = run_line(
vec![
dglyph(6, 7.0, Direction::Rtl),
dglyph(3, 8.0, Direction::Rtl),
dglyph(0, 9.0, Direction::Rtl),
],
vec![run(0..9, Direction::Rtl)],
);
assert_eq!(visual_line_edge(&rtl, false), Some((9, Affinity::Upstream)));
assert_eq!(
visual_line_edge(&rtl, true),
Some((0, Affinity::Downstream))
);
assert_eq!(visual_line_edge(&line(vec![glyph(0, 5.0)]), false), None);
}
#[test]
fn empty_runs_path_is_unchanged_by_run_branch() {
let l = line(vec![glyph(0, 5.0), glyph(1, 6.0), glyph(2, 7.0)]);
assert!(l.runs.is_empty());
assert_eq!(pixel_x_at_byte(&l, "abc", 0), 0.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 1), 5.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 2), 11.0);
assert_eq!(pixel_x_at_byte(&l, "abc", 3), 18.0);
assert_eq!(byte_at_pixel_x(&l, "abc", 0.0), 0);
assert_eq!(byte_at_pixel_x(&l, "abc", 5.0), 1);
assert_eq!(byte_at_pixel_x(&l, "abc", 18.0), 3);
}
}