diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 71193e7b3c96..7aaf34d9bf33 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -909,9 +909,8 @@ impl<'gc> EditText<'gc> { layout: &Layout<'gc>, ) { if flags.contains(LayoutDebugBoxesFlag::CHAR) { - let text = &self.text(); - for i in 0..text.len() { - if let Some(bounds) = layout.char_bounds(i, text) { + for i in 0..self.text().len() { + if let Some(bounds) = layout.char_bounds(i) { context.draw_rect_outline(Color::MAGENTA, bounds, Twips::ONE); } } @@ -2030,13 +2029,7 @@ impl<'gc> EditText<'gc> { pub fn char_bounds(self, index: usize) -> Option> { let edit_text = self.0.read(); - let text = edit_text.text_spans.text(); - - if index >= text.len() { - return None; - } - - let bounds = edit_text.layout.char_bounds(index, text)?; + let bounds = edit_text.layout.char_bounds(index)?; let padding = Twips::from_pixels(Self::INTERNAL_PADDING); let bounds = Matrix::translate(padding, padding) * bounds; Some(bounds) diff --git a/core/src/html/layout.rs b/core/src/html/layout.rs index 8c9aefb6f5cc..220e948c04dc 100644 --- a/core/src/html/layout.rs +++ b/core/src/html/layout.rs @@ -2,7 +2,7 @@ use crate::context::UpdateContext; use crate::drawing::Drawing; -use crate::font::{EvalParameters, Font, FontType, Glyph}; +use crate::font::{EvalParameters, Font, FontType}; use crate::html::dimensions::{BoxBounds, Position, Size}; use crate::html::text_format::{FormatSpans, TextFormat, TextSpan}; use crate::string::{utils as string_utils, WStr}; @@ -637,7 +637,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> { let text_size = Size::from(font.measure(text, params)); let box_origin = self.cursor - (Twips::ZERO, ascent).into(); - let mut new_box = LayoutBox::from_text(start, end, font, span); + let mut new_box = LayoutBox::from_text(text, start, end, font, span); new_box.interior_bounds = BoxBounds::from_position_and_size(box_origin, text_size); new_box.bounds = BoxBounds::from_position_and_size( box_origin, @@ -823,10 +823,10 @@ impl<'gc> Layout<'gc> { } /// Returns char bounds of the given char relative to this layout. - pub fn char_bounds(&self, position: usize, text: &WStr) -> Option> { + pub fn char_bounds(&self, position: usize) -> Option> { let line_index = self.find_line_index_by_position(position)?; let line = self.lines.get(line_index)?; - line.char_bounds(position, text) + line.char_bounds(position) } } @@ -923,13 +923,13 @@ impl<'gc> LayoutLine<'gc> { } /// Returns char bounds of the given char relative to the whole layout. - pub fn char_bounds(&self, position: usize, text: &WStr) -> Option> { + pub fn char_bounds(&self, position: usize) -> Option> { let box_index = self.find_box_index_by_position(position)?; let layout_box = self.boxes.get(box_index)?; let line_bounds = self.bounds(); let origin_x = layout_box.bounds().origin().x(); - let x_bounds = layout_box.char_x_bounds(position, text)?; + let x_bounds = layout_box.char_x_bounds(position)?; Some(Rectangle { x_min: origin_x + x_bounds.0, @@ -999,6 +999,21 @@ pub enum LayoutContent<'gc> { /// The color to render the font with. #[collect(require_static)] color: swf::Color, + + /// List of end positions (relative to this box) for each character. + /// + /// By having this here, we do not have to reevaluate the font + /// each time we want to get the position of a character, + /// and we can use this data along with layout box bounds to + /// calculate character bounds. + /// + /// For instance, for the text "hello", this field may contain: + /// + /// ```text + /// [100, 200, 250, 300, 400] + /// ``` + #[collect(require_static)] + char_end_pos: Vec, }, /// A layout box containing a bullet. @@ -1062,8 +1077,19 @@ impl<'gc> Debug for LayoutContent<'gc> { impl<'gc> LayoutBox<'gc> { /// Construct a text box for a text node. - pub fn from_text(start: usize, end: usize, font: Font<'gc>, span: &TextSpan) -> Self { + pub fn from_text( + text: &WStr, + start: usize, + end: usize, + font: Font<'gc>, + span: &TextSpan, + ) -> Self { let params = EvalParameters::from_span(span); + let mut char_end_pos = Vec::with_capacity(end - start); + + font.evaluate(text, Default::default(), params, |_, _, _, advance, x| { + char_end_pos.push(x + advance); + }); Self { interior_bounds: Default::default(), @@ -1075,6 +1101,7 @@ impl<'gc> LayoutBox<'gc> { font, params, color: span.font.color, + char_end_pos, }, } } @@ -1269,6 +1296,7 @@ impl<'gc> LayoutBox<'gc> { font, params, color, + .. } => Some(( text.slice(*start..*end)?, text_format, @@ -1327,24 +1355,21 @@ impl<'gc> LayoutBox<'gc> { } /// Return x-axis char bounds of the given char relative to this layout box. - pub fn char_x_bounds(&self, position: usize, text: &WStr) -> Option<(Twips, Twips)> { + pub fn char_x_bounds(&self, position: usize) -> Option<(Twips, Twips)> { let relative_position = position.checked_sub(self.start())?; - let mut x_bounds = None; - if let Some((text, _tf, font, params, _color)) = self.as_renderable_text(text) { - font.evaluate( - text, - Default::default(), - params, - |pos, _transform, _glyph: &Glyph, advance, x| { - if pos == relative_position { - x_bounds = Some((x, x + advance)); - } - }, - ); - } + let LayoutContent::Text { char_end_pos, .. } = &self.content else { + return None; + }; - x_bounds + Some(if relative_position == 0 { + (Twips::ZERO, *char_end_pos.get(0)?) + } else { + ( + *char_end_pos.get(relative_position - 1)?, + *char_end_pos.get(relative_position)?, + ) + }) } }