Skip to content

Commit 56dd32c

Browse files
authored
Fix cluster index for rtl cursors (#75)
The `cluster_index` field of `CursorPath` is used for relative indexing within cluster range of the corresponding `Run`. For RTL runs the logical `cluster_index` is calculated incorrectly from the visual cluster index as it uses the global cluster range as reference. This can be reproduced with the `We will لقاء في 09:35 في ال 🏖️` snippet., where `cluster_index` for cursors within RTL segments may reach values of 20 and higher, while the the individual runs only span a few clusters.
1 parent 0b26fd2 commit 56dd32c

File tree

2 files changed

+28
-29
lines changed

2 files changed

+28
-29
lines changed

parley/src/layout/cursor.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,12 @@ impl Cursor {
5252
let mut last_edge = line_metrics.offset;
5353
for (run_index, run) in line.runs().enumerate() {
5454
result.path.run_index = run_index;
55-
let cluster_range = run.data().cluster_range.clone();
5655
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
5756
let range = cluster.text_range();
5857
result.text_start = range.start;
5958
result.text_end = range.end;
6059
result.is_rtl = run.is_rtl();
61-
result.path.cluster_index = if result.is_rtl {
62-
cluster_range.end - cluster_index - 1
63-
} else {
64-
cluster_index
65-
};
60+
result.path.cluster_index = run.visual_to_logical(cluster_index).unwrap();
6661
if x >= last_edge {
6762
let advance = cluster.advance();
6863
let next_edge = last_edge + advance;
@@ -122,18 +117,13 @@ impl Cursor {
122117
result.offset = last_edge;
123118
continue;
124119
}
125-
let cluster_range = run.data().cluster_range.clone();
126120
for (cluster_index, cluster) in run.visual_clusters().enumerate() {
127121
let range = cluster.text_range();
128122
result.text_start = range.start;
129123
result.text_end = range.end;
130124
result.offset = last_edge;
131125
result.is_rtl = run.is_rtl();
132-
result.path.cluster_index = if result.is_rtl {
133-
cluster_range.end - cluster_index - 1
134-
} else {
135-
cluster_index
136-
};
126+
result.path.cluster_index = run.visual_to_logical(cluster_index).unwrap();
137127
let advance = cluster.advance();
138128
if range.contains(&position) {
139129
if !is_leading || !result.is_inside {

parley/src/layout/run.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,17 @@ impl<'a, B: Brush> Run<'a, B> {
6565
self.data.bidi_level & 1 != 0
6666
}
6767

68-
/// Returns the number of clusters in the run.
69-
pub fn len(&self) -> usize {
68+
/// Returns the cluster range for the run.
69+
pub fn cluster_range(&self) -> Range<usize> {
7070
self.line_data
7171
.map(|d| &d.cluster_range)
7272
.unwrap_or(&self.data.cluster_range)
73-
.len()
73+
.clone()
74+
}
75+
76+
/// Returns the number of clusters in the run.
77+
pub fn len(&self) -> usize {
78+
self.cluster_range().len()
7479
}
7580

7681
/// Returns true if the run is empty.
@@ -93,35 +98,39 @@ impl<'a, B: Brush> Run<'a, B> {
9398

9499
/// Returns an iterator over the clusters in logical order.
95100
pub fn clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
96-
let range = self
97-
.line_data
98-
.map(|d| &d.cluster_range)
99-
.unwrap_or(&self.data.cluster_range)
100-
.clone();
101+
let range = self.cluster_range();
101102
Clusters {
102103
run: self,
103104
range,
104105
rev: false,
105106
}
106107
}
107108

109+
/// Returns the logical cluster index for the specified visual cluster index.
110+
pub fn visual_to_logical(&self, visual_index: usize) -> Option<usize> {
111+
let num_clusters = self.len();
112+
if visual_index >= num_clusters {
113+
return None;
114+
}
115+
116+
let logical_index = if self.is_rtl() {
117+
num_clusters - 1 - visual_index
118+
} else {
119+
visual_index
120+
};
121+
122+
Some(logical_index)
123+
}
124+
108125
/// Returns an iterator over the clusters in visual order.
109126
pub fn visual_clusters(&'a self) -> impl Iterator<Item = Cluster<'a, B>> + 'a + Clone {
110-
let range = self
111-
.line_data
112-
.map(|d| &d.cluster_range)
113-
.unwrap_or(&self.data.cluster_range)
114-
.clone();
127+
let range = self.cluster_range();
115128
Clusters {
116129
run: self,
117130
range,
118131
rev: self.is_rtl(),
119132
}
120133
}
121-
122-
pub(crate) fn data(&self) -> &'a RunData {
123-
self.data
124-
}
125134
}
126135

127136
struct Clusters<'a, B: Brush> {

0 commit comments

Comments
 (0)