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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
use std::sync::Arc;
use emath::{Align2, Rot2};
use crate::*;
/// How to paint some text on screen.
///
/// This needs to be recreated if `pixels_per_point` (dpi scale) changes.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TextShape {
/// Where the origin of [`Self::galley`] is.
///
/// Usually the top left corner of the first character.
pub pos: Pos2,
/// The laid out text, from [`FontsView::layout_job`].
pub galley: Arc<Galley>,
/// Add this underline to the whole text.
/// You can also set an underline when creating the galley.
pub underline: Stroke,
/// Any [`Color32::PLACEHOLDER`] in the galley will be replaced by the given color.
/// Affects everything: backgrounds, glyphs, strikethrough, underline, etc.
pub fallback_color: Color32,
/// If set, the text color in the galley will be ignored and replaced
/// with the given color.
///
/// This only affects the glyphs and will NOT replace background color nor strikethrough/underline color.
pub override_text_color: Option<Color32>,
/// If set, the text will be rendered with the given opacity in gamma space
/// Affects everything: backgrounds, glyphs, strikethrough, underline, etc.
pub opacity_factor: f32,
/// Rotate text by this many radians clockwise.
/// The pivot is `pos` (the upper left corner of the text).
pub angle: f32,
}
impl TextShape {
/// The given fallback color will be used for any uncolored part of the galley (using [`Color32::PLACEHOLDER`]).
///
/// Any non-placeholder color in the galley takes precedence over this fallback color.
#[inline]
pub fn new(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
Self {
pos,
galley,
underline: Stroke::NONE,
fallback_color,
override_text_color: None,
opacity_factor: 1.0,
angle: 0.0,
}
}
/// The visual bounding rectangle
#[inline]
pub fn visual_bounding_rect(&self) -> Rect {
self.galley
.mesh_bounds
.rotate_bb(emath::Rot2::from_angle(self.angle))
.translate(self.pos.to_vec2())
}
#[inline]
pub fn with_underline(mut self, underline: Stroke) -> Self {
self.underline = underline;
self
}
/// Use the given color for the text, regardless of what color is already in the galley.
#[inline]
pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self {
self.override_text_color = Some(override_text_color);
self
}
/// Set text rotation to `angle` radians clockwise.
/// The pivot is `pos` (the upper left corner of the text).
#[inline]
pub fn with_angle(mut self, angle: f32) -> Self {
self.angle = angle;
self
}
/// Set the text rotation to the `angle` radians clockwise.
/// The pivot is determined by the given `anchor` point on the text bounding box.
#[inline]
pub fn with_angle_and_anchor(mut self, angle: f32, anchor: Align2) -> Self {
self.angle = angle;
let a0 = anchor.pos_in_rect(&self.galley.rect).to_vec2();
let a1 = Rot2::from_angle(angle) * a0;
self.pos += a0 - a1;
self
}
/// Render text with this opacity in gamma space
#[inline]
pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {
self.opacity_factor = opacity_factor;
self
}
/// Move the shape by this many points, in-place.
pub fn transform(&mut self, transform: emath::TSTransform) {
let Self {
pos,
galley,
underline,
fallback_color: _,
override_text_color: _,
opacity_factor: _,
angle: _,
} = self;
*pos = transform * *pos;
underline.width *= transform.scaling;
let Galley {
job: _,
rows,
elided: _,
rect,
mesh_bounds,
num_vertices: _,
num_indices: _,
pixels_per_point: _,
intrinsic_size,
} = Arc::make_mut(galley);
*rect = transform.scaling * *rect;
*mesh_bounds = transform.scaling * *mesh_bounds;
*intrinsic_size = transform.scaling * *intrinsic_size;
for text::PlacedRow {
pos,
row,
ends_with_newline: _,
} in rows
{
*pos *= transform.scaling;
let text::Row {
section_index_at_start: _,
glyphs: _, // TODO(emilk): would it make sense to transform these?
size,
visuals,
} = Arc::make_mut(row);
*size *= transform.scaling;
let text::RowVisuals {
mesh,
mesh_bounds,
glyph_index_start: _,
glyph_vertex_range: _,
} = visuals;
*mesh_bounds = transform.scaling * *mesh_bounds;
for v in &mut mesh.vertices {
v.pos *= transform.scaling;
}
}
}
}
impl From<TextShape> for Shape {
#[inline(always)]
fn from(shape: TextShape) -> Self {
Self::Text(shape)
}
}
#[cfg(test)]
mod tests {
use super::{super::*, *};
use crate::text::FontDefinitions;
use emath::almost_equal;
#[test]
fn text_bounding_box_under_rotation() {
let mut fonts = Fonts::new(TextOptions::default(), FontDefinitions::default());
let font = FontId::monospace(12.0);
let mut t = crate::Shape::text(
&mut fonts.with_pixels_per_point(1.0),
Pos2::ZERO,
emath::Align2::CENTER_CENTER,
"testing123",
font,
Color32::BLACK,
);
let size_orig = t.visual_bounding_rect().size();
// 90 degree rotation
if let Shape::Text(ts) = &mut t {
ts.angle = std::f32::consts::PI / 2.0;
}
let size_rot = t.visual_bounding_rect().size();
// make sure the box is actually rotated
assert!(almost_equal(size_orig.x, size_rot.y, 1e-4));
assert!(almost_equal(size_orig.y, size_rot.x, 1e-4));
}
}