piet_web/text/
grapheme.rs

1// Copyright 2019 the Piet Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use piet::HitTestPoint;
5use unicode_segmentation::UnicodeSegmentation;
6use web_sys::CanvasRenderingContext2d;
7
8use super::hit_test_line_position;
9
10// currently copied and pasted from cairo backend.
11//
12// However, not cleaning up because cairo and web implementations should diverge soon; and putting this
13// code in `piet` core doesn't really make sense as it's implementation specific.
14//
15/// get grapheme boundaries, intended to act on a line of text, not a full text layout that has
16/// both horizontal and vertical components
17pub(crate) fn get_grapheme_boundaries(
18    ctx: &CanvasRenderingContext2d,
19    text: &str,
20    grapheme_position: usize,
21) -> Option<GraphemeBoundaries> {
22    let mut graphemes = UnicodeSegmentation::grapheme_indices(text, true);
23    let (text_position, _) = graphemes.nth(grapheme_position)?;
24    let (next_text_position, _) = graphemes.next().unwrap_or((text.len(), ""));
25
26    let curr_edge = hit_test_line_position(ctx, text, text_position);
27    let next_edge = hit_test_line_position(ctx, text, next_text_position);
28
29    let res = GraphemeBoundaries {
30        curr_idx: text_position,
31        next_idx: next_text_position,
32        leading: curr_edge,
33        trailing: next_edge,
34    };
35
36    Some(res)
37}
38
39pub(crate) fn point_x_in_grapheme(
40    point_x: f64,
41    grapheme_boundaries: &GraphemeBoundaries,
42) -> Option<HitTestPoint> {
43    let leading = grapheme_boundaries.leading;
44    let trailing = grapheme_boundaries.trailing;
45    let curr_idx = grapheme_boundaries.curr_idx;
46    let next_idx = grapheme_boundaries.next_idx;
47
48    if point_x >= leading && point_x <= trailing {
49        // Check which boundary it's closer to.
50        // Round up to next grapheme boundary if
51        let midpoint = leading + ((trailing - leading) / 2.0);
52        let is_inside = true;
53        let idx = if point_x >= midpoint {
54            next_idx
55        } else {
56            curr_idx
57        };
58        Some(HitTestPoint::new(idx, is_inside))
59    } else {
60        None
61    }
62}
63
64#[derive(Debug, Default, PartialEq)]
65pub(crate) struct GraphemeBoundaries {
66    pub curr_idx: usize,
67    pub next_idx: usize,
68    pub leading: f64,
69    // not technically trailing; it's the lead boundary for the next grapheme cluster
70    pub trailing: f64,
71}