1use crate::platform::{IDrawingContextImpl, LtrbPixelRect, LtrbRect};
2use std::cell::Cell;
3use std::hash::{BuildHasher, Hasher};
4
5pub trait IDirtyRectTracker: IDirtyRectCollector {
10 fn finalize_frame(&self, bounds: LtrbRect);
12
13 fn begin_draw(&self, ctx: &mut dyn IDrawingContextImpl);
21
22 fn end_draw(&self, ctx: &mut dyn IDrawingContextImpl);
24
25 fn is_empty(&self) -> bool;
26
27 fn intersects(&self, rect: LtrbRect) -> bool;
28
29 fn initialize(&self, bounds: LtrbRect);
30
31 fn visualize(&self, context: &mut dyn IDrawingContextImpl);
33
34 fn combined_rect(&self) -> LtrbRect;
35
36 fn is_single_dirty_rect_tracker(&self) -> bool {
39 false
40 }
41}
42
43pub trait IDirtyRectCollector {
45 fn add_rect(&self, rect: LtrbRect);
46}
47
48pub(super) fn full_union(left: Option<LtrbRect>, right: Option<LtrbRect>) -> Option<LtrbRect> {
56 match (left, right) {
57 (None, right) => right,
58 (left, None) => left,
59 (Some(left), Some(right)) => Some(right.union(left)),
60 }
61}
62
63pub(super) fn contains_rect(outer: &LtrbRect, rect: LtrbRect) -> bool {
65 rect.left >= outer.left && rect.right <= outer.right && rect.top >= outer.top && rect.bottom <= outer.bottom
66}
67
68pub(super) fn pixel_rect_from_rect_unscaled(rect: LtrbRect) -> LtrbPixelRect {
71 LtrbPixelRect::new(rect.left as i32, rect.top as i32, rect.right.ceil() as i32, rect.bottom.ceil() as i32)
72}
73
74pub(super) fn pixel_rect_to_ltrb_rect_unscaled(rect: LtrbPixelRect) -> LtrbRect {
76 LtrbRect::new(rect.left as f64, rect.top as f64, rect.right as f64, rect.bottom as f64)
77}
78
79pub(super) struct VisualizeRandom {
82 state: Cell<u64>,
83}
84
85impl VisualizeRandom {
86 pub(super) fn new() -> Self {
87 let seed = std::collections::hash_map::RandomState::new().build_hasher().finish();
89 Self { state: Cell::new(seed | 1) }
90 }
91
92 pub(super) fn next(&self, max_value: i32) -> i32 {
94 let mut x = self.state.get();
96 x ^= x >> 12;
97 x ^= x << 25;
98 x ^= x >> 27;
99 self.state.set(x);
100 let value = x.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 33;
101 if max_value <= 0 {
102 0
103 } else {
104 (value % max_value as u64) as i32
105 }
106 }
107}
108
109#[cfg(test)]
110pub(crate) mod tests {
111 use super::*;
112 use crate::media::{BoxShadows, Color, IBrush, IPen, RenderOptions};
113 use crate::platform::{
114 IBitmapImpl, IDrawingContextLayerImpl, IGeometryImpl, IGlyphRunImpl, IPlatformRenderInterfaceRegion,
115 };
116 use crate::{Matrix, PixelSize, Point, Rect, RoundedRect};
117 use std::any::{Any, TypeId};
118 use std::cell::RefCell;
119 use std::rc::Rc;
120 use std::sync::Arc;
121
122 pub(crate) struct MockDrawingContext {
124 pub log: Vec<String>,
125 pub transform: Matrix,
126 }
127
128 impl Default for MockDrawingContext {
129 fn default() -> Self {
130 Self { log: Vec::new(), transform: Matrix::IDENTITY }
131 }
132 }
133
134 fn describe_brush(brush: Option<&dyn IBrush>) -> String {
135 match brush {
136 None => "none".to_string(),
137 Some(brush) => match brush.as_solid_color_brush() {
138 Some(solid) => format!("#{:08x}@{}", solid.color().to_uint32(), brush.opacity()),
139 None => "brush".to_string(),
140 },
141 }
142 }
143
144 impl IDrawingContextImpl for MockDrawingContext {
145 fn transform(&self) -> Matrix {
146 self.transform
147 }
148 fn set_transform(&mut self, value: Matrix) {
149 self.transform = value;
150 }
151 fn clear(&mut self, _color: Color) {
152 self.log.push("clear".to_string());
153 }
154 fn draw_bitmap(&mut self, _: &dyn IBitmapImpl, _: f64, _: Rect, _: Rect) {
155 self.log.push("bitmap".to_string());
156 }
157 fn draw_bitmap_with_mask(&mut self, _: &dyn IBitmapImpl, _: &dyn IBrush, _: Rect, _: Rect) {
158 self.log.push("bitmap_mask".to_string());
159 }
160 fn draw_line(&mut self, _: Option<&dyn IPen>, p1: Point, p2: Point) {
161 self.log.push(format!("line {p1} {p2}"));
162 }
163 fn draw_geometry(&mut self, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>, _: &dyn IGeometryImpl) {
164 let pen = pen.map(|p| describe_brush(p.brush().as_deref())).unwrap_or("none".to_string());
165 self.log.push(format!(
166 "geometry {} pen {pen} at {},{}",
167 describe_brush(brush),
168 self.transform.m31,
169 self.transform.m32
170 ));
171 }
172 fn draw_rectangle(
173 &mut self,
174 brush: Option<&dyn IBrush>,
175 _pen: Option<&dyn IPen>,
176 rect: RoundedRect,
177 _box_shadows: &BoxShadows,
178 ) {
179 self.log.push(format!("rect {} {}", describe_brush(brush), rect.rect));
180 }
181 fn draw_region(
182 &mut self,
183 brush: Option<&dyn IBrush>,
184 _pen: Option<&dyn IPen>,
185 region: &dyn IPlatformRenderInterfaceRegion,
186 ) {
187 let alpha = brush.and_then(|b| b.as_solid_color_brush()).map(|b| b.color().a).unwrap_or(0);
188 self.log.push(format!("region alpha {alpha} rects {}", region.rects().len()));
189 }
190 fn draw_ellipse(&mut self, _: Option<&dyn IBrush>, _: Option<&dyn IPen>, _: Rect) {
191 self.log.push("ellipse".to_string());
192 }
193 fn draw_glyph_run(&mut self, foreground: Option<&dyn IBrush>, glyph_run: &dyn IGlyphRunImpl) {
194 use crate::rendering::composition::server::diagnostic_text_renderer::tests::MockTextGlyphRunImpl;
195
196 if let Some(text) = glyph_run.as_any().downcast_ref::<MockTextGlyphRunImpl>() {
198 self.log.push(format!(
199 "text {} {} at {},{}",
200 text.0,
201 describe_brush(foreground),
202 self.transform.m31,
203 self.transform.m32
204 ));
205 return;
206 }
207
208 self.log.push(format!(
209 "glyph {} {} at {},{}",
210 glyph_run.font_rendering_em_size(),
211 describe_brush(foreground),
212 self.transform.m31,
213 self.transform.m32
214 ));
215 }
216 fn create_layer(&mut self, _: PixelSize) -> Rc<dyn IDrawingContextLayerImpl> {
217 unimplemented!()
218 }
219 fn push_clip(&mut self, clip: Rect) {
220 self.log.push(format!("push_clip {clip}"));
221 }
222 fn push_clip_rounded(&mut self, clip: RoundedRect) {
223 self.log.push(format!("push_clip_rounded {}", clip.rect));
224 }
225 fn push_clip_region(&mut self, region: &dyn IPlatformRenderInterfaceRegion) {
226 self.log.push(format!("push_clip_region {:?}", region.rects()));
227 }
228 fn pop_clip(&mut self) {
229 self.log.push("pop_clip".to_string());
230 }
231 fn push_layer(&mut self, _: Rect) {}
232 fn pop_layer(&mut self) {}
233 fn push_opacity(&mut self, _: f64, _: Option<Rect>) {}
234 fn pop_opacity(&mut self) {}
235 fn push_opacity_mask(&mut self, _: &dyn IBrush, _: Rect) {}
236 fn pop_opacity_mask(&mut self) {}
237 fn push_geometry_clip(&mut self, _: &dyn IGeometryImpl) {}
238 fn pop_geometry_clip(&mut self) {}
239 fn push_render_options(&mut self, _: RenderOptions) {}
240 fn pop_render_options(&mut self) {}
241 fn push_text_options(&mut self, _: crate::media::TextOptions) {}
242 fn pop_text_options(&mut self) {}
243 fn get_feature(&mut self, _: TypeId) -> Option<Rc<dyn Any>> {
244 None
245 }
246 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
247 self
248 }
249 fn dispose(&mut self) {}
250 }
251
252 #[derive(Default)]
254 pub(crate) struct MockRegion {
255 pub rects: RefCell<Vec<LtrbPixelRect>>,
256 }
257
258 impl IPlatformRenderInterfaceRegion for MockRegion {
259 fn add_rect(&self, rect: LtrbPixelRect) {
260 self.rects.borrow_mut().push(rect);
261 }
262 fn reset(&self) {
263 self.rects.borrow_mut().clear();
264 }
265 fn is_empty(&self) -> bool {
266 self.rects.borrow().is_empty()
267 }
268 fn bounds(&self) -> LtrbPixelRect {
269 let rects = self.rects.borrow();
270 let mut iter = rects.iter();
271 match iter.next() {
272 None => LtrbPixelRect::default(),
273 Some(first) => iter.fold(*first, |acc, r| acc.union(*r)),
274 }
275 }
276 fn rects(&self) -> Vec<LtrbPixelRect> {
277 self.rects.borrow().clone()
278 }
279 fn intersects(&self, rect: LtrbRect) -> bool {
280 self.rects.borrow().iter().any(|r| pixel_rect_to_ltrb_rect_unscaled(*r).intersects(rect))
281 }
282 fn contains(&self, pt: Point) -> bool {
283 self.rects.borrow().iter().any(|r| pixel_rect_to_ltrb_rect_unscaled(*r).contains(pt.x, pt.y))
284 }
285 fn dispose(&self) {}
286 fn as_any(&self) -> &dyn Any {
287 self
288 }
289 }
290
291 pub(crate) struct MockStreamGeometry {
294 pub log: Arc<std::sync::Mutex<Vec<String>>>,
295 }
296
297 struct MockStreamGeometryBuilder {
300 log: Arc<std::sync::Mutex<Vec<String>>>,
301 }
302
303 impl crate::platform::IGeometryContext for MockStreamGeometryBuilder {
304 fn arc_to(&mut self, _: Point, _: crate::Size, _: f64, _: bool, _: crate::media::SweepDirection, _: bool) {
305 self.log.lock().unwrap().push("arc".to_string());
306 }
307 fn begin_figure(&mut self, start_point: Point, is_filled: bool) {
308 self.log.lock().unwrap().push(format!("begin {start_point} {is_filled}"));
309 }
310 fn cubic_bezier_to(&mut self, _: Point, _: Point, _: Point, _: bool) {
311 self.log.lock().unwrap().push("cubic".to_string());
312 }
313 fn quadratic_bezier_to(&mut self, _: Point, _: Point, _: bool) {
314 self.log.lock().unwrap().push("quad".to_string());
315 }
316 fn line_to(&mut self, point: Point, is_stroked: bool) {
317 self.log.lock().unwrap().push(format!("line {point} {is_stroked}"));
318 }
319 fn end_figure(&mut self, is_closed: bool) {
320 self.log.lock().unwrap().push(format!("end {is_closed}"));
321 }
322 fn set_fill_rule(&mut self, _: crate::media::FillRule) {
323 self.log.lock().unwrap().push("fill".to_string());
324 }
325 fn dispose(&mut self) {
326 self.log.lock().unwrap().push("dispose".to_string());
327 }
328 }
329
330 impl crate::platform::IStreamGeometryBuilder for MockStreamGeometryBuilder {
331 fn to_geometry(&mut self) -> Arc<dyn IGeometryImpl> {
332 Arc::new(MockStreamGeometry { log: self.log.clone() })
333 }
334 }
335
336 impl IGeometryImpl for MockStreamGeometry {
337 fn as_any(&self) -> &dyn std::any::Any {
338 self
339 }
340 fn bounds(&self) -> Rect {
341 Rect::default()
342 }
343 fn contour_length(&self) -> f64 {
344 0.0
345 }
346 fn get_render_bounds(&self, _pen: Option<&dyn IPen>) -> Rect {
347 Rect::default()
348 }
349 fn get_widened_geometry(&self, _pen: &dyn IPen) -> Arc<dyn IGeometryImpl> {
350 unimplemented!()
351 }
352 fn fill_contains(&self, _point: Point) -> bool {
353 false
354 }
355 fn get_fill_intersection_result(&self, _geometry: &dyn IGeometryImpl) -> crate::media::IntersectionResult {
356 crate::media::IntersectionResult::Empty
357 }
358 fn intersect(&self, _geometry: &dyn IGeometryImpl) -> Option<Arc<dyn IGeometryImpl>> {
359 None
360 }
361 fn stroke_contains(&self, _pen: Option<&dyn IPen>, _point: Point) -> bool {
362 false
363 }
364 fn try_get_point_at_distance(&self, _distance: f64) -> Option<Point> {
365 None
366 }
367 fn try_get_point_and_tangent_at_distance(&self, _distance: f64) -> Option<(Point, Point)> {
368 None
369 }
370 fn try_get_segment(&self, _start: f64, _stop: f64, _begin: bool) -> Option<Arc<dyn IGeometryImpl>> {
371 None
372 }
373 fn with_transform(&self, _: Matrix) -> Arc<dyn crate::platform::ITransformedGeometryImpl> {
374 unimplemented!()
375 }
376 }
377
378 #[derive(Default)]
381 pub(crate) struct MockRenderInterface {
382 pub regions: RefCell<Vec<Rc<MockRegion>>>,
383 pub streams: RefCell<Vec<Arc<std::sync::Mutex<Vec<String>>>>>,
384 }
385
386 impl crate::platform::IPlatformRenderInterface for MockRenderInterface {
387 fn build_glyph_run_geometry(&self, _: &crate::media::GlyphRun) -> Arc<dyn IGeometryImpl> {
388 unimplemented!()
389 }
390 fn create_glyph_run(
391 &self,
392 _: &Rc<crate::media::GlyphTypeface>,
393 _: f64,
394 _: &[crate::media::text_formatting::GlyphInfo],
395 _: Point,
396 ) -> std::sync::Arc<dyn crate::platform::IGlyphRunImpl> {
397 unimplemented!()
398 }
399 fn create_ellipse_geometry(&self, _: Rect) -> Arc<dyn IGeometryImpl> {
400 unimplemented!()
401 }
402 fn create_line_geometry(&self, _: Point, _: Point) -> Arc<dyn IGeometryImpl> {
403 unimplemented!()
404 }
405 fn create_rectangle_geometry(&self, _: Rect) -> Arc<dyn IGeometryImpl> {
406 unimplemented!()
407 }
408 fn create_stream_geometry_builder(
409 &self,
410 _source: Option<&dyn IGeometryImpl>,
411 ) -> Box<dyn crate::platform::IStreamGeometryBuilder> {
412 let log = Arc::new(std::sync::Mutex::new(Vec::new()));
413 self.streams.borrow_mut().push(log.clone());
414 Box::new(MockStreamGeometryBuilder { log })
415 }
416 fn create_geometry_group(&self, _: crate::media::FillRule, _: &[Arc<dyn IGeometryImpl>]) -> Arc<dyn IGeometryImpl> {
417 unimplemented!()
418 }
419 fn create_combined_geometry(
420 &self,
421 _: crate::media::GeometryCombineMode,
422 _: Arc<dyn IGeometryImpl>,
423 _: Arc<dyn IGeometryImpl>,
424 ) -> Arc<dyn IGeometryImpl> {
425 unimplemented!()
426 }
427 fn create_render_target_bitmap(
428 &self,
429 _: crate::PixelSize,
430 _: crate::Vector,
431 ) -> std::sync::Arc<dyn crate::platform::IRenderTargetBitmapImpl> {
432 unimplemented!()
433 }
434 fn create_writeable_bitmap(
435 &self,
436 _: crate::PixelSize,
437 _: crate::Vector,
438 _: crate::platform::PixelFormat,
439 _: crate::platform::AlphaFormat,
440 ) -> std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl> {
441 unimplemented!()
442 }
443 fn load_bitmap_from_file(&self, _: &str) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
444 unimplemented!()
445 }
446 fn load_bitmap(&self, _: &mut dyn std::io::Read) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
447 unimplemented!()
448 }
449 fn load_writeable_bitmap_to_width(
450 &self,
451 _: &mut dyn std::io::Read,
452 _: i32,
453 _: crate::media::imaging::BitmapInterpolationMode,
454 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
455 unimplemented!()
456 }
457 fn load_writeable_bitmap_to_height(
458 &self,
459 _: &mut dyn std::io::Read,
460 _: i32,
461 _: crate::media::imaging::BitmapInterpolationMode,
462 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
463 unimplemented!()
464 }
465 fn load_writeable_bitmap_from_file(
466 &self,
467 _: &str,
468 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
469 unimplemented!()
470 }
471 fn load_writeable_bitmap(
472 &self,
473 _: &mut dyn std::io::Read,
474 ) -> std::io::Result<std::sync::Arc<dyn crate::platform::IWriteableBitmapImpl>> {
475 unimplemented!()
476 }
477 fn load_bitmap_to_width(
478 &self,
479 _: &mut dyn std::io::Read,
480 _: i32,
481 _: crate::media::imaging::BitmapInterpolationMode,
482 ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
483 unimplemented!()
484 }
485 fn load_bitmap_to_height(
486 &self,
487 _: &mut dyn std::io::Read,
488 _: i32,
489 _: crate::media::imaging::BitmapInterpolationMode,
490 ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
491 unimplemented!()
492 }
493 fn resize_bitmap(
494 &self,
495 _: &dyn crate::platform::IBitmapImpl,
496 _: crate::PixelSize,
497 _: crate::media::imaging::BitmapInterpolationMode,
498 ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
499 unimplemented!()
500 }
501 fn load_bitmap_from_pixels(
502 &self,
503 _: crate::platform::PixelFormat,
504 _: crate::platform::AlphaFormat,
505 _: &[u8],
506 _: crate::PixelSize,
507 _: crate::Vector,
508 _: i32,
509 ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
510 unimplemented!()
511 }
512 fn create_backend_context(
513 &self,
514 _: Option<Rc<dyn crate::platform::IPlatformGraphicsContext>>,
515 ) -> Rc<dyn crate::platform::IPlatformRenderInterfaceContext> {
516 unimplemented!()
517 }
518 fn supports_individual_round_rects(&self) -> bool {
519 false
520 }
521 fn default_alpha_format(&self) -> crate::platform::AlphaFormat {
522 crate::platform::AlphaFormat::Premul
523 }
524 fn default_pixel_format(&self) -> crate::platform::PixelFormat {
525 crate::platform::PixelFormats::RGBA8888
526 }
527 fn is_supported_bitmap_pixel_format(&self, _: crate::platform::PixelFormat) -> bool {
528 true
529 }
530 fn supports_regions(&self) -> bool {
531 true
532 }
533 fn create_region(&self) -> Rc<dyn IPlatformRenderInterfaceRegion> {
534 let region = Rc::new(MockRegion::default());
535 self.regions.borrow_mut().push(region.clone());
536 region
537 }
538 }
539
540 #[test]
541 fn full_union_treats_none_as_identity() {
542 let a = LtrbRect::new(0.0, 0.0, 10.0, 10.0);
543 let b = LtrbRect::new(5.0, 5.0, 20.0, 30.0);
544 assert_eq!(None, full_union(None, None));
545 assert_eq!(Some(a), full_union(Some(a), None));
546 assert_eq!(Some(b), full_union(None, Some(b)));
547 assert_eq!(Some(LtrbRect::new(0.0, 0.0, 20.0, 30.0)), full_union(Some(a), Some(b)));
548 }
549
550 #[test]
551 fn pixel_rect_conversion_truncates_left_top_and_ceils_right_bottom() {
552 let px = pixel_rect_from_rect_unscaled(LtrbRect::new(1.7, 2.2, 3.1, 4.0));
553 assert_eq!(LtrbPixelRect::new(1, 2, 4, 4), px);
554 let px = pixel_rect_from_rect_unscaled(LtrbRect::new(-1.7, -0.2, -0.5, 0.5));
556 assert_eq!(LtrbPixelRect::new(-1, 0, 0, 1), px);
557 assert_eq!(LtrbRect::new(-1.0, 0.0, 0.0, 1.0), pixel_rect_to_ltrb_rect_unscaled(px));
558 }
559
560 #[test]
561 fn contains_rect_is_inclusive() {
562 let outer = LtrbRect::new(0.0, 0.0, 10.0, 10.0);
563 assert!(contains_rect(&outer, outer));
564 assert!(contains_rect(&outer, LtrbRect::new(1.0, 1.0, 9.0, 9.0)));
565 assert!(!contains_rect(&outer, LtrbRect::new(1.0, 1.0, 10.5, 9.0)));
566 }
567
568 #[test]
569 fn visualize_random_stays_in_range() {
570 let random = VisualizeRandom::new();
571 for _ in 0..1000 {
572 let value = random.next(255);
573 assert!((0..255).contains(&value));
574 }
575 }
576}