1use std::{ops::AddAssign, rc::Rc};
4
5use crate::{
6 ArcRecordArgs, Brush, Color, ColorFilter, CommandRecorder, CommandRecording, ImageBitmap,
7 ImageSampling, normalized_band,
8 stroke::Stroke,
9 typography::{
10 DrawTextMeasurer, DrawTextStyle, TextAlign, TextMeasurement, TextVerticalAlign,
11 estimate_text_measurement,
12 },
13};
14
15const VECTOR_PATH_MASK_CACHE_ENTRIES: usize = 96;
16const VECTOR_PATH_MASK_CACHE_BYTES: usize = 8 * 1024 * 1024;
17
18struct VectorPathMaskCache {
19 entries: Vec<(u64, ImageBitmap)>,
20 bytes: usize,
21}
22
23impl VectorPathMaskCache {
24 const fn new() -> Self {
25 Self {
26 entries: Vec::new(),
27 bytes: 0,
28 }
29 }
30
31 fn get(&mut self, key: u64) -> Option<ImageBitmap> {
32 let index = self.entries.iter().position(|(seen, _)| *seen == key)?;
33 let entry = self.entries.remove(index);
34 let image = entry.1.clone();
35 self.entries.push(entry);
36 Some(image)
37 }
38
39 fn put(&mut self, key: u64, image: ImageBitmap) {
40 let bytes = image.width() as usize * image.height() as usize * 4;
41 if bytes > VECTOR_PATH_MASK_CACHE_BYTES {
42 return;
43 }
44 self.bytes += bytes;
45 self.entries.push((key, image));
46 while self.entries.len() > VECTOR_PATH_MASK_CACHE_ENTRIES
47 || self.bytes > VECTOR_PATH_MASK_CACHE_BYTES
48 {
49 let (_, dropped) = self.entries.remove(0);
50 self.bytes = self
51 .bytes
52 .saturating_sub(dropped.width() as usize * dropped.height() as usize * 4);
53 }
54 }
55}
56
57thread_local! {
58 static VECTOR_PATH_MASKS: std::cell::RefCell<VectorPathMaskCache> =
59 const { std::cell::RefCell::new(VectorPathMaskCache::new()) };
60}
61
62fn vector_path_mask_key(
63 path: &crate::VectorPath,
64 origin: Point,
65 mask_size: (usize, usize),
66 rgb: [u8; 3],
67 alpha: f32,
68) -> u64 {
69 use std::hash::Hasher;
70 let mut hasher = crate::fx_hash::FxHasher::default();
71 hasher.write_u8(path.fill_rule() as u8);
72 hasher.write_u32(origin.x.to_bits());
73 hasher.write_u32(origin.y.to_bits());
74 hasher.write_usize(mask_size.0);
75 hasher.write_usize(mask_size.1);
76 hasher.write(&rgb);
77 hasher.write_u32(alpha.to_bits());
78 for subpath in path.subpaths() {
79 hasher.write_usize(subpath.len());
80 for point in subpath {
81 hasher.write_u32(point.x.to_bits());
82 hasher.write_u32(point.y.to_bits());
83 }
84 }
85 hasher.finish()
86}
87
88fn vector_path_mask_cache_get(key: u64) -> Option<ImageBitmap> {
89 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().get(key))
90}
91
92fn vector_path_mask_cache_put(key: u64, image: ImageBitmap) {
93 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().put(key, image));
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Default)]
97pub struct Point {
98 pub x: f32,
99 pub y: f32,
100}
101
102impl Point {
103 pub const fn new(x: f32, y: f32) -> Self {
104 Self { x, y }
105 }
106
107 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Default)]
111pub struct Size {
112 pub width: f32,
113 pub height: f32,
114}
115
116impl Size {
117 pub const fn new(width: f32, height: f32) -> Self {
118 Self { width, height }
119 }
120
121 pub const ZERO: Size = Size {
122 width: 0.0,
123 height: 0.0,
124 };
125}
126
127#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct Rect {
129 pub x: f32,
130 pub y: f32,
131 pub width: f32,
132 pub height: f32,
133}
134
135impl Rect {
136 pub fn from_origin_size(origin: Point, size: Size) -> Self {
137 Self {
138 x: origin.x,
139 y: origin.y,
140 width: size.width,
141 height: size.height,
142 }
143 }
144
145 pub fn from_size(size: Size) -> Self {
146 Self {
147 x: 0.0,
148 y: 0.0,
149 width: size.width,
150 height: size.height,
151 }
152 }
153
154 pub fn translate(&self, dx: f32, dy: f32) -> Self {
155 Self {
156 x: self.x + dx,
157 y: self.y + dy,
158 width: self.width,
159 height: self.height,
160 }
161 }
162
163 pub fn contains(&self, x: f32, y: f32) -> bool {
164 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
165 }
166
167 pub fn intersect(&self, other: Rect) -> Option<Rect> {
169 let left = self.x.max(other.x);
170 let top = self.y.max(other.y);
171 let right = (self.x + self.width).min(other.x + other.width);
172 let bottom = (self.y + self.height).min(other.y + other.height);
173 let width = right - left;
174 let height = bottom - top;
175 if width <= 0.0 || height <= 0.0 {
176 None
177 } else {
178 Some(Rect {
179 x: left,
180 y: top,
181 width,
182 height,
183 })
184 }
185 }
186
187 pub fn union(&self, other: Rect) -> Rect {
188 let left = self.x.min(other.x);
189 let top = self.y.min(other.y);
190 let right = (self.x + self.width).max(other.x + other.width);
191 let bottom = (self.y + self.height).max(other.y + other.height);
192 Rect {
193 x: left,
194 y: top,
195 width: (right - left).max(0.0),
196 height: (bottom - top).max(0.0),
197 }
198 }
199}
200
201#[derive(Clone, Copy, Debug, Default, PartialEq)]
203pub struct EdgeInsets {
204 pub left: f32,
205 pub top: f32,
206 pub right: f32,
207 pub bottom: f32,
208}
209
210impl EdgeInsets {
211 pub fn uniform(all: f32) -> Self {
212 Self {
213 left: all,
214 top: all,
215 right: all,
216 bottom: all,
217 }
218 }
219
220 pub fn horizontal(horizontal: f32) -> Self {
221 Self {
222 left: horizontal,
223 right: horizontal,
224 ..Self::default()
225 }
226 }
227
228 pub fn vertical(vertical: f32) -> Self {
229 Self {
230 top: vertical,
231 bottom: vertical,
232 ..Self::default()
233 }
234 }
235
236 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
237 Self {
238 left: horizontal,
239 right: horizontal,
240 top: vertical,
241 bottom: vertical,
242 }
243 }
244
245 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
246 Self {
247 left,
248 top,
249 right,
250 bottom,
251 }
252 }
253
254 pub fn is_zero(&self) -> bool {
255 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
256 }
257
258 pub fn horizontal_sum(&self) -> f32 {
259 self.left + self.right
260 }
261
262 pub fn vertical_sum(&self) -> f32 {
263 self.top + self.bottom
264 }
265}
266
267impl AddAssign for EdgeInsets {
268 fn add_assign(&mut self, rhs: Self) {
269 self.left += rhs.left;
270 self.top += rhs.top;
271 self.right += rhs.right;
272 self.bottom += rhs.bottom;
273 }
274}
275
276#[derive(Clone, Copy, Debug, Default, PartialEq)]
277pub struct CornerRadii {
278 pub top_left: f32,
279 pub top_right: f32,
280 pub bottom_right: f32,
281 pub bottom_left: f32,
282}
283
284impl CornerRadii {
285 pub fn uniform(radius: f32) -> Self {
286 Self {
287 top_left: radius,
288 top_right: radius,
289 bottom_right: radius,
290 bottom_left: radius,
291 }
292 }
293}
294
295#[derive(Clone, Copy, Debug, PartialEq)]
296pub struct RoundedCornerShape {
297 radii: CornerRadii,
298}
299
300impl RoundedCornerShape {
301 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
302 Self {
303 radii: CornerRadii {
304 top_left,
305 top_right,
306 bottom_right,
307 bottom_left,
308 },
309 }
310 }
311
312 pub fn uniform(radius: f32) -> Self {
313 Self {
314 radii: CornerRadii::uniform(radius),
315 }
316 }
317
318 pub fn with_radii(radii: CornerRadii) -> Self {
319 Self { radii }
320 }
321
322 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
323 let mut resolved = self.radii;
324 let max_width = (width / 2.0).max(0.0);
325 let max_height = (height / 2.0).max(0.0);
326 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
327 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
328 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
329 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
330 resolved
331 }
332
333 pub fn radii(&self) -> CornerRadii {
334 self.radii
335 }
336}
337
338#[derive(Clone, Copy, Debug, PartialEq)]
339pub struct TransformOrigin {
340 pub pivot_fraction_x: f32,
341 pub pivot_fraction_y: f32,
342}
343
344impl TransformOrigin {
345 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
346 Self {
347 pivot_fraction_x,
348 pivot_fraction_y,
349 }
350 }
351
352 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
353}
354
355impl Default for TransformOrigin {
356 fn default() -> Self {
357 Self::CENTER
358 }
359}
360
361#[derive(Clone, Copy, Debug, Default, PartialEq)]
362pub enum LayerShape {
363 #[default]
364 Rectangle,
365 Rounded(RoundedCornerShape),
366}
367
368#[derive(Clone, Debug, PartialEq)]
369pub struct GraphicsLayer {
370 pub alpha: f32,
371 pub scale: f32,
372 pub scale_x: f32,
373 pub scale_y: f32,
374 pub rotation_x: f32,
375 pub rotation_y: f32,
376 pub rotation_z: f32,
377 pub camera_distance: f32,
378 pub transform_origin: TransformOrigin,
379 pub translation_x: f32,
380 pub translation_y: f32,
381 pub shadow_elevation: f32,
382 pub ambient_shadow_color: Color,
383 pub spot_shadow_color: Color,
384 pub shape: LayerShape,
385 pub clip: bool,
386 pub compositing_strategy: CompositingStrategy,
387 pub blend_mode: BlendMode,
388 pub color_filter: Option<ColorFilter>,
389 pub render_effect: Option<crate::render_effect::RenderEffect>,
390 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
391}
392
393impl GraphicsLayer {
394 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
418 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
419 }
420}
421
422impl Default for GraphicsLayer {
423 fn default() -> Self {
424 Self {
425 alpha: 1.0,
426 scale: 1.0,
427 scale_x: 1.0,
428 scale_y: 1.0,
429 rotation_x: 0.0,
430 rotation_y: 0.0,
431 rotation_z: 0.0,
432 camera_distance: 8.0,
433 transform_origin: TransformOrigin::CENTER,
434 translation_x: 0.0,
435 translation_y: 0.0,
436 shadow_elevation: 0.0,
437 ambient_shadow_color: Color::BLACK,
438 spot_shadow_color: Color::BLACK,
439 shape: LayerShape::Rectangle,
440 clip: false,
441 compositing_strategy: CompositingStrategy::Auto,
442 blend_mode: BlendMode::SrcOver,
443 color_filter: None,
444 render_effect: None,
445 backdrop_effect: None,
446 }
447 }
448}
449
450#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
455pub enum BlendMode {
456 Clear,
457 Src,
458 Dst,
459 #[default]
460 SrcOver,
461 DstOver,
462 SrcIn,
463 DstIn,
464 SrcOut,
465 DstOut,
466 SrcAtop,
467 DstAtop,
468 Xor,
469 Plus,
470 Modulate,
471 Screen,
472 Overlay,
473 Darken,
474 Lighten,
475 ColorDodge,
476 ColorBurn,
477 HardLight,
478 SoftLight,
479 Difference,
480 Exclusion,
481 Multiply,
482 Hue,
483 Saturation,
484 Color,
485 Luminosity,
486}
487
488impl BlendMode {
490 pub const ALL: [BlendMode; 29] = [
492 BlendMode::Clear,
493 BlendMode::Src,
494 BlendMode::Dst,
495 BlendMode::SrcOver,
496 BlendMode::DstOver,
497 BlendMode::SrcIn,
498 BlendMode::DstIn,
499 BlendMode::SrcOut,
500 BlendMode::DstOut,
501 BlendMode::SrcAtop,
502 BlendMode::DstAtop,
503 BlendMode::Xor,
504 BlendMode::Plus,
505 BlendMode::Modulate,
506 BlendMode::Screen,
507 BlendMode::Overlay,
508 BlendMode::Darken,
509 BlendMode::Lighten,
510 BlendMode::ColorDodge,
511 BlendMode::ColorBurn,
512 BlendMode::HardLight,
513 BlendMode::SoftLight,
514 BlendMode::Difference,
515 BlendMode::Exclusion,
516 BlendMode::Multiply,
517 BlendMode::Hue,
518 BlendMode::Saturation,
519 BlendMode::Color,
520 BlendMode::Luminosity,
521 ];
522}
523
524#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
525pub enum CompositingStrategy {
526 #[default]
528 Auto,
529 Offscreen,
531 ModulateAlpha,
533}
534
535#[derive(Clone, Debug, PartialEq)]
536pub enum DrawPrimitive {
537 Content,
540 Blend {
542 primitive: Box<DrawPrimitive>,
543 blend_mode: BlendMode,
544 },
545 Rect {
546 rect: Rect,
547 brush: Brush,
548 stroke: Option<Stroke>,
551 },
552 RoundRect {
553 rect: Rect,
554 brush: Brush,
555 radii: CornerRadii,
556 stroke: Option<Stroke>,
559 },
560 Arc {
570 rect: Rect,
574 brush: Brush,
575 center: Point,
576 radius: f32,
577 start_angle: f32,
578 sweep_angle: f32,
579 stroke: Option<Stroke>,
580 inner_radius: f32,
582 },
583 Image {
584 rect: Rect,
585 image: ImageBitmap,
586 alpha: f32,
587 color_filter: Option<ColorFilter>,
588 sampling: ImageSampling,
589 src_rect: Option<Rect>,
593 },
594 Text(Box<TextPrimitive>),
596 Shadow(ShadowPrimitive),
599}
600
601#[derive(Clone, Debug, PartialEq)]
610pub struct TextPrimitive {
611 pub rect: Rect,
614 pub text: std::rc::Rc<str>,
617 pub style: DrawTextStyle,
618 pub color: Color,
622}
623
624fn shared_text_str(text: &str) -> Rc<str> {
634 use std::{
635 cell::RefCell,
636 collections::HashMap,
637 hash::{Hash, Hasher},
638 };
639
640 const POOL_CAPACITY: usize = 256;
641 thread_local! {
642 static POOL: RefCell<HashMap<u64, Rc<str>>> = RefCell::new(HashMap::new());
643 }
644
645 let mut hasher = crate::FxHasher::default();
646 text.hash(&mut hasher);
647 let key = hasher.finish();
648
649 POOL.with(|pool| {
650 let mut pool = pool.borrow_mut();
651 if let Some(shared) = pool.get(&key)
652 && &**shared == text
653 {
654 return Rc::clone(shared);
655 }
656 let shared: Rc<str> = Rc::from(text);
657 if pool.len() >= POOL_CAPACITY {
658 pool.clear();
659 }
660 pool.insert(key, Rc::clone(&shared));
661 shared
662 })
663}
664
665#[derive(Clone, Debug, PartialEq)]
667pub enum ShadowPrimitive {
668 Drop {
672 shape: Box<DrawPrimitive>,
673 cutout: Option<Box<DrawPrimitive>>,
674 blur_radius: f32,
675 blend_mode: BlendMode,
676 },
677 Inner {
679 fill: Box<DrawPrimitive>,
680 cutout: Box<DrawPrimitive>,
681 blur_radius: f32,
682 blend_mode: BlendMode,
683 clip_rect: Rect,
685 },
686}
687
688pub trait DrawScope {
689 fn size(&self) -> Size;
690 fn draw_content(&mut self);
691 fn draw_rect(&mut self, brush: Brush);
692 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
693 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
695 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
696 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
697 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
698 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
700 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
701 fn draw_circle_blend(
702 &mut self,
703 brush: Brush,
704 center: Point,
705 radius: f32,
706 blend_mode: BlendMode,
707 );
708
709 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
711 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
712 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
714 fn draw_rect_at_stroked_blend(
715 &mut self,
716 rect: Rect,
717 brush: Brush,
718 stroke: Stroke,
719 blend_mode: BlendMode,
720 );
721 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
723 fn draw_round_rect_stroked_blend(
724 &mut self,
725 brush: Brush,
726 radii: CornerRadii,
727 stroke: Stroke,
728 blend_mode: BlendMode,
729 );
730 fn draw_round_rect_at_stroked(
732 &mut self,
733 rect: Rect,
734 brush: Brush,
735 radii: CornerRadii,
736 stroke: Stroke,
737 );
738 #[allow(clippy::too_many_arguments)]
739 fn draw_round_rect_at_stroked_blend(
740 &mut self,
741 rect: Rect,
742 brush: Brush,
743 radii: CornerRadii,
744 stroke: Stroke,
745 blend_mode: BlendMode,
746 );
747 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
750 fn draw_circle_stroked_blend(
751 &mut self,
752 brush: Brush,
753 center: Point,
754 radius: f32,
755 stroke: Stroke,
756 blend_mode: BlendMode,
757 );
758
759 #[allow(clippy::too_many_arguments)]
772 fn draw_arc(
773 &mut self,
774 brush: Brush,
775 center: Point,
776 radius: f32,
777 start_angle: f32,
778 sweep_angle: f32,
779 stroke: Stroke,
780 );
781 #[allow(clippy::too_many_arguments)]
782 fn draw_arc_blend(
783 &mut self,
784 brush: Brush,
785 center: Point,
786 radius: f32,
787 start_angle: f32,
788 sweep_angle: f32,
789 stroke: Stroke,
790 blend_mode: BlendMode,
791 );
792
793 #[allow(clippy::too_many_arguments)]
802 fn draw_annular_sector(
803 &mut self,
804 brush: Brush,
805 center: Point,
806 inner_radius: f32,
807 outer_radius: f32,
808 start_angle: f32,
809 sweep_angle: f32,
810 );
811 #[allow(clippy::too_many_arguments)]
812 fn draw_annular_sector_blend(
813 &mut self,
814 brush: Brush,
815 center: Point,
816 inner_radius: f32,
817 outer_radius: f32,
818 start_angle: f32,
819 sweep_angle: f32,
820 blend_mode: BlendMode,
821 );
822
823 fn draw_image(&mut self, image: ImageBitmap);
824 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
825 fn draw_image_at(
826 &mut self,
827 rect: Rect,
828 image: ImageBitmap,
829 alpha: f32,
830 color_filter: Option<ColorFilter>,
831 );
832 fn draw_image_at_sampled(
833 &mut self,
834 rect: Rect,
835 image: ImageBitmap,
836 alpha: f32,
837 color_filter: Option<ColorFilter>,
838 sampling: ImageSampling,
839 );
840 fn draw_image_at_blend(
841 &mut self,
842 rect: Rect,
843 image: ImageBitmap,
844 alpha: f32,
845 color_filter: Option<ColorFilter>,
846 blend_mode: BlendMode,
847 );
848 fn draw_image_src(
851 &mut self,
852 image: ImageBitmap,
853 src_rect: Rect,
854 dst_rect: Rect,
855 alpha: f32,
856 color_filter: Option<ColorFilter>,
857 );
858 fn draw_image_src_sampled(
859 &mut self,
860 image: ImageBitmap,
861 src_rect: Rect,
862 dst_rect: Rect,
863 alpha: f32,
864 color_filter: Option<ColorFilter>,
865 sampling: ImageSampling,
866 );
867 fn draw_image_src_blend(
868 &mut self,
869 image: ImageBitmap,
870 src_rect: Rect,
871 dst_rect: Rect,
872 alpha: f32,
873 color_filter: Option<ColorFilter>,
874 blend_mode: BlendMode,
875 );
876 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
885 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
891 if let Ok(path) = crate::VectorPath::parse(d) {
892 self.draw_vector_path(&path, brush);
893 }
894 }
895
896 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
903
904 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
907 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
908 }
909
910 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
917
918 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
924 if text.is_empty() {
925 return;
926 }
927 let measurement = self.measure_text(text, style);
928 self.draw_text_at(
929 Rect::from_origin_size(top_left, measurement.size),
930 brush,
931 text,
932 &DrawTextStyle {
933 align: TextAlign::Left,
934 vertical_align: TextVerticalAlign::Top,
935 ..style.clone()
936 },
937 );
938 }
939
940 fn into_primitives(self) -> Vec<DrawPrimitive>;
941}
942
943pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
949 let x = match style.align {
950 TextAlign::Left => rect.x,
951 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
952 TextAlign::Right => rect.x + rect.width - measurement.size.width,
953 };
954 let y = match style.vertical_align {
955 TextVerticalAlign::Top => rect.y,
956 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
957 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
958 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
959 };
960 Point::new(x, y)
961}
962
963#[derive(Default)]
964pub struct DrawScopeDefault {
965 size: Size,
966 recording: CommandRecorder,
967 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
968}
969
970const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
971
972thread_local! {
973 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
974 std::cell::RefCell::new(std::collections::HashMap::new());
975}
976
977fn recorded_primitive_capacity(size: Size) -> usize {
978 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
979 counts
980 .borrow()
981 .get(&(size.width.to_bits(), size.height.to_bits()))
982 .copied()
983 .unwrap_or(0)
984 })
985}
986
987fn note_recorded_primitive_count(size: Size, count: usize) {
988 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
989 let mut counts = counts.borrow_mut();
990 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
991 counts.clear();
992 }
993 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
994 });
995}
996
997impl DrawScopeDefault {
998 pub fn new(size: Size) -> Self {
999 Self::with_storage(size, None, CommandRecording::default())
1000 }
1001
1002 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1007 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1008 }
1009
1010 pub fn with_text_measurer_reusing(
1013 size: Size,
1014 text_measurer: Rc<dyn DrawTextMeasurer>,
1015 storage: CommandRecording,
1016 ) -> Self {
1017 Self::with_storage(size, Some(text_measurer), storage)
1018 }
1019
1020 fn with_storage(
1021 size: Size,
1022 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1023 recording: CommandRecording,
1024 ) -> Self {
1025 let mut recording = CommandRecorder::reusing(recording);
1026 recording.reserve_shapes(recorded_primitive_capacity(size));
1027 Self {
1028 size,
1029 recording,
1030 text_measurer,
1031 }
1032 }
1033
1034 pub fn content_marker_count(&self) -> u32 {
1036 self.recording.content_markers()
1037 }
1038
1039 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1041 for primitive in primitives {
1042 self.recording.push_primitive(primitive);
1043 }
1044 }
1045
1046 pub fn finish(self) -> CommandRecording {
1049 note_recorded_primitive_count(self.size, self.recording.len());
1050 self.recording.finish()
1051 }
1052
1053 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1054 if blend_mode != BlendMode::SrcOver {
1055 self.recording.push_other(DrawPrimitive::Blend {
1056 primitive: Box::new(primitive),
1057 blend_mode,
1058 });
1059 return;
1060 }
1061 self.recording.push_other(primitive);
1062 }
1063
1064 #[allow(clippy::too_many_arguments)]
1065 #[inline]
1066 fn push_arc(
1067 &mut self,
1068 brush: Brush,
1069 center: Point,
1070 radius: f32,
1071 start_angle: f32,
1072 sweep_angle: f32,
1073 stroke: Option<Stroke>,
1074 inner_radius: f32,
1075 blend_mode: BlendMode,
1076 ) {
1077 let args = ArcRecordArgs {
1078 brush: &brush,
1079 center,
1080 radius,
1081 start_angle,
1082 sweep_angle,
1083 stroke,
1084 inner_radius,
1085 blend_mode,
1086 };
1087 let geometry = normalized_band(&args);
1088 if geometry.is_degenerate() {
1089 return;
1090 }
1091 self.recording.push_scope_arc(&args, &geometry);
1092 }
1093}
1094
1095impl DrawScope for DrawScopeDefault {
1096 fn size(&self) -> Size {
1097 self.size
1098 }
1099
1100 fn draw_content(&mut self) {
1101 self.recording.push_content();
1102 }
1103
1104 fn draw_rect(&mut self, brush: Brush) {
1105 self.draw_rect_blend(brush, BlendMode::SrcOver);
1106 }
1107
1108 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1109 self.recording
1110 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1111 }
1112
1113 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1114 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1115 }
1116
1117 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1118 self.recording.push_rect(rect, &brush, None, blend_mode);
1119 }
1120
1121 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1122 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1123 }
1124
1125 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1126 self.recording
1127 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1128 }
1129
1130 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1131 self.recording
1132 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1133 }
1134
1135 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1136 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1137 }
1138
1139 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1140 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1141 }
1142
1143 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1144 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1145 }
1146
1147 fn draw_rect_at_stroked_blend(
1148 &mut self,
1149 rect: Rect,
1150 brush: Brush,
1151 stroke: Stroke,
1152 blend_mode: BlendMode,
1153 ) {
1154 if !stroke.is_visible() {
1155 return;
1156 }
1157 self.recording
1158 .push_rect(rect, &brush, Some(stroke), blend_mode);
1159 }
1160
1161 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1162 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1163 }
1164
1165 fn draw_round_rect_stroked_blend(
1166 &mut self,
1167 brush: Brush,
1168 radii: CornerRadii,
1169 stroke: Stroke,
1170 blend_mode: BlendMode,
1171 ) {
1172 self.draw_round_rect_at_stroked_blend(
1173 Rect::from_size(self.size),
1174 brush,
1175 radii,
1176 stroke,
1177 blend_mode,
1178 );
1179 }
1180
1181 fn draw_round_rect_at_stroked(
1182 &mut self,
1183 rect: Rect,
1184 brush: Brush,
1185 radii: CornerRadii,
1186 stroke: Stroke,
1187 ) {
1188 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1189 }
1190
1191 fn draw_round_rect_at_stroked_blend(
1192 &mut self,
1193 rect: Rect,
1194 brush: Brush,
1195 radii: CornerRadii,
1196 stroke: Stroke,
1197 blend_mode: BlendMode,
1198 ) {
1199 if !stroke.is_visible() {
1200 return;
1201 }
1202 self.recording
1203 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1204 }
1205
1206 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1207 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1208 }
1209
1210 fn draw_circle_stroked_blend(
1211 &mut self,
1212 brush: Brush,
1213 center: Point,
1214 radius: f32,
1215 stroke: Stroke,
1216 blend_mode: BlendMode,
1217 ) {
1218 if !stroke.is_visible() || !radius.is_finite() {
1219 return;
1220 }
1221 let radius = radius.max(0.0);
1222 let diameter = radius * 2.0;
1223 self.draw_round_rect_at_stroked_blend(
1224 Rect {
1225 x: center.x - radius,
1226 y: center.y - radius,
1227 width: diameter,
1228 height: diameter,
1229 },
1230 brush,
1231 CornerRadii::uniform(radius),
1232 stroke,
1233 blend_mode,
1234 );
1235 }
1236
1237 fn draw_arc(
1238 &mut self,
1239 brush: Brush,
1240 center: Point,
1241 radius: f32,
1242 start_angle: f32,
1243 sweep_angle: f32,
1244 stroke: Stroke,
1245 ) {
1246 self.draw_arc_blend(
1247 brush,
1248 center,
1249 radius,
1250 start_angle,
1251 sweep_angle,
1252 stroke,
1253 BlendMode::SrcOver,
1254 );
1255 }
1256
1257 fn draw_arc_blend(
1258 &mut self,
1259 brush: Brush,
1260 center: Point,
1261 radius: f32,
1262 start_angle: f32,
1263 sweep_angle: f32,
1264 stroke: Stroke,
1265 blend_mode: BlendMode,
1266 ) {
1267 if !stroke.is_visible() {
1268 return;
1269 }
1270 self.push_arc(
1271 brush,
1272 center,
1273 radius,
1274 start_angle,
1275 sweep_angle,
1276 Some(stroke),
1277 0.0,
1278 blend_mode,
1279 );
1280 }
1281
1282 fn draw_annular_sector(
1283 &mut self,
1284 brush: Brush,
1285 center: Point,
1286 inner_radius: f32,
1287 outer_radius: f32,
1288 start_angle: f32,
1289 sweep_angle: f32,
1290 ) {
1291 self.draw_annular_sector_blend(
1292 brush,
1293 center,
1294 inner_radius,
1295 outer_radius,
1296 start_angle,
1297 sweep_angle,
1298 BlendMode::SrcOver,
1299 );
1300 }
1301
1302 fn draw_annular_sector_blend(
1303 &mut self,
1304 brush: Brush,
1305 center: Point,
1306 inner_radius: f32,
1307 outer_radius: f32,
1308 start_angle: f32,
1309 sweep_angle: f32,
1310 blend_mode: BlendMode,
1311 ) {
1312 self.push_arc(
1313 brush,
1314 center,
1315 outer_radius,
1316 start_angle,
1317 sweep_angle,
1318 None,
1319 inner_radius,
1320 blend_mode,
1321 );
1322 }
1323
1324 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1325 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1326 }
1327
1328 fn draw_circle_blend(
1329 &mut self,
1330 brush: Brush,
1331 center: Point,
1332 radius: f32,
1333 blend_mode: BlendMode,
1334 ) {
1335 let radius = radius.max(0.0);
1336 let diameter = radius * 2.0;
1337 self.recording.push_round_rect(
1338 Rect {
1339 x: center.x - radius,
1340 y: center.y - radius,
1341 width: diameter,
1342 height: diameter,
1343 },
1344 &brush,
1345 CornerRadii::uniform(radius),
1346 None,
1347 blend_mode,
1348 );
1349 }
1350
1351 fn draw_image(&mut self, image: ImageBitmap) {
1352 self.draw_image_blend(image, BlendMode::SrcOver);
1353 }
1354
1355 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1356 self.push_blended_primitive(
1357 DrawPrimitive::Image {
1358 rect: Rect::from_size(self.size),
1359 image,
1360 alpha: 1.0,
1361 color_filter: None,
1362 sampling: ImageSampling::Nearest,
1363 src_rect: None,
1364 },
1365 blend_mode,
1366 );
1367 }
1368
1369 fn draw_image_at(
1370 &mut self,
1371 rect: Rect,
1372 image: ImageBitmap,
1373 alpha: f32,
1374 color_filter: Option<ColorFilter>,
1375 ) {
1376 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1377 }
1378
1379 fn draw_image_at_sampled(
1380 &mut self,
1381 rect: Rect,
1382 image: ImageBitmap,
1383 alpha: f32,
1384 color_filter: Option<ColorFilter>,
1385 sampling: ImageSampling,
1386 ) {
1387 self.push_blended_primitive(
1388 DrawPrimitive::Image {
1389 rect,
1390 image,
1391 alpha: alpha.clamp(0.0, 1.0),
1392 color_filter,
1393 sampling,
1394 src_rect: None,
1395 },
1396 BlendMode::SrcOver,
1397 );
1398 }
1399
1400 fn draw_image_at_blend(
1401 &mut self,
1402 rect: Rect,
1403 image: ImageBitmap,
1404 alpha: f32,
1405 color_filter: Option<ColorFilter>,
1406 blend_mode: BlendMode,
1407 ) {
1408 self.push_blended_primitive(
1409 DrawPrimitive::Image {
1410 rect,
1411 image,
1412 alpha: alpha.clamp(0.0, 1.0),
1413 color_filter,
1414 sampling: ImageSampling::Nearest,
1415 src_rect: None,
1416 },
1417 blend_mode,
1418 );
1419 }
1420
1421 fn draw_image_src(
1422 &mut self,
1423 image: ImageBitmap,
1424 src_rect: Rect,
1425 dst_rect: Rect,
1426 alpha: f32,
1427 color_filter: Option<ColorFilter>,
1428 ) {
1429 self.draw_image_src_blend(
1430 image,
1431 src_rect,
1432 dst_rect,
1433 alpha,
1434 color_filter,
1435 BlendMode::SrcOver,
1436 );
1437 }
1438
1439 fn draw_image_src_sampled(
1440 &mut self,
1441 image: ImageBitmap,
1442 src_rect: Rect,
1443 dst_rect: Rect,
1444 alpha: f32,
1445 color_filter: Option<ColorFilter>,
1446 sampling: ImageSampling,
1447 ) {
1448 self.push_blended_primitive(
1449 DrawPrimitive::Image {
1450 rect: dst_rect,
1451 image,
1452 alpha: alpha.clamp(0.0, 1.0),
1453 color_filter,
1454 sampling,
1455 src_rect: Some(src_rect),
1456 },
1457 BlendMode::SrcOver,
1458 );
1459 }
1460
1461 fn draw_image_src_blend(
1462 &mut self,
1463 image: ImageBitmap,
1464 src_rect: Rect,
1465 dst_rect: Rect,
1466 alpha: f32,
1467 color_filter: Option<ColorFilter>,
1468 blend_mode: BlendMode,
1469 ) {
1470 self.push_blended_primitive(
1471 DrawPrimitive::Image {
1472 rect: dst_rect,
1473 image,
1474 alpha: alpha.clamp(0.0, 1.0),
1475 color_filter,
1476 sampling: ImageSampling::Nearest,
1477 src_rect: Some(src_rect),
1478 },
1479 blend_mode,
1480 );
1481 }
1482
1483 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1484 const SUPERSAMPLE: f32 = 2.0;
1485 const MAX_MASK_PIXELS: f32 = 4096.0;
1486
1487 if path.is_empty() {
1488 return;
1489 }
1490 let bounds = path.bounds();
1491 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1492 return;
1493 }
1494
1495 let color = match &brush {
1496 Brush::Solid(color) => *color,
1497 Brush::LinearGradient { colors, .. }
1498 | Brush::RadialGradient { colors, .. }
1499 | Brush::SweepGradient { colors, .. } => match colors.first() {
1500 Some(color) => *color,
1501 None => return,
1502 },
1503 };
1504 if color.3 <= 0.0 {
1505 return;
1506 }
1507
1508 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1509 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1510 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1511 let mask_width = (rect_width * SUPERSAMPLE)
1512 .ceil()
1513 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1514 let mask_height = (rect_height * SUPERSAMPLE)
1515 .ceil()
1516 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1517
1518 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1519 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1520 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1521 let alpha = color.3.clamp(0.0, 1.0);
1522 let key = vector_path_mask_key(
1523 path,
1524 origin,
1525 (mask_width, mask_height),
1526 [red, green, blue],
1527 alpha,
1528 );
1529 let cached = vector_path_mask_cache_get(key);
1530 let image = match cached {
1531 Some(image) => image,
1532 None => {
1533 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1534 let mut pixels = Vec::with_capacity(mask.len() * 4);
1535 for coverage in mask {
1536 pixels.extend_from_slice(&[
1537 red,
1538 green,
1539 blue,
1540 (alpha * coverage as f32 + 0.5) as u8,
1541 ]);
1542 }
1543 let Ok(image) =
1544 ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
1545 else {
1546 return;
1547 };
1548 vector_path_mask_cache_put(key, image.clone());
1549 image
1550 }
1551 };
1552
1553 self.recording.push_other(DrawPrimitive::Image {
1554 rect: Rect {
1555 x: origin.x,
1556 y: origin.y,
1557 width: rect_width,
1558 height: rect_height,
1559 },
1560 image,
1561 alpha: 1.0,
1562 color_filter: None,
1563 sampling: ImageSampling::Linear,
1564 src_rect: None,
1565 });
1566 }
1567
1568 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1569 match &self.text_measurer {
1570 Some(measurer) => measurer.measure_text(text, style),
1571 None => estimate_text_measurement(text, style),
1572 }
1573 }
1574
1575 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1576 if text.is_empty() {
1577 return;
1578 }
1579 let Some(color) = solid_fill_color(&brush) else {
1580 return;
1581 };
1582 if color.3 <= 0.0 {
1583 return;
1584 }
1585 let measurement = self.measure_text(text, style);
1586 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1587 return;
1588 }
1589 let origin = align_text_block(rect, measurement, style);
1590 if !origin.x.is_finite() || !origin.y.is_finite() {
1591 return;
1592 }
1593 self.recording
1594 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1595 rect: Rect::from_origin_size(origin, measurement.size),
1596 text: shared_text_str(text),
1597 style: style.clone(),
1598 color,
1599 })));
1600 }
1601
1602 fn into_primitives(self) -> Vec<DrawPrimitive> {
1603 self.finish().into_primitives_with_markers()
1604 }
1605}
1606
1607fn solid_fill_color(brush: &Brush) -> Option<Color> {
1612 match brush {
1613 Brush::Solid(color) => Some(*color),
1614 Brush::LinearGradient { colors, .. }
1615 | Brush::RadialGradient { colors, .. }
1616 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1617 }
1618}
1619
1620#[cfg(test)]
1621mod tests {
1622 use super::*;
1623 use crate::{Color, FontStyle, FontWeight, ImageBitmap, RenderEffect};
1624
1625 #[test]
1626 fn recorded_iterators_preserve_shapes_content_and_shadows() {
1627 let shape = DrawPrimitive::Rect {
1628 rect: Rect::from_size(Size::new(12.0, 8.0)),
1629 brush: Brush::solid(Color::RED),
1630 stroke: None,
1631 };
1632 let shadow = DrawPrimitive::Shadow(ShadowPrimitive::Drop {
1633 shape: Box::new(shape.clone()),
1634 cutout: None,
1635 blur_radius: 4.0,
1636 blend_mode: BlendMode::SrcOver,
1637 });
1638 let mut scope = DrawScopeDefault::new(Size::new(24.0, 24.0));
1639 scope.push_recorded(None);
1640 scope.push_recorded(Some(shadow.clone()));
1641 scope.push_recorded([DrawPrimitive::Content, shape.clone()]);
1642 scope.push_recorded(std::iter::empty());
1643 let recording = scope.finish();
1644 assert_eq!(recording.content_markers(), 1);
1645 assert_eq!(
1646 recording.into_primitives_with_markers(),
1647 vec![shadow, DrawPrimitive::Content, shape]
1648 );
1649 }
1650
1651 #[test]
1652 fn compact_recording_materializes_in_recorded_order() {
1653 let size = Size::new(100.0, 100.0);
1654 let solid = Brush::solid(Color::WHITE);
1655 let gradient = Brush::vertical_gradient(vec![Color::RED, Color::BLUE], 0.0, 100.0);
1656 let center = Point::new(50.0, 50.0);
1657 let stroke = Stroke::new(4.0);
1658 let rect = Rect {
1659 x: 10.0,
1660 y: 20.0,
1661 width: 30.0,
1662 height: 40.0,
1663 };
1664 let batch = vec![
1665 DrawPrimitive::Content,
1666 DrawPrimitive::Rect {
1667 rect,
1668 brush: solid.clone(),
1669 stroke: None,
1670 },
1671 ];
1672
1673 let record = |scope: &mut DrawScopeDefault| {
1674 scope.draw_rect_at(rect, solid.clone());
1675 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 1.5, stroke);
1676 scope.draw_rect_at(rect, gradient.clone());
1677 scope.draw_circle(solid.clone(), center, 12.0);
1678 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 0.0, stroke);
1679 scope.draw_rect_at_blend(rect, solid.clone(), BlendMode::Plus);
1680 scope.draw_content();
1681 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
1682 scope.push_recorded(batch.clone());
1683 };
1684
1685 let mut compact = DrawScopeDefault::new(size);
1686 record(&mut compact);
1687 let finished = compact.finish();
1688
1689 let arc_via_ordinary = |brush: Brush, radius: f32, start: f32, sweep: f32| {
1690 let mut scope = DrawScopeDefault::new(size);
1691 scope.draw_arc(brush, center, radius, start, sweep, stroke);
1692 scope.into_primitives().remove(0)
1693 };
1694 let expected = vec![
1695 DrawPrimitive::Rect {
1696 rect,
1697 brush: solid.clone(),
1698 stroke: None,
1699 },
1700 arc_via_ordinary(solid.clone(), 30.0, 0.5, 1.5),
1701 DrawPrimitive::Rect {
1702 rect,
1703 brush: gradient.clone(),
1704 stroke: None,
1705 },
1706 DrawPrimitive::RoundRect {
1707 rect: Rect {
1708 x: center.x - 12.0,
1709 y: center.y - 12.0,
1710 width: 24.0,
1711 height: 24.0,
1712 },
1713 brush: solid.clone(),
1714 radii: CornerRadii::uniform(12.0),
1715 stroke: None,
1716 },
1717 DrawPrimitive::Blend {
1718 primitive: Box::new(DrawPrimitive::Rect {
1719 rect,
1720 brush: solid.clone(),
1721 stroke: None,
1722 }),
1723 blend_mode: BlendMode::Plus,
1724 },
1725 DrawPrimitive::Content,
1726 {
1727 let mut scope = DrawScopeDefault::new(size);
1728 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
1729 scope.into_primitives().remove(0)
1730 },
1731 DrawPrimitive::Content,
1732 DrawPrimitive::Rect {
1733 rect,
1734 brush: solid.clone(),
1735 stroke: None,
1736 },
1737 ];
1738 assert_eq!(finished.content_markers(), 2);
1739 assert_eq!(finished.into_primitives_with_markers(), expected);
1740 }
1741
1742 #[test]
1743 fn redrawing_the_same_text_shares_one_str_allocation() {
1744 let first = shared_text_str("BREAK THE RING");
1745 let second = shared_text_str("BREAK THE RING");
1746 assert!(Rc::ptr_eq(&first, &second));
1747 assert_eq!(&*second, "BREAK THE RING");
1748 }
1749
1750 #[test]
1751 fn different_text_gets_its_own_str() {
1752 let first = shared_text_str("340");
1753 let second = shared_text_str("350");
1754 assert!(!Rc::ptr_eq(&first, &second));
1755 assert_eq!(&*first, "340");
1756 assert_eq!(&*second, "350");
1757 }
1758
1759 #[test]
1760 fn the_text_pool_survives_overflowing_its_capacity() {
1761 for index in 0..600 {
1762 let text = format!("run-{index}");
1763 assert_eq!(&*shared_text_str(&text), text.as_str());
1764 }
1765 assert_eq!(&*shared_text_str("still correct"), "still correct");
1766 }
1767
1768 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
1769 match primitive {
1770 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
1771 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
1772 other => panic!("expected image primitive, got {other:?}"),
1773 }
1774 }
1775
1776 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
1777 match primitive {
1778 DrawPrimitive::Image { .. } => primitive,
1779 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
1780 other => panic!("expected image primitive, got {other:?}"),
1781 }
1782 }
1783
1784 #[test]
1785 fn draw_svg_path_emits_supersampled_image_over_path_bounds() {
1786 let mut scope = DrawScopeDefault::new(Size::new(32.0, 32.0));
1787 scope.draw_svg_path("M 4 4 H 20 V 20 H 4 Z", Brush::solid(Color::RED));
1788
1789 let primitives = scope.into_primitives();
1790 assert_eq!(primitives.len(), 1);
1791 let DrawPrimitive::Image { rect, image, .. } = &primitives[0] else {
1792 panic!("expected image primitive, got {:?}", primitives[0]);
1793 };
1794
1795 assert_eq!((rect.x, rect.y), (3.0, 3.0));
1796 assert_eq!((rect.width, rect.height), (18.0, 18.0));
1797 assert_eq!((image.width(), image.height()), (36, 36));
1798
1799 let pixels = image.pixels();
1800 let index = (18 * 36 + 18) * 4;
1801 assert_eq!(
1802 &pixels[index..index + 4],
1803 &[255, 0, 0, 255],
1804 "path interior must be opaque brush color"
1805 );
1806 assert_eq!(pixels[3], 0, "outside the path must stay transparent");
1807 }
1808
1809 #[test]
1810 fn draw_svg_path_ignores_invalid_data() {
1811 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1812 scope.draw_svg_path("definitely not a path", Brush::solid(Color::WHITE));
1813 assert!(scope.into_primitives().is_empty());
1814 }
1815
1816 #[test]
1817 fn draw_vector_path_applies_brush_alpha() {
1818 let path = crate::VectorPath::parse("M 0 0 H 8 V 8 H 0 Z").expect("valid path");
1819 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1820 scope.draw_vector_path(&path, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 0.5)));
1821
1822 let primitives = scope.into_primitives();
1823 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1824 panic!("expected image primitive");
1825 };
1826 let pixels = image.pixels();
1827 let width = image.width() as usize;
1828 let index = ((image.height() as usize / 2) * width + width / 2) * 4;
1829 assert_eq!(&pixels[index..index + 3], &[0, 0, 255]);
1830 let alpha = pixels[index + 3];
1831 assert!(
1832 (alpha as i32 - 128).abs() <= 2,
1833 "interior alpha must honor the brush alpha, got {alpha}"
1834 );
1835 }
1836
1837 #[test]
1838 fn the_same_path_and_color_reuse_one_raster() {
1839 let path = crate::VectorPath::parse("M 0 0 H 7 V 7 H 0 Z").expect("valid path");
1840 let raster_of = |brush: Brush| {
1841 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1842 scope.draw_vector_path(&path, brush);
1843 let primitives = scope.into_primitives();
1844 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1845 panic!("expected image primitive");
1846 };
1847 image.clone()
1848 };
1849
1850 let first = raster_of(Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1851 let second = raster_of(Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1852 assert_eq!(first.id(), second.id());
1853
1854 let other_color = raster_of(Brush::solid(Color::rgba(1.0, 0.0, 0.0, 1.0)));
1855 assert_ne!(first.id(), other_color.id());
1856
1857 let wider = crate::VectorPath::parse("M 0 0 H 9 V 7 H 0 Z").expect("valid path");
1858 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1859 scope.draw_vector_path(&wider, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1860 let primitives = scope.into_primitives();
1861 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1862 panic!("expected image primitive");
1863 };
1864 assert_ne!(first.id(), image.id());
1865 }
1866
1867 #[test]
1868 fn draw_content_inserts_content_marker() {
1869 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
1870 scope.draw_rect(Brush::solid(Color::WHITE));
1871 scope.draw_content();
1872 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
1873
1874 let primitives = scope.into_primitives();
1875 assert!(matches!(primitives[1], DrawPrimitive::Content));
1876 assert!(matches!(
1877 primitives[2],
1878 DrawPrimitive::Blend {
1879 blend_mode: BlendMode::DstOut,
1880 ..
1881 }
1882 ));
1883 }
1884
1885 #[test]
1886 fn a_text_block_is_placed_by_its_alignment_inside_the_rect() {
1887 let rect = Rect {
1888 x: 10.0,
1889 y: 20.0,
1890 width: 100.0,
1891 height: 40.0,
1892 };
1893 let measurement = TextMeasurement {
1894 size: Size::new(60.0, 16.0),
1895 line_height: 16.0,
1896 first_baseline: 12.0,
1897 line_count: 1,
1898 };
1899 let style = |align, vertical| {
1900 DrawTextStyle::default()
1901 .with_align(align)
1902 .with_vertical_align(vertical)
1903 };
1904
1905 let left = align_text_block(
1906 rect,
1907 measurement,
1908 &style(TextAlign::Left, TextVerticalAlign::Top),
1909 );
1910 assert_eq!(left, Point::new(10.0, 20.0));
1911
1912 let centered = align_text_block(
1913 rect,
1914 measurement,
1915 &style(TextAlign::Center, TextVerticalAlign::Center),
1916 );
1917 assert_eq!(centered, Point::new(10.0 + 20.0, 20.0 + 12.0));
1918
1919 let right = align_text_block(
1920 rect,
1921 measurement,
1922 &style(TextAlign::Right, TextVerticalAlign::Bottom),
1923 );
1924 assert_eq!(right, Point::new(50.0, 44.0));
1925
1926 let baseline = align_text_block(
1927 rect,
1928 measurement,
1929 &style(TextAlign::Left, TextVerticalAlign::Baseline),
1930 );
1931 assert_eq!(baseline, Point::new(10.0, 20.0 - 12.0));
1932 }
1933
1934 #[test]
1935 fn draw_rect_blend_wraps_non_default_modes() {
1936 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
1937 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
1938
1939 let primitives = scope.into_primitives();
1940 assert_eq!(primitives.len(), 1);
1941 match &primitives[0] {
1942 DrawPrimitive::Blend {
1943 primitive,
1944 blend_mode,
1945 } => {
1946 assert_eq!(*blend_mode, BlendMode::DstOut);
1947 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
1948 }
1949 other => panic!("expected blended primitive, got {other:?}"),
1950 }
1951 }
1952
1953 #[test]
1954 fn draw_circle_records_centered_round_rect() {
1955 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
1956 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
1957
1958 let primitives = scope.into_primitives();
1959 assert_eq!(primitives.len(), 1);
1960 match &primitives[0] {
1961 DrawPrimitive::RoundRect { rect, radii, .. } => {
1962 assert_eq!(
1963 *rect,
1964 Rect {
1965 x: 7.0,
1966 y: 11.0,
1967 width: 10.0,
1968 height: 10.0,
1969 }
1970 );
1971 assert_eq!(*radii, CornerRadii::uniform(5.0));
1972 }
1973 other => panic!("expected circular round rect, got {other:?}"),
1974 }
1975 }
1976
1977 #[test]
1978 fn draw_circle_blend_wraps_non_default_modes() {
1979 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
1980 scope.draw_circle_blend(
1981 Brush::solid(Color::RED),
1982 Point::new(5.0, 5.0),
1983 3.0,
1984 BlendMode::Plus,
1985 );
1986
1987 let primitives = scope.into_primitives();
1988 assert_eq!(primitives.len(), 1);
1989 match &primitives[0] {
1990 DrawPrimitive::Blend {
1991 primitive,
1992 blend_mode,
1993 } => {
1994 assert_eq!(*blend_mode, BlendMode::Plus);
1995 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
1996 }
1997 other => panic!("expected blended circle primitive, got {other:?}"),
1998 }
1999 }
2000
2001 #[test]
2002 fn rect_union_encloses_both_inputs() {
2003 let lhs = Rect {
2004 x: 10.0,
2005 y: 5.0,
2006 width: 8.0,
2007 height: 4.0,
2008 };
2009 let rhs = Rect {
2010 x: 4.0,
2011 y: 7.0,
2012 width: 10.0,
2013 height: 6.0,
2014 };
2015
2016 assert_eq!(
2017 lhs.union(rhs),
2018 Rect {
2019 x: 4.0,
2020 y: 5.0,
2021 width: 14.0,
2022 height: 8.0,
2023 }
2024 );
2025 }
2026
2027 #[test]
2028 fn draw_image_uses_scope_size_as_default_rect() {
2029 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
2030 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
2031 scope.draw_image(image.clone());
2032 let primitives = scope.into_primitives();
2033 assert_eq!(primitives.len(), 1);
2034 match unwrap_image(&primitives[0]) {
2035 DrawPrimitive::Image {
2036 rect,
2037 image: actual,
2038 alpha,
2039 color_filter,
2040 sampling,
2041 src_rect,
2042 } => {
2043 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
2044 assert_eq!(*actual, image);
2045 assert_eq!(*alpha, 1.0);
2046 assert!(color_filter.is_none());
2047 assert_eq!(*sampling, ImageSampling::Nearest);
2048 assert!(src_rect.is_none());
2049 }
2050 other => panic!("expected image primitive, got {other:?}"),
2051 }
2052 }
2053
2054 #[test]
2055 fn draw_image_src_stores_src_rect() {
2056 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2057 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2058 let src = Rect {
2059 x: 10.0,
2060 y: 20.0,
2061 width: 30.0,
2062 height: 40.0,
2063 };
2064 let dst = Rect {
2065 x: 0.0,
2066 y: 0.0,
2067 width: 60.0,
2068 height: 80.0,
2069 };
2070 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
2071 let primitives = scope.into_primitives();
2072 assert_eq!(primitives.len(), 1);
2073 match unwrap_image(&primitives[0]) {
2074 DrawPrimitive::Image {
2075 rect,
2076 image: actual,
2077 alpha,
2078 sampling,
2079 src_rect,
2080 ..
2081 } => {
2082 assert_eq!(*rect, dst);
2083 assert_eq!(*actual, image);
2084 assert!((alpha - 0.8).abs() < 1e-5);
2085 assert_eq!(*sampling, ImageSampling::Nearest);
2086 assert_eq!(*src_rect, Some(src));
2087 }
2088 other => panic!("expected image primitive, got {other:?}"),
2089 }
2090 }
2091
2092 #[test]
2093 fn draw_image_at_sampled_records_requested_sampling() {
2094 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2095 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
2096 let dst = Rect {
2097 x: 2.0,
2098 y: 3.0,
2099 width: 40.0,
2100 height: 30.0,
2101 };
2102
2103 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
2104
2105 let primitives = scope.into_primitives();
2106 assert_eq!(primitives.len(), 1);
2107 match unwrap_image(&primitives[0]) {
2108 DrawPrimitive::Image {
2109 rect,
2110 image: actual,
2111 alpha,
2112 sampling,
2113 src_rect,
2114 ..
2115 } => {
2116 assert_eq!(*rect, dst);
2117 assert_eq!(*actual, image);
2118 assert!((alpha - 0.7).abs() < 1e-5);
2119 assert_eq!(*sampling, ImageSampling::Linear);
2120 assert!(src_rect.is_none());
2121 }
2122 other => panic!("expected image primitive, got {other:?}"),
2123 }
2124 }
2125
2126 #[test]
2127 fn draw_image_src_sampled_records_requested_sampling() {
2128 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2129 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2130 let src = Rect {
2131 x: 4.0,
2132 y: 6.0,
2133 width: 16.0,
2134 height: 20.0,
2135 };
2136 let dst = Rect {
2137 x: 8.0,
2138 y: 10.0,
2139 width: 32.0,
2140 height: 40.0,
2141 };
2142
2143 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
2144
2145 let primitives = scope.into_primitives();
2146 assert_eq!(primitives.len(), 1);
2147 match unwrap_image(&primitives[0]) {
2148 DrawPrimitive::Image {
2149 rect,
2150 image: actual,
2151 alpha,
2152 sampling,
2153 src_rect,
2154 ..
2155 } => {
2156 assert_eq!(*rect, dst);
2157 assert_eq!(*actual, image);
2158 assert!((alpha - 0.5).abs() < 1e-5);
2159 assert_eq!(*sampling, ImageSampling::Linear);
2160 assert_eq!(*src_rect, Some(src));
2161 }
2162 other => panic!("expected image primitive, got {other:?}"),
2163 }
2164 }
2165
2166 #[test]
2167 fn draw_image_at_clamps_alpha() {
2168 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2169 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
2170 scope.draw_image_at(
2171 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
2172 image,
2173 3.0,
2174 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
2175 );
2176 assert_image_alpha(&scope.into_primitives()[0], 1.0);
2177 }
2178
2179 #[test]
2180 fn graphics_layer_clone_with_render_effect() {
2181 let layer = GraphicsLayer {
2182 render_effect: Some(RenderEffect::blur(10.0)),
2183 backdrop_effect: Some(RenderEffect::blur(6.0)),
2184 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
2185 alpha: 0.5,
2186 rotation_z: 12.0,
2187 shadow_elevation: 4.0,
2188 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
2189 clip: true,
2190 compositing_strategy: CompositingStrategy::Offscreen,
2191 blend_mode: BlendMode::SrcOver,
2192 ..Default::default()
2193 };
2194 let cloned = layer.clone();
2195 assert_eq!(cloned.alpha, 0.5);
2196 assert!(cloned.render_effect.is_some());
2197 assert!(cloned.backdrop_effect.is_some());
2198 assert_eq!(layer.color_filter, cloned.color_filter);
2199 assert_eq!(layer.render_effect, cloned.render_effect);
2200 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
2201 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
2202 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
2203 assert_eq!(
2204 cloned.shape,
2205 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
2206 );
2207 assert!(cloned.clip);
2208 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
2209 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
2210 }
2211
2212 #[test]
2213 fn graphics_layer_default_has_no_effect() {
2214 let layer = GraphicsLayer::default();
2215 assert!(layer.color_filter.is_none());
2216 assert!(layer.render_effect.is_none());
2217 assert!(layer.backdrop_effect.is_none());
2218 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
2219 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
2220 assert_eq!(layer.alpha, 1.0);
2221 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
2222 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
2223 assert_eq!(layer.shape, LayerShape::Rectangle);
2224 assert!(!layer.clip);
2225 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
2226 assert_eq!(layer.spot_shadow_color, Color::BLACK);
2227 }
2228
2229 #[test]
2230 fn transform_origin_construction() {
2231 let origin = TransformOrigin::new(0.25, 0.75);
2232 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
2233 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
2234 }
2235
2236 #[test]
2237 fn layer_shape_default_is_rectangle() {
2238 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
2239 }
2240
2241 use std::f32::consts::{FRAC_PI_2, PI};
2242
2243 use crate::{StrokeCap, StrokeJoin};
2244
2245 fn approx(a: f32, b: f32) -> bool {
2246 (a - b).abs() < 0.25
2247 }
2248
2249 fn scope(size: f32) -> DrawScopeDefault {
2250 DrawScopeDefault::new(Size::new(size, size))
2251 }
2252
2253 #[test]
2254 fn draw_rect_stroked_records_scope_rect_and_stroke() {
2255 let mut scope = scope(20.0);
2256 scope.draw_rect_stroked(
2257 Brush::solid(Color::RED),
2258 Stroke::new(3.0).with_join(StrokeJoin::Bevel),
2259 );
2260
2261 let primitives = scope.into_primitives();
2262 assert_eq!(primitives.len(), 1);
2263 match &primitives[0] {
2264 DrawPrimitive::Rect {
2265 rect,
2266 stroke: Some(stroke),
2267 ..
2268 } => {
2269 assert_eq!(*rect, Rect::from_size(Size::new(20.0, 20.0)));
2270 assert_eq!(stroke.width, 3.0);
2271 assert_eq!(stroke.join, StrokeJoin::Bevel);
2272 }
2273 other => panic!("expected stroked rect, got {other:?}"),
2274 }
2275 }
2276
2277 #[test]
2278 fn draw_rect_at_stroked_records_requested_rect() {
2279 let mut scope = scope(50.0);
2280 let rect = Rect {
2281 x: 4.0,
2282 y: 6.0,
2283 width: 12.0,
2284 height: 9.0,
2285 };
2286 scope.draw_rect_at_stroked(rect, Brush::solid(Color::BLUE), Stroke::new(2.0));
2287 match &scope.into_primitives()[0] {
2288 DrawPrimitive::Rect {
2289 rect: actual,
2290 stroke: Some(stroke),
2291 ..
2292 } => {
2293 assert_eq!(*actual, rect);
2294 assert_eq!(stroke.width, 2.0);
2295 }
2296 other => panic!("expected stroked rect, got {other:?}"),
2297 }
2298 }
2299
2300 #[test]
2301 fn draw_round_rect_stroked_keeps_radii_and_stroke() {
2302 let mut scope = scope(30.0);
2303 scope.draw_round_rect_stroked(
2304 Brush::solid(Color::GREEN),
2305 CornerRadii::uniform(5.0),
2306 Stroke::new(4.0).with_join(StrokeJoin::Round),
2307 );
2308 match &scope.into_primitives()[0] {
2309 DrawPrimitive::RoundRect {
2310 rect,
2311 radii,
2312 stroke: Some(stroke),
2313 ..
2314 } => {
2315 assert_eq!(*rect, Rect::from_size(Size::new(30.0, 30.0)));
2316 assert_eq!(*radii, CornerRadii::uniform(5.0));
2317 assert_eq!(stroke.width, 4.0);
2318 assert_eq!(stroke.join, StrokeJoin::Round);
2319 }
2320 other => panic!("expected stroked round rect, got {other:?}"),
2321 }
2322 }
2323
2324 #[test]
2325 fn draw_round_rect_at_stroked_records_requested_rect() {
2326 let mut scope = scope(60.0);
2327 let rect = Rect {
2328 x: 1.0,
2329 y: 2.0,
2330 width: 20.0,
2331 height: 10.0,
2332 };
2333 scope.draw_round_rect_at_stroked(
2334 rect,
2335 Brush::solid(Color::WHITE),
2336 CornerRadii::uniform(3.0),
2337 Stroke::new(1.5),
2338 );
2339 match &scope.into_primitives()[0] {
2340 DrawPrimitive::RoundRect {
2341 rect: actual,
2342 radii,
2343 stroke: Some(stroke),
2344 ..
2345 } => {
2346 assert_eq!(*actual, rect);
2347 assert_eq!(*radii, CornerRadii::uniform(3.0));
2348 assert_eq!(stroke.width, 1.5);
2349 }
2350 other => panic!("expected stroked round rect, got {other:?}"),
2351 }
2352 }
2353
2354 #[test]
2355 fn draw_circle_stroked_lowers_to_stroked_round_rect() {
2356 let mut scope = scope(40.0);
2357 scope.draw_circle_stroked(
2358 Brush::solid(Color::BLUE),
2359 Point::new(12.0, 16.0),
2360 5.0,
2361 Stroke::new(2.0),
2362 );
2363 match &scope.into_primitives()[0] {
2364 DrawPrimitive::RoundRect {
2365 rect,
2366 radii,
2367 stroke: Some(stroke),
2368 ..
2369 } => {
2370 assert_eq!(
2371 *rect,
2372 Rect {
2373 x: 7.0,
2374 y: 11.0,
2375 width: 10.0,
2376 height: 10.0,
2377 }
2378 );
2379 assert_eq!(*radii, CornerRadii::uniform(5.0));
2380 assert_eq!(stroke.width, 2.0);
2381 }
2382 other => panic!("expected stroked circular round rect, got {other:?}"),
2383 }
2384 }
2385
2386 #[test]
2387 fn draw_arc_records_arc_primitive_with_tight_bounds() {
2388 let mut scope = scope(200.0);
2389 scope.draw_arc(
2390 Brush::solid(Color::RED),
2391 Point::new(100.0, 100.0),
2392 50.0,
2393 0.0,
2394 FRAC_PI_2,
2395 Stroke::new(10.0),
2396 );
2397 let primitives = scope.into_primitives();
2398 assert_eq!(primitives.len(), 1);
2399 match &primitives[0] {
2400 DrawPrimitive::Arc {
2401 rect,
2402 center,
2403 radius,
2404 start_angle,
2405 sweep_angle,
2406 stroke: Some(stroke),
2407 inner_radius,
2408 ..
2409 } => {
2410 assert_eq!(*center, Point::new(100.0, 100.0));
2411 assert_eq!(*radius, 50.0);
2412 assert_eq!(*start_angle, 0.0);
2413 assert!(approx(*sweep_angle, FRAC_PI_2));
2414 assert_eq!(stroke.width, 10.0);
2415 assert_eq!(*inner_radius, 0.0);
2416 assert!(approx(rect.x, 100.0), "{rect:?}");
2417 assert!(approx(rect.y, 100.0), "{rect:?}");
2418 assert!(approx(rect.width, 55.0), "{rect:?}");
2419 assert!(approx(rect.height, 55.0), "{rect:?}");
2420 }
2421 other => panic!("expected arc primitive, got {other:?}"),
2422 }
2423 }
2424
2425 #[test]
2426 fn draw_arc_bounds_cover_a_quadrant_spanning_sweep() {
2427 let mut scope = scope(200.0);
2428 scope.draw_arc(
2429 Brush::solid(Color::RED),
2430 Point::new(100.0, 100.0),
2431 50.0,
2432 0.0,
2433 3.0 * FRAC_PI_2,
2434 Stroke::new(4.0),
2435 );
2436 let DrawPrimitive::Arc { rect, .. } = &scope.into_primitives()[0] else {
2437 panic!("expected arc primitive");
2438 };
2439 assert!(approx(rect.x, 48.0), "{rect:?}");
2440 assert!(approx(rect.y, 48.0), "{rect:?}");
2441 assert!(approx(rect.width, 104.0), "{rect:?}");
2442 assert!(approx(rect.height, 104.0), "{rect:?}");
2443 }
2444
2445 #[test]
2446 fn draw_annular_sector_records_inner_radius_and_no_stroke() {
2447 let mut scope = scope(200.0);
2448 scope.draw_annular_sector(
2449 Brush::solid(Color::WHITE),
2450 Point::new(100.0, 100.0),
2451 30.0,
2452 50.0,
2453 0.0,
2454 PI,
2455 );
2456 match &scope.into_primitives()[0] {
2457 DrawPrimitive::Arc {
2458 rect,
2459 center,
2460 radius,
2461 inner_radius,
2462 stroke,
2463 sweep_angle,
2464 ..
2465 } => {
2466 assert!(stroke.is_none(), "annular sectors are filled, not stroked");
2467 assert_eq!(*center, Point::new(100.0, 100.0));
2468 assert_eq!(*radius, 50.0);
2469 assert_eq!(*inner_radius, 30.0);
2470 assert!(approx(*sweep_angle, PI));
2471 assert!(approx(rect.x, 50.0), "{rect:?}");
2472 assert!(approx(rect.y, 100.0), "{rect:?}");
2473 assert!(approx(rect.width, 100.0), "{rect:?}");
2474 assert!(approx(rect.height, 50.0), "{rect:?}");
2475 }
2476 other => panic!("expected arc primitive, got {other:?}"),
2477 }
2478 }
2479
2480 #[test]
2481 fn draw_arc_blend_wraps_non_default_modes() {
2482 let mut scope = scope(100.0);
2483 scope.draw_arc_blend(
2484 Brush::solid(Color::RED),
2485 Point::new(50.0, 50.0),
2486 20.0,
2487 0.0,
2488 1.0,
2489 Stroke::new(2.0),
2490 BlendMode::DstOut,
2491 );
2492 match &scope.into_primitives()[0] {
2493 DrawPrimitive::Blend {
2494 primitive,
2495 blend_mode,
2496 } => {
2497 assert_eq!(*blend_mode, BlendMode::DstOut);
2498 assert!(matches!(**primitive, DrawPrimitive::Arc { .. }));
2499 }
2500 other => panic!("expected blended arc, got {other:?}"),
2501 }
2502 }
2503
2504 #[test]
2505 fn draw_annular_sector_blend_wraps_non_default_modes() {
2506 let mut scope = scope(100.0);
2507 scope.draw_annular_sector_blend(
2508 Brush::solid(Color::RED),
2509 Point::new(50.0, 50.0),
2510 5.0,
2511 20.0,
2512 0.0,
2513 1.0,
2514 BlendMode::Plus,
2515 );
2516 assert!(matches!(
2517 &scope.into_primitives()[0],
2518 DrawPrimitive::Blend {
2519 blend_mode: BlendMode::Plus,
2520 ..
2521 }
2522 ));
2523 }
2524
2525 #[test]
2526 fn stroked_blend_variants_wrap_non_default_modes() {
2527 let mut scope = scope(20.0);
2528 scope.draw_rect_stroked_blend(
2529 Brush::solid(Color::RED),
2530 Stroke::new(2.0),
2531 BlendMode::DstOut,
2532 );
2533 scope.draw_round_rect_stroked_blend(
2534 Brush::solid(Color::RED),
2535 CornerRadii::uniform(2.0),
2536 Stroke::new(2.0),
2537 BlendMode::DstOut,
2538 );
2539 scope.draw_circle_stroked_blend(
2540 Brush::solid(Color::RED),
2541 Point::new(10.0, 10.0),
2542 5.0,
2543 Stroke::new(2.0),
2544 BlendMode::DstOut,
2545 );
2546 let primitives = scope.into_primitives();
2547 assert_eq!(primitives.len(), 3);
2548 for primitive in &primitives {
2549 assert!(
2550 matches!(
2551 primitive,
2552 DrawPrimitive::Blend {
2553 blend_mode: BlendMode::DstOut,
2554 ..
2555 }
2556 ),
2557 "expected blended primitive, got {primitive:?}"
2558 );
2559 }
2560 }
2561
2562 #[test]
2563 fn negative_sweeps_and_overlong_sweeps_produce_finite_bounds() {
2564 let mut scope = scope(200.0);
2565 scope.draw_arc(
2566 Brush::solid(Color::RED),
2567 Point::new(100.0, 100.0),
2568 40.0,
2569 FRAC_PI_2,
2570 -FRAC_PI_2,
2571 Stroke::new(4.0),
2572 );
2573 scope.draw_arc(
2574 Brush::solid(Color::RED),
2575 Point::new(100.0, 100.0),
2576 40.0,
2577 0.3,
2578 crate::stroke::TAU * 4.0,
2579 Stroke::new(4.0),
2580 );
2581 let primitives = scope.into_primitives();
2582 assert_eq!(primitives.len(), 2);
2583
2584 let DrawPrimitive::Arc { rect: negative, .. } = &primitives[0] else {
2585 panic!("expected arc");
2586 };
2587 assert!(approx(negative.x, 100.0), "{negative:?}");
2588 assert!(approx(negative.y, 100.0), "{negative:?}");
2589 assert!(approx(negative.width, 42.0), "{negative:?}");
2590
2591 let DrawPrimitive::Arc { rect: full, .. } = &primitives[1] else {
2592 panic!("expected arc");
2593 };
2594 assert!(approx(full.x, 58.0), "{full:?}");
2595 assert!(approx(full.width, 84.0), "{full:?}");
2596 assert!(approx(full.height, 84.0), "{full:?}");
2597 }
2598
2599 #[test]
2600 fn degenerate_stroke_and_arc_inputs_emit_nothing_and_never_panic() {
2601 let mut scope = scope(50.0);
2602 let brush = Brush::solid(Color::RED);
2603 let center = Point::new(25.0, 25.0);
2604
2605 scope.draw_rect_stroked(brush.clone(), Stroke::new(0.0));
2606 scope.draw_rect_stroked(brush.clone(), Stroke::new(-4.0));
2607 scope.draw_rect_stroked(brush.clone(), Stroke::new(f32::NAN));
2608 scope.draw_round_rect_stroked(brush.clone(), CornerRadii::uniform(2.0), Stroke::new(0.0));
2609 scope.draw_circle_stroked(brush.clone(), center, 10.0, Stroke::new(0.0));
2610 scope.draw_circle_stroked(brush.clone(), center, f32::NAN, Stroke::new(2.0));
2611 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 0.0, Stroke::new(2.0));
2612 scope.draw_arc(brush.clone(), center, 10.0, 0.0, f32::NAN, Stroke::new(2.0));
2613 scope.draw_arc(
2614 brush.clone(),
2615 center,
2616 f32::INFINITY,
2617 0.0,
2618 1.0,
2619 Stroke::new(2.0),
2620 );
2621 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 1.0, Stroke::new(0.0));
2622 scope.draw_arc(brush.clone(), center, 0.0, 0.0, 1.0, Stroke::new(0.0));
2623 scope.draw_annular_sector(brush.clone(), center, 10.0, 10.0, 0.0, 1.0);
2624 scope.draw_annular_sector(brush.clone(), center, 20.0, 10.0, 0.0, 1.0);
2625 scope.draw_annular_sector(brush.clone(), center, 0.0, 0.0, 0.0, 1.0);
2626 scope.draw_annular_sector(brush.clone(), center, 0.0, 10.0, 0.0, 0.0);
2627 scope.draw_annular_sector(brush, center, f32::NAN, 10.0, 0.0, 1.0);
2628
2629 assert!(
2630 scope.into_primitives().is_empty(),
2631 "degenerate stroke/arc requests must not reach the renderer"
2632 );
2633 }
2634
2635 #[test]
2636 fn zero_radius_arc_with_positive_width_stays_finite() {
2637 let mut scope = scope(50.0);
2638 scope.draw_arc(
2639 Brush::solid(Color::RED),
2640 Point::new(25.0, 25.0),
2641 0.0,
2642 0.0,
2643 FRAC_PI_2,
2644 Stroke::new(6.0).with_cap(StrokeCap::Round),
2645 );
2646 let primitives = scope.into_primitives();
2647 assert_eq!(primitives.len(), 1);
2648 let DrawPrimitive::Arc { rect, .. } = &primitives[0] else {
2649 panic!("expected arc");
2650 };
2651 for value in [rect.x, rect.y, rect.width, rect.height] {
2652 assert!(value.is_finite(), "{rect:?}");
2653 }
2654 assert!(rect.width > 0.0 && rect.height > 0.0, "{rect:?}");
2655 }
2656
2657 struct FixedAdvanceTextMeasurer {
2658 advance: f32,
2659 line_height: f32,
2660 calls: std::cell::Cell<usize>,
2661 }
2662
2663 impl FixedAdvanceTextMeasurer {
2664 fn shared(advance: f32, line_height: f32) -> Rc<Self> {
2665 Rc::new(Self {
2666 advance,
2667 line_height,
2668 calls: std::cell::Cell::new(0),
2669 })
2670 }
2671 }
2672
2673 impl DrawTextMeasurer for FixedAdvanceTextMeasurer {
2674 fn measure_text(&self, text: &str, _style: &DrawTextStyle) -> TextMeasurement {
2675 self.calls.set(self.calls.get() + 1);
2676 let lines: Vec<&str> = text.split('\n').collect();
2677 let width = lines
2678 .iter()
2679 .map(|line| line.chars().count() as f32 * self.advance)
2680 .fold(0.0_f32, f32::max);
2681 TextMeasurement {
2682 size: Size::new(width, lines.len() as f32 * self.line_height),
2683 line_height: self.line_height,
2684 first_baseline: self.line_height * 0.75,
2685 line_count: lines.len(),
2686 }
2687 }
2688 }
2689
2690 fn text_scope(size: Size) -> (DrawScopeDefault, Rc<FixedAdvanceTextMeasurer>) {
2691 let measurer = FixedAdvanceTextMeasurer::shared(10.0, 20.0);
2692 (
2693 DrawScopeDefault::with_text_measurer(size, measurer.clone()),
2694 measurer,
2695 )
2696 }
2697
2698 fn unwrap_text(primitive: &DrawPrimitive) -> &TextPrimitive {
2699 match primitive {
2700 DrawPrimitive::Text(text) => text,
2701 other => panic!("expected text primitive, got {other:?}"),
2702 }
2703 }
2704
2705 #[test]
2706 fn drawn_text_occupies_exactly_the_box_measure_text_reported() {
2707 let (mut scope, _) = text_scope(Size::new(200.0, 100.0));
2708 let style = DrawTextStyle::new(16.0);
2709 let measured = scope.measure_text("ABCD", &style);
2710
2711 scope.draw_text_from(
2712 Point::new(7.0, 11.0),
2713 Brush::solid(Color::WHITE),
2714 "ABCD",
2715 &style,
2716 );
2717
2718 let primitives = scope.into_primitives();
2719 assert_eq!(primitives.len(), 1);
2720 let text = unwrap_text(&primitives[0]);
2721 assert_eq!(
2722 text.rect,
2723 Rect {
2724 x: 7.0,
2725 y: 11.0,
2726 width: measured.size.width,
2727 height: measured.size.height,
2728 },
2729 "the drawn block must be the measured block, or callers cannot center text"
2730 );
2731 assert_eq!(&*text.text, "ABCD");
2732 assert_eq!(text.color, Color::WHITE);
2733 }
2734
2735 #[test]
2736 fn text_alignment_positions_the_measured_block_inside_the_box() {
2737 let box_rect = Rect {
2738 x: 100.0,
2739 y: 50.0,
2740 width: 200.0,
2741 height: 80.0,
2742 };
2743 let cases = [
2744 (TextAlign::Left, TextVerticalAlign::Top, 100.0, 50.0),
2745 (TextAlign::Center, TextVerticalAlign::Center, 190.0, 80.0),
2746 (TextAlign::Right, TextVerticalAlign::Bottom, 280.0, 110.0),
2747 ];
2748 for (align, vertical_align, expected_x, expected_y) in cases {
2749 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2750 let style = DrawTextStyle::new(16.0)
2751 .with_align(align)
2752 .with_vertical_align(vertical_align);
2753 scope.draw_text_at(box_rect, Brush::solid(Color::WHITE), "AB", &style);
2754 let primitives = scope.into_primitives();
2755 let text = unwrap_text(&primitives[0]);
2756 assert!(
2757 approx(text.rect.x, expected_x) && approx(text.rect.y, expected_y),
2758 "{align:?}/{vertical_align:?} placed the block at {:?}",
2759 text.rect
2760 );
2761 assert!(approx(text.rect.width, 20.0) && approx(text.rect.height, 20.0));
2762 }
2763 }
2764
2765 #[test]
2766 fn baseline_aligned_text_hangs_above_the_box_edge() {
2767 let (mut scope, _) = text_scope(Size::new(200.0, 200.0));
2768 let style = DrawTextStyle::new(16.0).with_vertical_align(TextVerticalAlign::Baseline);
2769 let measured = scope.measure_text("Ag", &style);
2770 scope.draw_text_at(
2771 Rect {
2772 x: 0.0,
2773 y: 100.0,
2774 width: 200.0,
2775 height: 0.0,
2776 },
2777 Brush::solid(Color::WHITE),
2778 "Ag",
2779 &style,
2780 );
2781 let primitives = scope.into_primitives();
2782 let text = unwrap_text(&primitives[0]);
2783 assert!(
2784 approx(text.rect.y, 100.0 - measured.first_baseline),
2785 "{:?}",
2786 text.rect
2787 );
2788 }
2789
2790 #[test]
2791 fn draw_text_fills_the_whole_scope_rect() {
2792 let (mut scope, _) = text_scope(Size::new(120.0, 60.0));
2793 let style = DrawTextStyle::new(16.0)
2794 .with_align(TextAlign::Right)
2795 .with_vertical_align(TextVerticalAlign::Bottom);
2796 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
2797 let primitives = scope.into_primitives();
2798 let text = unwrap_text(&primitives[0]);
2799 assert!(
2800 approx(text.rect.x, 100.0) && approx(text.rect.y, 40.0),
2801 "{:?}",
2802 text.rect
2803 );
2804 }
2805
2806 #[test]
2807 fn draw_text_from_ignores_alignment_and_anchors_the_top_left() {
2808 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2809 let style = DrawTextStyle::new(16.0)
2810 .with_align(TextAlign::Center)
2811 .with_vertical_align(TextVerticalAlign::Bottom);
2812 scope.draw_text_from(
2813 Point::new(30.0, 40.0),
2814 Brush::solid(Color::WHITE),
2815 "AB",
2816 &style,
2817 );
2818 let primitives = scope.into_primitives();
2819 let text = unwrap_text(&primitives[0]);
2820 assert!(
2821 approx(text.rect.x, 30.0) && approx(text.rect.y, 40.0),
2822 "{:?}",
2823 text.rect
2824 );
2825 }
2826
2827 #[test]
2828 fn multiline_text_measures_the_widest_line_and_stacks_the_lines() {
2829 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2830 let style = DrawTextStyle::new(16.0);
2831 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "AB\nABCDE", &style);
2832 let primitives = scope.into_primitives();
2833 let text = unwrap_text(&primitives[0]);
2834 assert!(approx(text.rect.width, 50.0), "{:?}", text.rect);
2835 assert!(approx(text.rect.height, 40.0), "{:?}", text.rect);
2836 }
2837
2838 #[test]
2839 fn empty_text_draws_nothing_and_never_measures() {
2840 let (mut scope, measurer) = text_scope(Size::new(100.0, 100.0));
2841 scope.draw_text(Brush::solid(Color::WHITE), "", &DrawTextStyle::new(16.0));
2842 scope.draw_text_at(
2843 Rect::from_size(Size::new(10.0, 10.0)),
2844 Brush::solid(Color::WHITE),
2845 "",
2846 &DrawTextStyle::new(16.0),
2847 );
2848 scope.draw_text_from(
2849 Point::ZERO,
2850 Brush::solid(Color::WHITE),
2851 "",
2852 &DrawTextStyle::new(16.0),
2853 );
2854 assert!(scope.into_primitives().is_empty());
2855 assert_eq!(
2856 measurer.calls.get(),
2857 0,
2858 "an empty string must not cost a measurement"
2859 );
2860 }
2861
2862 #[test]
2863 fn invisible_text_draws_nothing() {
2864 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2865 let style = DrawTextStyle::new(16.0);
2866 scope.draw_text(Brush::solid(Color(1.0, 1.0, 1.0, 0.0)), "AB", &style);
2867 scope.draw_text(
2868 Brush::LinearGradient {
2869 colors: Vec::new(),
2870 stops: None,
2871 start: Point::ZERO,
2872 end: Point::new(1.0, 1.0),
2873 tile_mode: crate::render_effect::TileMode::Clamp,
2874 },
2875 "AB",
2876 &style,
2877 );
2878 assert!(scope.into_primitives().is_empty());
2879 }
2880
2881 #[test]
2882 fn gradient_text_brushes_fall_back_to_their_first_stop() {
2883 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2884 scope.draw_text(
2885 Brush::linear_gradient(vec![Color::RED, Color::BLUE]),
2886 "AB",
2887 &DrawTextStyle::new(16.0),
2888 );
2889 let primitives = scope.into_primitives();
2890 assert_eq!(unwrap_text(&primitives[0]).color, Color::RED);
2891 }
2892
2893 #[test]
2894 fn a_scope_without_a_measurer_falls_back_to_the_font_free_estimate() {
2895 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2896 let style = DrawTextStyle::new(16.0);
2897 assert_eq!(
2898 scope.measure_text("ABC", &style),
2899 crate::estimate_text_measurement("ABC", &style)
2900 );
2901 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "ABC", &style);
2902 let primitives = scope.into_primitives();
2903 let text = unwrap_text(&primitives[0]);
2904 assert!(text.rect.width > 0.0 && text.rect.height > 0.0);
2905 }
2906
2907 #[test]
2908 fn degenerate_text_geometry_emits_nothing_and_never_panics() {
2909 struct DegenerateTextMeasurer;
2910 impl DrawTextMeasurer for DegenerateTextMeasurer {
2911 fn measure_text(&self, _text: &str, _style: &DrawTextStyle) -> TextMeasurement {
2912 TextMeasurement {
2913 size: Size::new(f32::NAN, 0.0),
2914 line_height: f32::NAN,
2915 first_baseline: f32::NAN,
2916 line_count: 1,
2917 }
2918 }
2919 }
2920
2921 let mut scope = DrawScopeDefault::with_text_measurer(
2922 Size::new(50.0, 50.0),
2923 Rc::new(DegenerateTextMeasurer),
2924 );
2925 scope.draw_text(Brush::solid(Color::WHITE), "AB", &DrawTextStyle::new(16.0));
2926 scope.draw_text_at(
2927 Rect {
2928 x: f32::NAN,
2929 y: 0.0,
2930 width: 10.0,
2931 height: 10.0,
2932 },
2933 Brush::solid(Color::WHITE),
2934 "AB",
2935 &DrawTextStyle::new(16.0),
2936 );
2937 assert!(
2938 scope.into_primitives().is_empty(),
2939 "unmeasurable text must not reach the renderer"
2940 );
2941 }
2942
2943 #[test]
2944 fn text_style_survives_lowering_into_the_primitive() {
2945 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2946 let style = DrawTextStyle::new(21.0)
2947 .with_font_family("Fira Sans")
2948 .with_weight(FontWeight::BOLD)
2949 .with_style(FontStyle::Italic)
2950 .with_letter_spacing(2.0)
2951 .with_line_height(26.0);
2952 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
2953 let primitives = scope.into_primitives();
2954 assert_eq!(unwrap_text(&primitives[0]).style, style);
2955 }
2956
2957 #[test]
2958 fn a_layers_composite_alpha_is_a_truncated_byte() {
2959 for byte in 0..=255u32 {
2960 let exact = byte as f32 / 255.0;
2961 assert!(
2962 (GraphicsLayer::composite_alpha_8bit(exact) - exact).abs() < 1e-6,
2963 "byte {byte} moved"
2964 );
2965 if byte < 255 {
2966 let nearly_next = (byte as f32 + 0.999) / 255.0;
2967 assert!(
2968 (GraphicsLayer::composite_alpha_8bit(nearly_next) - exact).abs() < 1e-6,
2969 "byte {byte} + 0.999 did not truncate"
2970 );
2971 }
2972 }
2973 assert_eq!(GraphicsLayer::composite_alpha_8bit(1.0), 1.0);
2974 assert_eq!(GraphicsLayer::composite_alpha_8bit(0.0), 0.0);
2975 assert_eq!(GraphicsLayer::composite_alpha_8bit(-3.0), 0.0);
2976 assert_eq!(GraphicsLayer::composite_alpha_8bit(7.0), 1.0);
2977 }
2978}