diff --git a/render-wasm/src/render/text_editor.rs b/render-wasm/src/render/text_editor.rs index 9c1cdaf526..e2c55ac864 100644 --- a/render-wasm/src/render/text_editor.rs +++ b/render-wasm/src/render/text_editor.rs @@ -123,6 +123,9 @@ fn calculate_cursor_rect( .map(|span| span.text.chars().count()) .sum(); + let layout_char_pos = para.logical_to_layout_offset(char_pos); + let layout_para_count = para.logical_to_layout_offset(para_char_count); + let (cursor_x, cursor_height) = if para_char_count == 0 { // Empty paragraph - use default height (0.0, laid_out_para.height()) @@ -139,7 +142,7 @@ fn calculate_cursor_rect( } } else if char_pos >= para_char_count { let rects = laid_out_para.get_rects_for_range( - para_char_count.saturating_sub(1)..para_char_count, + layout_para_count.saturating_sub(1)..layout_para_count, RectHeightStyle::Max, RectWidthStyle::Tight, ); @@ -150,7 +153,7 @@ fn calculate_cursor_rect( } } else { let rects = laid_out_para.get_rects_for_range( - char_pos..char_pos + 1, + layout_char_pos..layout_char_pos + 1, RectHeightStyle::Max, RectWidthStyle::Tight, ); @@ -221,9 +224,11 @@ fn calculate_selection_rects( }; if range_start < range_end { + let layout_range_start = para.logical_to_layout_offset(range_start); + let layout_range_end = para.logical_to_layout_offset(range_end); use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; let text_boxes = laid_out_para.get_rects_for_range( - range_start..range_end, + layout_range_start..layout_range_end, RectHeightStyle::Max, RectWidthStyle::Tight, ); diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 298b44cc55..d90abbc7a6 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -522,10 +522,12 @@ impl TextContent { } } + let layout_offset = position_with_affinity.position as usize; + let logical_offset = paragraph.layout_to_logical_offset(layout_offset); return Some(TextPositionWithAffinity::new( position_with_affinity, paragraph_index, - position_with_affinity.position as usize, + logical_offset, )); } } @@ -996,6 +998,49 @@ impl Paragraph { .iter_mut() .for_each(|l| l.scale_content(value)); } + + /// Convert a logical character offset (based on original span text) + /// to a layout offset (as used by Skia). `apply_text_transform` inserts + /// a zero-width space after each '/', so the layout text is longer. + pub fn logical_to_layout_offset(&self, logical_offset: usize) -> usize { + let mut pos = 0; + let mut extra = 0; + for span in self.children() { + for ch in span.text.chars() { + if pos >= logical_offset { + return logical_offset + extra; + } + if ch == '/' { + extra += 1; + } + pos += 1; + } + } + logical_offset + extra + } + + /// Convert a layout character offset (from Skia, which includes + /// zero-width spaces after '/') back to a logical offset. + pub fn layout_to_logical_offset(&self, layout_offset: usize) -> usize { + let mut logical_pos = 0; + let mut layout_pos = 0; + for span in self.children() { + for ch in span.text.chars() { + if layout_pos >= layout_offset { + return logical_pos; + } + logical_pos += 1; + layout_pos += 1; + if ch == '/' { + layout_pos += 1; + if layout_pos > layout_offset { + return logical_pos; + } + } + } + } + logical_pos + } } #[derive(Debug, PartialEq, Clone)] diff --git a/render-wasm/src/wasm/text_editor.rs b/render-wasm/src/wasm/text_editor.rs index 54b360ac39..7bffca948f 100644 --- a/render-wasm/src/wasm/text_editor.rs +++ b/render-wasm/src/wasm/text_editor.rs @@ -851,7 +851,8 @@ fn get_cursor_rect( let mut y_offset = valign_offset; for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() { if idx == cursor.paragraph { - let char_pos = cursor.offset; + let para = ¶graphs[idx]; + let char_pos = para.logical_to_layout_offset(cursor.offset); use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; let rects = laid_out_para.get_rects_for_range( @@ -938,9 +939,11 @@ fn get_selection_rects( }; if range_start < range_end { + let layout_range_start = para.logical_to_layout_offset(range_start); + let layout_range_end = para.logical_to_layout_offset(range_end); use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; let text_boxes = laid_out_para.get_rects_for_range( - range_start..range_end, + layout_range_start..layout_range_end, RectHeightStyle::Tight, RectWidthStyle::Tight, );