epaint/shapes/
text_shape.rs1use std::sync::Arc;
2
3use emath::{Align2, Rot2};
4
5use crate::*;
6
7#[derive(Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
12pub struct TextShape {
13 pub pos: Pos2,
17
18 pub galley: Arc<Galley>,
20
21 pub underline: Stroke,
24
25 pub fallback_color: Color32,
28
29 pub override_text_color: Option<Color32>,
34
35 pub opacity_factor: f32,
38
39 pub angle: f32,
42}
43
44impl TextShape {
45 #[inline]
49 pub fn new(pos: Pos2, galley: Arc<Galley>, fallback_color: Color32) -> Self {
50 Self {
51 pos,
52 galley,
53 underline: Stroke::NONE,
54 fallback_color,
55 override_text_color: None,
56 opacity_factor: 1.0,
57 angle: 0.0,
58 }
59 }
60
61 #[inline]
63 pub fn visual_bounding_rect(&self) -> Rect {
64 self.galley
65 .mesh_bounds
66 .rotate_bb(emath::Rot2::from_angle(self.angle))
67 .translate(self.pos.to_vec2())
68 }
69
70 #[inline]
71 pub fn with_underline(mut self, underline: Stroke) -> Self {
72 self.underline = underline;
73 self
74 }
75
76 #[inline]
78 pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self {
79 self.override_text_color = Some(override_text_color);
80 self
81 }
82
83 #[inline]
86 pub fn with_angle(mut self, angle: f32) -> Self {
87 self.angle = angle;
88 self
89 }
90
91 #[inline]
94 pub fn with_angle_and_anchor(mut self, angle: f32, anchor: Align2) -> Self {
95 self.angle = angle;
96 let a0 = anchor.pos_in_rect(&self.galley.rect).to_vec2();
97 let a1 = Rot2::from_angle(angle) * a0;
98 self.pos += a0 - a1;
99 self
100 }
101
102 #[inline]
104 pub fn with_opacity_factor(mut self, opacity_factor: f32) -> Self {
105 self.opacity_factor = opacity_factor;
106 self
107 }
108
109 pub fn transform(&mut self, transform: emath::TSTransform) {
111 let Self {
112 pos,
113 galley,
114 underline,
115 fallback_color: _,
116 override_text_color: _,
117 opacity_factor: _,
118 angle: _,
119 } = self;
120
121 *pos = transform * *pos;
122 underline.width *= transform.scaling;
123
124 let Galley {
125 job: _,
126 rows,
127 elided: _,
128 rect,
129 mesh_bounds,
130 num_vertices: _,
131 num_indices: _,
132 pixels_per_point: _,
133 intrinsic_size,
134 } = Arc::make_mut(galley);
135
136 *rect = transform.scaling * *rect;
137 *mesh_bounds = transform.scaling * *mesh_bounds;
138 *intrinsic_size = transform.scaling * *intrinsic_size;
139
140 for text::PlacedRow { pos, row } in rows {
141 *pos *= transform.scaling;
142
143 let text::Row {
144 section_index_at_start: _,
145 glyphs: _, size,
147 visuals,
148 ends_with_newline: _,
149 } = Arc::make_mut(row);
150
151 *size *= transform.scaling;
152
153 let text::RowVisuals {
154 mesh,
155 mesh_bounds,
156 glyph_index_start: _,
157 glyph_vertex_range: _,
158 } = visuals;
159
160 *mesh_bounds = transform.scaling * *mesh_bounds;
161
162 for v in &mut mesh.vertices {
163 v.pos *= transform.scaling;
164 }
165 }
166 }
167}
168
169impl From<TextShape> for Shape {
170 #[inline(always)]
171 fn from(shape: TextShape) -> Self {
172 Self::Text(shape)
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::{super::*, *};
179 use crate::text::FontDefinitions;
180 use emath::almost_equal;
181
182 #[test]
183 fn text_bounding_box_under_rotation() {
184 let fonts = Fonts::new(
185 1.0,
186 1024,
187 AlphaFromCoverage::default(),
188 FontDefinitions::default(),
189 );
190 let font = FontId::monospace(12.0);
191
192 let mut t = crate::Shape::text(
193 &fonts,
194 Pos2::ZERO,
195 emath::Align2::CENTER_CENTER,
196 "testing123",
197 font,
198 Color32::BLACK,
199 );
200
201 let size_orig = t.visual_bounding_rect().size();
202
203 if let Shape::Text(ts) = &mut t {
205 ts.angle = std::f32::consts::PI / 2.0;
206 }
207
208 let size_rot = t.visual_bounding_rect().size();
209
210 assert!(almost_equal(size_orig.x, size_rot.y, 1e-4));
212 assert!(almost_equal(size_orig.y, size_rot.x, 1e-4));
213 }
214}