From 920c809e67826fa0cf4df5b495f72cb240925728 Mon Sep 17 00:00:00 2001 From: Kamil Jarosz Date: Fri, 29 Nov 2024 16:26:00 +0100 Subject: [PATCH] avm2: Make TextField.getCharIndexAtPoint accurate This patch fixes getCharIndexAtPoint() so that it's accurate for all inputs. --- .../src/avm2/globals/flash/text/text_field.rs | 20 +++--------- core/src/display_object/edit_text.rs | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index 01b2995d7aa21..47b3767b72c4d 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -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>( @@ -1541,19 +1541,6 @@ pub fn get_char_index_at_point<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, 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()) @@ -1561,10 +1548,11 @@ pub fn get_char_index_at_point<'gc>( 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)) diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 81039fc02063b..219d13058e57f 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -2109,6 +2109,38 @@ impl<'gc> EditText<'gc> { ) } + pub fn char_index_at_point(self, position: Point) -> Option { + 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 { self.0.read().layout.find_line_index_by_position(index) }