1use geometry_core::{Rect, Transform};
2
3use crate::DrawCommand;
4use crate::transform_clip_rect;
5
6#[derive(Clone, Copy)]
9pub struct FontMetrics {
10 pub line_height_factor: f32,
12 pub ascender_ratio: f32,
14}
15
16impl Default for FontMetrics {
17 fn default() -> Self {
18 Self {
19 line_height_factor: 1.2,
20 ascender_ratio: 0.25,
21 }
22 }
23}
24
25pub fn overlaps(x: f32, y: f32, w: f32, h: f32, clip: Option<Rect>) -> bool {
26 match clip {
27 None => true,
28 Some(c) => Rect::new(x, y, w, h).overlaps(c),
29 }
30}
31
32pub fn expand_for_shadow(
33 rect: Rect,
34 blur_radius: f32,
35 spread: f32,
36 offset_x: f32,
37 offset_y: f32,
38) -> Rect {
39 let expand = (blur_radius * 1.5).ceil() + 1.0 + spread;
41 let expanded = Rect::new(
42 rect.x - expand,
43 rect.y - expand,
44 rect.width + expand * 2.0,
45 rect.height + expand * 2.0,
46 );
47 let shifted = Rect::new(
48 expanded.x + offset_x,
49 expanded.y + offset_y,
50 expanded.width,
51 expanded.height,
52 );
53 rect.union(shifted)
54}
55
56pub fn command_visual_rect(
57 cmd: &DrawCommand,
58 matrix: [f32; 6],
59 font_metrics: &FontMetrics,
60) -> Option<Rect> {
61 match cmd {
62 DrawCommand::Rect { rect, style } => {
63 let r = transform_clip_rect(matrix, *rect);
64 let shadow = style.shadow;
65 Some(match shadow {
66 Some(s) => expand_for_shadow(r, s.blur_radius, s.spread, s.offset_x, s.offset_y),
67 None => r,
68 })
69 }
70 DrawCommand::Text { rect, style, .. } => {
71 let font_size = style.font_size;
73 let shadow = style.shadow;
74 let line_h = font_size * font_metrics.line_height_factor;
75 let ascender_overshoot = font_size * font_metrics.ascender_ratio;
76 let extra_bottom = (line_h - rect.height).max(0.0);
77 let r = transform_clip_rect(
78 matrix,
79 Rect::new(
80 rect.x,
81 rect.y - ascender_overshoot,
82 rect.width,
83 rect.height + ascender_overshoot + extra_bottom,
84 ),
85 );
86 Some(match shadow {
87 Some(s) => expand_for_shadow(r, s.blur_radius, s.spread, s.offset_x, s.offset_y),
88 None => r,
89 })
90 }
91 DrawCommand::RichText { rect, base, .. } => {
92 let font_size = base.font_size;
94 let line_h = font_size * font_metrics.line_height_factor;
95 let ascender_overshoot = font_size * font_metrics.ascender_ratio;
96 let extra_bottom = (line_h - rect.height).max(0.0);
97 let r = transform_clip_rect(
98 matrix,
99 Rect::new(
100 rect.x,
101 rect.y - ascender_overshoot,
102 rect.width,
103 rect.height + ascender_overshoot + extra_bottom,
104 ),
105 );
106 Some(match base.shadow {
107 Some(s) => expand_for_shadow(r, s.blur_radius, s.spread, s.offset_x, s.offset_y),
108 None => r,
109 })
110 }
111 DrawCommand::Image { rect, .. } => Some(transform_clip_rect(matrix, *rect)),
112 DrawCommand::Line { p1, p2, style } => {
113 let half_w = style.width / 2.0;
114 let t = Transform::from_array(matrix);
115 let m1 = t.apply(*p1);
116 let m2 = t.apply(*p2);
117 let x = m1.x.min(m2.x) - half_w;
118 let y = m1.y.min(m2.y) - half_w;
119 let right = m1.x.max(m2.x) + half_w;
120 let bottom = m1.y.max(m2.y) + half_w;
121 Some(Rect::new(x, y, right - x, bottom - y))
122 }
123 DrawCommand::Path { data, style } => {
124 let base = data.bounds()?;
125 let r = transform_clip_rect(matrix, base);
126 let shadow = style.shadow;
127 Some(match shadow {
128 Some(s) => expand_for_shadow(r, s.blur_radius, s.spread, s.offset_x, s.offset_y),
129 None => r,
130 })
131 }
132 DrawCommand::PushClip { .. }
133 | DrawCommand::PopClip
134 | DrawCommand::PushMatrix { .. }
135 | DrawCommand::PopMatrix
136 | DrawCommand::PushLayer { .. }
137 | DrawCommand::PopLayer => None,
138 }
139}
140
141pub fn extend_bounds(current: Option<Rect>, new_rect: Rect) -> Option<Rect> {
142 Some(current.map_or(new_rect, |b| b.union(new_rect)))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn overlaps_no_clip() {
151 assert!(overlaps(0.0, 0.0, 10.0, 10.0, None));
152 }
153
154 #[test]
155 fn overlaps_inside_clip() {
156 let clip = Rect::new(0.0, 0.0, 100.0, 100.0);
157 assert!(overlaps(10.0, 10.0, 20.0, 20.0, Some(clip)));
158 }
159
160 #[test]
161 fn overlaps_outside_clip() {
162 let clip = Rect::new(0.0, 0.0, 10.0, 10.0);
163 assert!(!overlaps(20.0, 20.0, 5.0, 5.0, Some(clip)));
164 }
165
166 #[test]
167 fn expand_for_shadow_expands_all_sides() {
168 let r = Rect::new(10.0, 10.0, 20.0, 20.0);
169 let result = expand_for_shadow(r, 5.0, 2.0, 0.0, 0.0);
170 assert!(result.x < r.x);
171 assert!(result.y < r.y);
172 assert!(result.x + result.width > r.x + r.width);
173 assert!(result.y + result.height > r.y + r.height);
174 }
175
176 #[test]
177 fn transform_clip_rect_identity() {
178 let r = transform_clip_rect(
179 Transform::IDENTITY.to_array(),
180 Rect::new(10.0, 20.0, 30.0, 40.0),
181 );
182 assert_eq!(r.x, 10.0);
183 assert_eq!(r.y, 20.0);
184 assert_eq!(r.width, 30.0);
185 assert_eq!(r.height, 40.0);
186 }
187
188 #[test]
189 fn transform_clip_rect_scale() {
190 let scale = [2.0, 0.0, 0.0, 2.0, 0.0, 0.0];
191 let r = transform_clip_rect(scale, Rect::new(5.0, 5.0, 10.0, 10.0));
192 assert!((r.x - 10.0).abs() < 1e-4);
193 assert!((r.y - 10.0).abs() < 1e-4);
194 assert!((r.width - 20.0).abs() < 1e-4);
195 assert!((r.height - 20.0).abs() < 1e-4);
196 }
197}