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 {
141 pos,
142 row,
143 ends_with_newline: _,
144 } in rows
145 {
146 *pos *= transform.scaling;
147
148 let text::Row {
149 section_index_at_start: _,
150 glyphs: _, size,
152 visuals,
153 } = Arc::make_mut(row);
154
155 *size *= transform.scaling;
156
157 let text::RowVisuals {
158 mesh,
159 mesh_bounds,
160 glyph_index_start: _,
161 glyph_vertex_range: _,
162 } = visuals;
163
164 *mesh_bounds = transform.scaling * *mesh_bounds;
165
166 for v in &mut mesh.vertices {
167 v.pos *= transform.scaling;
168 }
169 }
170 }
171}
172
173impl From<TextShape> for Shape {
174 #[inline(always)]
175 fn from(shape: TextShape) -> Self {
176 Self::Text(shape)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::{super::*, *};
183 use crate::text::FontDefinitions;
184 use emath::almost_equal;
185
186 #[test]
187 fn text_bounding_box_under_rotation() {
188 let mut fonts = Fonts::new(TextOptions::default(), FontDefinitions::default());
189 let font = FontId::monospace(12.0);
190
191 let mut t = crate::Shape::text(
192 &mut fonts.with_pixels_per_point(1.0),
193 Pos2::ZERO,
194 emath::Align2::CENTER_CENTER,
195 "testing123",
196 font,
197 Color32::BLACK,
198 );
199
200 let size_orig = t.visual_bounding_rect().size();
201
202 if let Shape::Text(ts) = &mut t {
204 ts.angle = std::f32::consts::PI / 2.0;
205 }
206
207 let size_rot = t.visual_bounding_rect().size();
208
209 assert!(almost_equal(size_orig.x, size_rot.y, 1e-4));
211 assert!(almost_equal(size_orig.y, size_rot.x, 1e-4));
212 }
213}