Skip to content

Commit

Permalink
avm2: Make TextField.getCharIndexAtPoint accurate
Browse files Browse the repository at this point in the history
This patch fixes getCharIndexAtPoint() so that it's accurate for all inputs.
  • Loading branch information
kjarosh committed Dec 4, 2024
1 parent 44ebce3 commit 920c809
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 16 deletions.
20 changes: 4 additions & 16 deletions core/src/avm2/globals/flash/text/text_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::avm2::{ArrayObject, ArrayStorage, Error};
use crate::display_object::{AutoSizeMode, EditText, TDisplayObject, TextSelection};
use crate::html::TextFormat;
use crate::string::AvmString;
use crate::{avm2_stub_getter, avm2_stub_method, avm2_stub_setter};
use crate::{avm2_stub_getter, avm2_stub_setter};
use swf::{Color, Point};

pub fn text_field_allocator<'gc>(
Expand Down Expand Up @@ -1541,30 +1541,18 @@ pub fn get_char_index_at_point<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
// TODO This currently uses screen_position_to_index, which is inaccurate, because:
// 1. getCharIndexAtPoint should return -1 when clicked outside of a character,
// 2. screen_position_to_index returns caret index, not clicked character index.
// Currently, it is difficult to prove accuracy of this method, as at the time
// of writing this comment, text layout behaves differently compared to Flash.
// However, the current implementation is good enough to make some SWFs work.
avm2_stub_method!(
activation,
"flash.text.TextField",
"getCharIndexAtPoint",
"inaccurate char index detection"
);

let Some(this) = this
.as_display_object()
.and_then(|this| this.as_edit_text())
else {
return Ok(Value::Undefined);
};

let x = args.get_f64(activation, 0)?;
// No idea why FP does this weird 1px translation...
let x = args.get_f64(activation, 0)? + 1.0;
let y = args.get_f64(activation, 1)?;

if let Some(index) = this.screen_position_to_index(Point::from_pixels(x, y)) {
if let Some(index) = this.char_index_at_point(Point::from_pixels(x, y)) {
Ok(index.into())
} else {
Ok(Value::Number(-1f64))
Expand Down
32 changes: 32 additions & 0 deletions core/src/display_object/edit_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,38 @@ impl<'gc> EditText<'gc> {
)
}

pub fn char_index_at_point(self, position: Point<Twips>) -> Option<usize> {
let line_index = self.line_index_at_point(position)?;

let edit_text = self.0.read();
let line = &edit_text.layout.lines()[line_index];

// KJ: It's a bug in FP, it doesn't take into account horizontal
// scroll, but it does take into account vertical scroll.
// See https://github.com/airsdk/Adobe-Runtime-Support/issues/2315
// I guess we'll have to take scrollH into account here when
// we start supporting Harman runtimes.
let x = position.x - Self::GUTTER;

if x == Twips::ZERO {
return Some(line.start());
}

// TODO Use binary search here when possible
for ch in line.start()..line.end() {
let bounds = line.char_x_bounds(ch);
let Some((a, b)) = bounds else {
continue;
};

if a < x && x <= b {
return Some(ch);
}
}

None
}

pub fn line_index_of_char(self, index: usize) -> Option<usize> {
self.0.read().layout.find_line_index_by_position(index)
}
Expand Down

0 comments on commit 920c809

Please sign in to comment.