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}