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