Skip to content

Commit 69b80c0

Browse files
committed
avm2: Make TextField.getCharIndexAtPoint accurate
This patch fixes getCharIndexAtPoint() so that it's accurate for all inputs.
1 parent 601ee06 commit 69b80c0

File tree

2 files changed

+47
-16
lines changed

2 files changed

+47
-16
lines changed

core/src/avm2/globals/flash/text/text_field.rs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::avm2::{ArrayObject, ArrayStorage, Error};
1010
use crate::display_object::{AutoSizeMode, EditText, TDisplayObject, TextSelection};
1111
use crate::html::TextFormat;
1212
use crate::string::AvmString;
13-
use crate::{avm2_stub_getter, avm2_stub_method, avm2_stub_setter};
13+
use crate::{avm2_stub_getter, avm2_stub_setter};
1414
use swf::{Color, Point};
1515

1616
pub fn text_field_allocator<'gc>(
@@ -1652,30 +1652,18 @@ pub fn get_char_index_at_point<'gc>(
16521652
) -> Result<Value<'gc>, Error<'gc>> {
16531653
let this = this.as_object().unwrap();
16541654

1655-
// TODO This currently uses screen_position_to_index, which is inaccurate, because:
1656-
// 1. getCharIndexAtPoint should return -1 when clicked outside of a character,
1657-
// 2. screen_position_to_index returns caret index, not clicked character index.
1658-
// Currently, it is difficult to prove accuracy of this method, as at the time
1659-
// of writing this comment, text layout behaves differently compared to Flash.
1660-
// However, the current implementation is good enough to make some SWFs work.
1661-
avm2_stub_method!(
1662-
activation,
1663-
"flash.text.TextField",
1664-
"getCharIndexAtPoint",
1665-
"inaccurate char index detection"
1666-
);
1667-
16681655
let Some(this) = this
16691656
.as_display_object()
16701657
.and_then(|this| this.as_edit_text())
16711658
else {
16721659
return Ok(Value::Undefined);
16731660
};
16741661

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

1678-
if let Some(index) = this.screen_position_to_index(Point::from_pixels(x, y)) {
1666+
if let Some(index) = this.char_index_at_point(Point::from_pixels(x, y)) {
16791667
Ok(index.into())
16801668
} else {
16811669
Ok(Value::Number(-1f64))

core/src/display_object/edit_text.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,49 @@ impl<'gc> EditText<'gc> {
21742174
)
21752175
}
21762176

2177+
/// Returns the index of the character that is at the given position.
2178+
///
2179+
/// It returns `None` when there's no character at the given position.
2180+
/// It takes into account various quirks of Flash Player:
2181+
/// 1. It will return the index of the newline when `x`
2182+
/// is zero and the line is empty.
2183+
/// 2. It assumes (exclusive, inclusive) bounds.
2184+
/// 3. Positions with `y` below the last line will behave
2185+
/// the same way as at the last line.
2186+
pub fn char_index_at_point(self, position: Point<Twips>) -> Option<usize> {
2187+
let line_index = self.line_index_at_point(position)?;
2188+
2189+
let edit_text = self.0.read();
2190+
let line = &edit_text.layout.lines()[line_index];
2191+
2192+
// KJ: It's a bug in FP, it doesn't take into account horizontal
2193+
// scroll, but it does take into account vertical scroll.
2194+
// See https://github.com/airsdk/Adobe-Runtime-Support/issues/2315
2195+
// I guess we'll have to take scrollH into account here when
2196+
// we start supporting Harman runtimes.
2197+
let x = position.x - Self::GUTTER;
2198+
2199+
// Yes, this will return the index of the newline when the line is empty.
2200+
// Yes, that's how Flash Player does it.
2201+
if x == Twips::ZERO {
2202+
return Some(line.start());
2203+
}
2204+
2205+
// TODO Use binary search here when possible
2206+
for ch in line.start()..line.end() {
2207+
let bounds = line.char_x_bounds(ch);
2208+
let Some((a, b)) = bounds else {
2209+
continue;
2210+
};
2211+
2212+
if a < x && x <= b {
2213+
return Some(ch);
2214+
}
2215+
}
2216+
2217+
None
2218+
}
2219+
21772220
pub fn line_index_of_char(self, index: usize) -> Option<usize> {
21782221
self.0.read().layout.find_line_index_by_position(index)
21792222
}

0 commit comments

Comments
 (0)