Skip to content

Commit

Permalink
feat: Add basic text support on Unix (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
DataTriny authored May 26, 2024
1 parent fc5125e commit 52540f8
Show file tree
Hide file tree
Showing 13 changed files with 1,180 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ These are the libraries that implement platform accessibility APIs. The followin

* The Windows adapter, which implements the UI Automation API, is available in [the `accesskit_windows` crate](https://crates.io/crates/accesskit_windows), in [the `platforms/windows` directory](https://github.com/AccessKit/accesskit/tree/main/platforms/windows). It doesn't yet support all possible widget types, but it can now be used to make real, non-trivial applications accessible. In particular, it supports both single-line and multi-line text edit controls, but not yet rich text.
* The macOS adapter, which implements the Cocoa NSAccessibility protocol, is available in [the `accesskit_macos` crate](https://crates.io/crates/accesskit_macos), in [the `platforms/macos` directory](https://github.com/AccessKit/accesskit/tree/main/platforms/macos). It is roughly at feature parity with the Windows adapter, including support for text edit controls.
* The Unix adapter, which implements the D-Bus-based AT-SPI protocol, is available in [the `accesskit_unix` crate](https://crates.io/crates/accesskit_unix), in [the `platforms/unix` directory](https://github.com/AccessKit/accesskit/tree/main/platforms/unix). This adapter doesn't yet fully support text edit controls. It is also not yet usable with the Orca screen reader, due to a keyboard input handling issue that we are working with the appropriate GNOME development teams to solve.
* The Unix adapter, which implements the D-Bus-based AT-SPI protocol, is available in [the `accesskit_unix` crate](https://crates.io/crates/accesskit_unix), in [the `platforms/unix` directory](https://github.com/AccessKit/accesskit/tree/main/platforms/unix). This adapter also supports text edit controls. It is not yet fully usable with the Orca screen reader, due to a keyboard input handling issue that we are working with the appropriate GNOME development teams to solve.

The following adapters are planned:

Expand Down
164 changes: 164 additions & 0 deletions consumer/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@ impl<'a> Position<'a> {
Range::new(self.root_node, self.inner, self.inner)
}

pub fn to_global_usv_index(&self) -> usize {
let mut total_length = 0usize;
for node in self.root_node.inline_text_boxes() {
let node_text = node.data().value().unwrap();
if node.id() == self.inner.node.id() {
let character_lengths = node.data().character_lengths();
let slice_end = character_lengths[..self.inner.character_index]
.iter()
.copied()
.map(usize::from)
.sum::<usize>();
return total_length + node_text[..slice_end].chars().count();
}
total_length += node_text.chars().count();
}
panic!("invalid position")
}

pub fn to_global_utf16_index(&self) -> usize {
let mut total_length = 0usize;
for node in self.root_node.inline_text_boxes() {
Expand Down Expand Up @@ -996,6 +1014,48 @@ impl<'a> Node<'a> {
Some(Range::new(*self, pos.inner, end.inner))
}

pub fn text_position_from_global_usv_index(&self, index: usize) -> Option<Position> {
let mut total_length = 0usize;
for node in self.inline_text_boxes() {
let node_text = node.data().value().unwrap();
let node_text_length = node_text.chars().count();
let new_total_length = total_length + node_text_length;
if index >= total_length && index < new_total_length {
let index = index - total_length;
let mut utf8_length = 0usize;
let mut usv_length = 0usize;
for (character_index, utf8_char_length) in
node.data().character_lengths().iter().enumerate()
{
let new_utf8_length = utf8_length + (*utf8_char_length as usize);
let char_str = &node_text[utf8_length..new_utf8_length];
let usv_char_length = char_str.chars().count();
let new_usv_length = usv_length + usv_char_length;
if index >= usv_length && index < new_usv_length {
return Some(Position {
root_node: *self,
inner: InnerPosition {
node,
character_index,
},
});
}
utf8_length = new_utf8_length;
usv_length = new_usv_length;
}
panic!("index out of range");
}
total_length = new_total_length;
}
if index == total_length {
return Some(Position {
root_node: *self,
inner: self.document_end(),
});
}
None
}

pub fn text_position_from_global_utf16_index(&self, index: usize) -> Option<Position> {
let mut total_length = 0usize;
for node in self.inline_text_boxes() {
Expand Down Expand Up @@ -1691,6 +1751,31 @@ mod tests {
}
}

#[test]
fn to_global_usv_index() {
let tree = main_multiline_tree(None);
let state = tree.state();
let node = state.node_by_id(NodeId(1)).unwrap();

{
let range = node.document_range();
assert_eq!(range.start().to_global_usv_index(), 0);
assert_eq!(range.end().to_global_usv_index(), 97);
}

{
let range = node.document_range();
let pos = range.start().forward_to_line_end();
assert_eq!(pos.to_global_usv_index(), 38);
let pos = range.start().forward_to_line_start();
assert_eq!(pos.to_global_usv_index(), 38);
let pos = pos.forward_to_character_start();
assert_eq!(pos.to_global_usv_index(), 39);
let pos = pos.forward_to_line_start();
assert_eq!(pos.to_global_usv_index(), 55);
}
}

#[test]
fn to_global_utf16_index() {
let tree = main_multiline_tree(None);
Expand Down Expand Up @@ -1781,6 +1866,85 @@ mod tests {
assert!(node.line_range_from_index(6).is_none());
}

#[test]
fn text_position_from_global_usv_index() {
let tree = main_multiline_tree(None);
let state = tree.state();
let node = state.node_by_id(NodeId(1)).unwrap();

{
let pos = node.text_position_from_global_usv_index(0).unwrap();
assert!(pos.is_document_start());
}

{
let pos = node.text_position_from_global_usv_index(17).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "\u{a0}");
}

{
let pos = node.text_position_from_global_usv_index(18).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "l");
}

{
let pos = node.text_position_from_global_usv_index(37).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), " ");
}

{
let pos = node.text_position_from_global_usv_index(38).unwrap();
assert!(!pos.is_paragraph_start());
assert!(pos.is_line_start());
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "t");
}

{
let pos = node.text_position_from_global_usv_index(54).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "\n");
}

{
let pos = node.text_position_from_global_usv_index(55).unwrap();
assert!(pos.is_paragraph_start());
assert!(pos.is_line_start());
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "A");
}

for i in 94..=95 {
let pos = node.text_position_from_global_usv_index(i).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "\u{1f44d}\u{1f3fb}");
}

{
let pos = node.text_position_from_global_usv_index(96).unwrap();
let mut range = pos.to_degenerate_range();
range.set_end(pos.forward_to_character_end());
assert_eq!(range.text(), "\n");
}

{
let pos = node.text_position_from_global_usv_index(97).unwrap();
assert!(pos.is_document_end());
}

assert!(node.text_position_from_global_usv_index(98).is_none());
}

#[test]
fn text_position_from_global_utf16_index() {
let tree = main_multiline_tree(None);
Expand Down
Loading

0 comments on commit 52540f8

Please sign in to comment.