1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2020 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0

//! Helper functions for working with text in Masonry.

use kurbo::{Line, Rect, Stroke};
use parley::Layout;
use vello::{kurbo::Affine, peniko::Fill, Scene};

use crate::{text2::TextBrush, WidgetId};

/// A reference counted string slice.
///
/// This is a data-friendly way to represent strings in Masonry. Unlike `String`
/// it cannot be mutated, but unlike `String` it can be cheaply cloned.
pub type ArcStr = std::sync::Arc<str>;

/// A type we use to keep track of which widgets are responsible for which
/// ime sessions.
#[derive(Clone, Debug)]
#[allow(unused)]
pub(crate) struct TextFieldRegistration {
    pub widget_id: WidgetId,
}

// Copy-pasted from druid_shell
/// An event representing an application-initiated change in [`InputHandler`]
/// state.
///
/// When we change state that may have previously been retrieved from an
/// [`InputHandler`], we notify the platform so that it can invalidate any
/// data if necessary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ImeChangeSignal {
    /// Indicates the value returned by `InputHandler::selection` may have changed.
    SelectionChanged,

    /// Indicates the values returned by one or more of these methods may have changed:
    /// - `InputHandler::hit_test_point`
    /// - `InputHandler::line_range`
    /// - `InputHandler::bounding_box`
    /// - `InputHandler::slice_bounding_box`
    LayoutChanged,

    /// Indicates any value returned from any `InputHandler` method may have changed.
    Reset,
}

/// A function that renders laid out glyphs to a [Scene].
pub fn render_text(
    scene: &mut Scene,
    scratch_scene: &mut Scene,
    transform: Affine,
    layout: &Layout<TextBrush>,
) {
    scratch_scene.reset();
    for line in layout.lines() {
        let metrics = &line.metrics();
        for glyph_run in line.glyph_runs() {
            let mut x = glyph_run.offset();
            let y = glyph_run.baseline();
            let run = glyph_run.run();
            let font = run.font();
            let font_size = run.font_size();
            let synthesis = run.synthesis();
            let glyph_xform = synthesis
                .skew()
                .map(|angle| Affine::skew(angle.to_radians().tan() as f64, 0.0));
            let style = glyph_run.style();
            let coords = run
                .normalized_coords()
                .iter()
                .map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord))
                .collect::<Vec<_>>();
            let text_brush = match &style.brush {
                TextBrush::Normal(text_brush) => text_brush,
                TextBrush::Highlight { text, fill } => {
                    scene.fill(
                        Fill::EvenOdd,
                        transform,
                        fill,
                        None,
                        &Rect::from_origin_size(
                            (
                                glyph_run.offset() as f64,
                                // The y coordinate is on the baseline. We want to draw from the top of the line
                                // (Note that we are in a y-down coordinate system)
                                (y - metrics.ascent - metrics.leading) as f64,
                            ),
                            (glyph_run.advance() as f64, metrics.size() as f64),
                        ),
                    );

                    text
                }
            };
            scratch_scene
                .draw_glyphs(font)
                .brush(text_brush)
                .transform(transform)
                .glyph_transform(glyph_xform)
                .font_size(font_size)
                .normalized_coords(&coords)
                .draw(
                    Fill::NonZero,
                    glyph_run.glyphs().map(|glyph| {
                        let gx = x + glyph.x;
                        let gy = y - glyph.y;
                        x += glyph.advance;
                        vello::glyph::Glyph {
                            id: glyph.id as _,
                            x: gx,
                            y: gy,
                        }
                    }),
                );
            if let Some(underline) = &style.underline {
                let underline_brush = match &underline.brush {
                    TextBrush::Normal(text) => text,
                    // It doesn't make sense for an underline to have a highlight colour, so we
                    // just use the text colour for the colour
                    TextBrush::Highlight { text, .. } => text,
                };
                let run_metrics = glyph_run.run().metrics();
                let offset = match underline.offset {
                    Some(offset) => offset,
                    None => run_metrics.underline_offset,
                };
                let width = match underline.size {
                    Some(size) => size,
                    None => run_metrics.underline_size,
                };
                // The `offset` is the distance from the baseline to the *top* of the underline
                // so we move the line down by half the width
                // Remember that we are using a y-down coordinate system
                let y = glyph_run.baseline() - offset + width / 2.;

                let line = Line::new(
                    (glyph_run.offset() as f64, y as f64),
                    ((glyph_run.offset() + glyph_run.advance()) as f64, y as f64),
                );
                scratch_scene.stroke(
                    &Stroke::new(width.into()),
                    transform,
                    underline_brush,
                    None,
                    &line,
                );
            }
            if let Some(strikethrough) = &style.strikethrough {
                let strikethrough_brush = match &strikethrough.brush {
                    TextBrush::Normal(text) => text,
                    // It doesn't make sense for an underline to have a highlight colour, so we
                    // just use the text colour for the colour
                    TextBrush::Highlight { text, .. } => text,
                };
                let run_metrics = glyph_run.run().metrics();
                let offset = match strikethrough.offset {
                    Some(offset) => offset,
                    None => run_metrics.strikethrough_offset,
                };
                let width = match strikethrough.size {
                    Some(size) => size,
                    None => run_metrics.strikethrough_size,
                };
                // The `offset` is the distance from the baseline to the *top* of the strikethrough
                // so we move the line down by half the width
                // Remember that we are using a y-down coordinate system
                let y = glyph_run.baseline() - offset + width / 2.;

                let line = Line::new(
                    (glyph_run.offset() as f64, y as f64),
                    ((glyph_run.offset() + glyph_run.advance()) as f64, y as f64),
                );
                scratch_scene.stroke(
                    &Stroke::new(width.into()),
                    transform,
                    strikethrough_brush,
                    None,
                    &line,
                );
            }
        }
    }
    scene.append(scratch_scene, None);
}