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: Vec<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 compact_recording_materializes_in_recorded_order() {
1627 let size = Size::new(100.0, 100.0);
1628 let solid = Brush::solid(Color::WHITE);
1629 let gradient = Brush::vertical_gradient(vec![Color::RED, Color::BLUE], 0.0, 100.0);
1630 let center = Point::new(50.0, 50.0);
1631 let stroke = Stroke::new(4.0);
1632 let rect = Rect {
1633 x: 10.0,
1634 y: 20.0,
1635 width: 30.0,
1636 height: 40.0,
1637 };
1638 let batch = vec![
1639 DrawPrimitive::Content,
1640 DrawPrimitive::Rect {
1641 rect,
1642 brush: solid.clone(),
1643 stroke: None,
1644 },
1645 ];
1646
1647 let record = |scope: &mut DrawScopeDefault| {
1648 scope.draw_rect_at(rect, solid.clone());
1649 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 1.5, stroke);
1650 scope.draw_rect_at(rect, gradient.clone());
1651 scope.draw_circle(solid.clone(), center, 12.0);
1652 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 0.0, stroke);
1653 scope.draw_rect_at_blend(rect, solid.clone(), BlendMode::Plus);
1654 scope.draw_content();
1655 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
1656 scope.push_recorded(batch.clone());
1657 };
1658
1659 let mut compact = DrawScopeDefault::new(size);
1660 record(&mut compact);
1661 let finished = compact.finish();
1662
1663 let arc_via_ordinary = |brush: Brush, radius: f32, start: f32, sweep: f32| {
1664 let mut scope = DrawScopeDefault::new(size);
1665 scope.draw_arc(brush, center, radius, start, sweep, stroke);
1666 scope.into_primitives().remove(0)
1667 };
1668 let expected = vec![
1669 DrawPrimitive::Rect {
1670 rect,
1671 brush: solid.clone(),
1672 stroke: None,
1673 },
1674 arc_via_ordinary(solid.clone(), 30.0, 0.5, 1.5),
1675 DrawPrimitive::Rect {
1676 rect,
1677 brush: gradient.clone(),
1678 stroke: None,
1679 },
1680 DrawPrimitive::RoundRect {
1681 rect: Rect {
1682 x: center.x - 12.0,
1683 y: center.y - 12.0,
1684 width: 24.0,
1685 height: 24.0,
1686 },
1687 brush: solid.clone(),
1688 radii: CornerRadii::uniform(12.0),
1689 stroke: None,
1690 },
1691 DrawPrimitive::Blend {
1692 primitive: Box::new(DrawPrimitive::Rect {
1693 rect,
1694 brush: solid.clone(),
1695 stroke: None,
1696 }),
1697 blend_mode: BlendMode::Plus,
1698 },
1699 DrawPrimitive::Content,
1700 {
1701 let mut scope = DrawScopeDefault::new(size);
1702 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
1703 scope.into_primitives().remove(0)
1704 },
1705 DrawPrimitive::Content,
1706 DrawPrimitive::Rect {
1707 rect,
1708 brush: solid.clone(),
1709 stroke: None,
1710 },
1711 ];
1712 assert_eq!(finished.content_markers(), 2);
1713 assert_eq!(finished.into_primitives_with_markers(), expected);
1714 }
1715
1716 #[test]
1717 fn redrawing_the_same_text_shares_one_str_allocation() {
1718 let first = shared_text_str("BREAK THE RING");
1719 let second = shared_text_str("BREAK THE RING");
1720 assert!(Rc::ptr_eq(&first, &second));
1721 assert_eq!(&*second, "BREAK THE RING");
1722 }
1723
1724 #[test]
1725 fn different_text_gets_its_own_str() {
1726 let first = shared_text_str("340");
1727 let second = shared_text_str("350");
1728 assert!(!Rc::ptr_eq(&first, &second));
1729 assert_eq!(&*first, "340");
1730 assert_eq!(&*second, "350");
1731 }
1732
1733 #[test]
1734 fn the_text_pool_survives_overflowing_its_capacity() {
1735 for index in 0..600 {
1736 let text = format!("run-{index}");
1737 assert_eq!(&*shared_text_str(&text), text.as_str());
1738 }
1739 assert_eq!(&*shared_text_str("still correct"), "still correct");
1740 }
1741
1742 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
1743 match primitive {
1744 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
1745 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
1746 other => panic!("expected image primitive, got {other:?}"),
1747 }
1748 }
1749
1750 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
1751 match primitive {
1752 DrawPrimitive::Image { .. } => primitive,
1753 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
1754 other => panic!("expected image primitive, got {other:?}"),
1755 }
1756 }
1757
1758 #[test]
1759 fn draw_svg_path_emits_supersampled_image_over_path_bounds() {
1760 let mut scope = DrawScopeDefault::new(Size::new(32.0, 32.0));
1761 scope.draw_svg_path("M 4 4 H 20 V 20 H 4 Z", Brush::solid(Color::RED));
1762
1763 let primitives = scope.into_primitives();
1764 assert_eq!(primitives.len(), 1);
1765 let DrawPrimitive::Image { rect, image, .. } = &primitives[0] else {
1766 panic!("expected image primitive, got {:?}", primitives[0]);
1767 };
1768
1769 assert_eq!((rect.x, rect.y), (3.0, 3.0));
1770 assert_eq!((rect.width, rect.height), (18.0, 18.0));
1771 assert_eq!((image.width(), image.height()), (36, 36));
1772
1773 let pixels = image.pixels();
1774 let index = (18 * 36 + 18) * 4;
1775 assert_eq!(
1776 &pixels[index..index + 4],
1777 &[255, 0, 0, 255],
1778 "path interior must be opaque brush color"
1779 );
1780 assert_eq!(pixels[3], 0, "outside the path must stay transparent");
1781 }
1782
1783 #[test]
1784 fn draw_svg_path_ignores_invalid_data() {
1785 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1786 scope.draw_svg_path("definitely not a path", Brush::solid(Color::WHITE));
1787 assert!(scope.into_primitives().is_empty());
1788 }
1789
1790 #[test]
1791 fn draw_vector_path_applies_brush_alpha() {
1792 let path = crate::VectorPath::parse("M 0 0 H 8 V 8 H 0 Z").expect("valid path");
1793 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1794 scope.draw_vector_path(&path, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 0.5)));
1795
1796 let primitives = scope.into_primitives();
1797 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1798 panic!("expected image primitive");
1799 };
1800 let pixels = image.pixels();
1801 let width = image.width() as usize;
1802 let index = ((image.height() as usize / 2) * width + width / 2) * 4;
1803 assert_eq!(&pixels[index..index + 3], &[0, 0, 255]);
1804 let alpha = pixels[index + 3];
1805 assert!(
1806 (alpha as i32 - 128).abs() <= 2,
1807 "interior alpha must honor the brush alpha, got {alpha}"
1808 );
1809 }
1810
1811 #[test]
1812 fn the_same_path_and_color_reuse_one_raster() {
1813 let path = crate::VectorPath::parse("M 0 0 H 7 V 7 H 0 Z").expect("valid path");
1814 let raster_of = |brush: Brush| {
1815 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1816 scope.draw_vector_path(&path, brush);
1817 let primitives = scope.into_primitives();
1818 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1819 panic!("expected image primitive");
1820 };
1821 image.clone()
1822 };
1823
1824 let first = raster_of(Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1825 let second = raster_of(Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1826 assert_eq!(first.id(), second.id());
1827
1828 let other_color = raster_of(Brush::solid(Color::rgba(1.0, 0.0, 0.0, 1.0)));
1829 assert_ne!(first.id(), other_color.id());
1830
1831 let wider = crate::VectorPath::parse("M 0 0 H 9 V 7 H 0 Z").expect("valid path");
1832 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
1833 scope.draw_vector_path(&wider, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 1.0)));
1834 let primitives = scope.into_primitives();
1835 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
1836 panic!("expected image primitive");
1837 };
1838 assert_ne!(first.id(), image.id());
1839 }
1840
1841 #[test]
1842 fn draw_content_inserts_content_marker() {
1843 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
1844 scope.draw_rect(Brush::solid(Color::WHITE));
1845 scope.draw_content();
1846 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
1847
1848 let primitives = scope.into_primitives();
1849 assert!(matches!(primitives[1], DrawPrimitive::Content));
1850 assert!(matches!(
1851 primitives[2],
1852 DrawPrimitive::Blend {
1853 blend_mode: BlendMode::DstOut,
1854 ..
1855 }
1856 ));
1857 }
1858
1859 #[test]
1860 fn a_text_block_is_placed_by_its_alignment_inside_the_rect() {
1861 let rect = Rect {
1862 x: 10.0,
1863 y: 20.0,
1864 width: 100.0,
1865 height: 40.0,
1866 };
1867 let measurement = TextMeasurement {
1868 size: Size::new(60.0, 16.0),
1869 line_height: 16.0,
1870 first_baseline: 12.0,
1871 line_count: 1,
1872 };
1873 let style = |align, vertical| {
1874 DrawTextStyle::default()
1875 .with_align(align)
1876 .with_vertical_align(vertical)
1877 };
1878
1879 let left = align_text_block(
1880 rect,
1881 measurement,
1882 &style(TextAlign::Left, TextVerticalAlign::Top),
1883 );
1884 assert_eq!(left, Point::new(10.0, 20.0));
1885
1886 let centered = align_text_block(
1887 rect,
1888 measurement,
1889 &style(TextAlign::Center, TextVerticalAlign::Center),
1890 );
1891 assert_eq!(centered, Point::new(10.0 + 20.0, 20.0 + 12.0));
1892
1893 let right = align_text_block(
1894 rect,
1895 measurement,
1896 &style(TextAlign::Right, TextVerticalAlign::Bottom),
1897 );
1898 assert_eq!(right, Point::new(50.0, 44.0));
1899
1900 let baseline = align_text_block(
1901 rect,
1902 measurement,
1903 &style(TextAlign::Left, TextVerticalAlign::Baseline),
1904 );
1905 assert_eq!(baseline, Point::new(10.0, 20.0 - 12.0));
1906 }
1907
1908 #[test]
1909 fn draw_rect_blend_wraps_non_default_modes() {
1910 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
1911 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
1912
1913 let primitives = scope.into_primitives();
1914 assert_eq!(primitives.len(), 1);
1915 match &primitives[0] {
1916 DrawPrimitive::Blend {
1917 primitive,
1918 blend_mode,
1919 } => {
1920 assert_eq!(*blend_mode, BlendMode::DstOut);
1921 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
1922 }
1923 other => panic!("expected blended primitive, got {other:?}"),
1924 }
1925 }
1926
1927 #[test]
1928 fn draw_circle_records_centered_round_rect() {
1929 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
1930 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
1931
1932 let primitives = scope.into_primitives();
1933 assert_eq!(primitives.len(), 1);
1934 match &primitives[0] {
1935 DrawPrimitive::RoundRect { rect, radii, .. } => {
1936 assert_eq!(
1937 *rect,
1938 Rect {
1939 x: 7.0,
1940 y: 11.0,
1941 width: 10.0,
1942 height: 10.0,
1943 }
1944 );
1945 assert_eq!(*radii, CornerRadii::uniform(5.0));
1946 }
1947 other => panic!("expected circular round rect, got {other:?}"),
1948 }
1949 }
1950
1951 #[test]
1952 fn draw_circle_blend_wraps_non_default_modes() {
1953 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
1954 scope.draw_circle_blend(
1955 Brush::solid(Color::RED),
1956 Point::new(5.0, 5.0),
1957 3.0,
1958 BlendMode::Plus,
1959 );
1960
1961 let primitives = scope.into_primitives();
1962 assert_eq!(primitives.len(), 1);
1963 match &primitives[0] {
1964 DrawPrimitive::Blend {
1965 primitive,
1966 blend_mode,
1967 } => {
1968 assert_eq!(*blend_mode, BlendMode::Plus);
1969 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
1970 }
1971 other => panic!("expected blended circle primitive, got {other:?}"),
1972 }
1973 }
1974
1975 #[test]
1976 fn rect_union_encloses_both_inputs() {
1977 let lhs = Rect {
1978 x: 10.0,
1979 y: 5.0,
1980 width: 8.0,
1981 height: 4.0,
1982 };
1983 let rhs = Rect {
1984 x: 4.0,
1985 y: 7.0,
1986 width: 10.0,
1987 height: 6.0,
1988 };
1989
1990 assert_eq!(
1991 lhs.union(rhs),
1992 Rect {
1993 x: 4.0,
1994 y: 5.0,
1995 width: 14.0,
1996 height: 8.0,
1997 }
1998 );
1999 }
2000
2001 #[test]
2002 fn draw_image_uses_scope_size_as_default_rect() {
2003 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
2004 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
2005 scope.draw_image(image.clone());
2006 let primitives = scope.into_primitives();
2007 assert_eq!(primitives.len(), 1);
2008 match unwrap_image(&primitives[0]) {
2009 DrawPrimitive::Image {
2010 rect,
2011 image: actual,
2012 alpha,
2013 color_filter,
2014 sampling,
2015 src_rect,
2016 } => {
2017 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
2018 assert_eq!(*actual, image);
2019 assert_eq!(*alpha, 1.0);
2020 assert!(color_filter.is_none());
2021 assert_eq!(*sampling, ImageSampling::Nearest);
2022 assert!(src_rect.is_none());
2023 }
2024 other => panic!("expected image primitive, got {other:?}"),
2025 }
2026 }
2027
2028 #[test]
2029 fn draw_image_src_stores_src_rect() {
2030 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2031 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2032 let src = Rect {
2033 x: 10.0,
2034 y: 20.0,
2035 width: 30.0,
2036 height: 40.0,
2037 };
2038 let dst = Rect {
2039 x: 0.0,
2040 y: 0.0,
2041 width: 60.0,
2042 height: 80.0,
2043 };
2044 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
2045 let primitives = scope.into_primitives();
2046 assert_eq!(primitives.len(), 1);
2047 match unwrap_image(&primitives[0]) {
2048 DrawPrimitive::Image {
2049 rect,
2050 image: actual,
2051 alpha,
2052 sampling,
2053 src_rect,
2054 ..
2055 } => {
2056 assert_eq!(*rect, dst);
2057 assert_eq!(*actual, image);
2058 assert!((alpha - 0.8).abs() < 1e-5);
2059 assert_eq!(*sampling, ImageSampling::Nearest);
2060 assert_eq!(*src_rect, Some(src));
2061 }
2062 other => panic!("expected image primitive, got {other:?}"),
2063 }
2064 }
2065
2066 #[test]
2067 fn draw_image_at_sampled_records_requested_sampling() {
2068 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2069 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
2070 let dst = Rect {
2071 x: 2.0,
2072 y: 3.0,
2073 width: 40.0,
2074 height: 30.0,
2075 };
2076
2077 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
2078
2079 let primitives = scope.into_primitives();
2080 assert_eq!(primitives.len(), 1);
2081 match unwrap_image(&primitives[0]) {
2082 DrawPrimitive::Image {
2083 rect,
2084 image: actual,
2085 alpha,
2086 sampling,
2087 src_rect,
2088 ..
2089 } => {
2090 assert_eq!(*rect, dst);
2091 assert_eq!(*actual, image);
2092 assert!((alpha - 0.7).abs() < 1e-5);
2093 assert_eq!(*sampling, ImageSampling::Linear);
2094 assert!(src_rect.is_none());
2095 }
2096 other => panic!("expected image primitive, got {other:?}"),
2097 }
2098 }
2099
2100 #[test]
2101 fn draw_image_src_sampled_records_requested_sampling() {
2102 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2103 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2104 let src = Rect {
2105 x: 4.0,
2106 y: 6.0,
2107 width: 16.0,
2108 height: 20.0,
2109 };
2110 let dst = Rect {
2111 x: 8.0,
2112 y: 10.0,
2113 width: 32.0,
2114 height: 40.0,
2115 };
2116
2117 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
2118
2119 let primitives = scope.into_primitives();
2120 assert_eq!(primitives.len(), 1);
2121 match unwrap_image(&primitives[0]) {
2122 DrawPrimitive::Image {
2123 rect,
2124 image: actual,
2125 alpha,
2126 sampling,
2127 src_rect,
2128 ..
2129 } => {
2130 assert_eq!(*rect, dst);
2131 assert_eq!(*actual, image);
2132 assert!((alpha - 0.5).abs() < 1e-5);
2133 assert_eq!(*sampling, ImageSampling::Linear);
2134 assert_eq!(*src_rect, Some(src));
2135 }
2136 other => panic!("expected image primitive, got {other:?}"),
2137 }
2138 }
2139
2140 #[test]
2141 fn draw_image_at_clamps_alpha() {
2142 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2143 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
2144 scope.draw_image_at(
2145 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
2146 image,
2147 3.0,
2148 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
2149 );
2150 assert_image_alpha(&scope.into_primitives()[0], 1.0);
2151 }
2152
2153 #[test]
2154 fn graphics_layer_clone_with_render_effect() {
2155 let layer = GraphicsLayer {
2156 render_effect: Some(RenderEffect::blur(10.0)),
2157 backdrop_effect: Some(RenderEffect::blur(6.0)),
2158 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
2159 alpha: 0.5,
2160 rotation_z: 12.0,
2161 shadow_elevation: 4.0,
2162 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
2163 clip: true,
2164 compositing_strategy: CompositingStrategy::Offscreen,
2165 blend_mode: BlendMode::SrcOver,
2166 ..Default::default()
2167 };
2168 let cloned = layer.clone();
2169 assert_eq!(cloned.alpha, 0.5);
2170 assert!(cloned.render_effect.is_some());
2171 assert!(cloned.backdrop_effect.is_some());
2172 assert_eq!(layer.color_filter, cloned.color_filter);
2173 assert_eq!(layer.render_effect, cloned.render_effect);
2174 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
2175 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
2176 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
2177 assert_eq!(
2178 cloned.shape,
2179 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
2180 );
2181 assert!(cloned.clip);
2182 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
2183 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
2184 }
2185
2186 #[test]
2187 fn graphics_layer_default_has_no_effect() {
2188 let layer = GraphicsLayer::default();
2189 assert!(layer.color_filter.is_none());
2190 assert!(layer.render_effect.is_none());
2191 assert!(layer.backdrop_effect.is_none());
2192 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
2193 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
2194 assert_eq!(layer.alpha, 1.0);
2195 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
2196 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
2197 assert_eq!(layer.shape, LayerShape::Rectangle);
2198 assert!(!layer.clip);
2199 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
2200 assert_eq!(layer.spot_shadow_color, Color::BLACK);
2201 }
2202
2203 #[test]
2204 fn transform_origin_construction() {
2205 let origin = TransformOrigin::new(0.25, 0.75);
2206 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
2207 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
2208 }
2209
2210 #[test]
2211 fn layer_shape_default_is_rectangle() {
2212 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
2213 }
2214
2215 use std::f32::consts::{FRAC_PI_2, PI};
2216
2217 use crate::{StrokeCap, StrokeJoin};
2218
2219 fn approx(a: f32, b: f32) -> bool {
2220 (a - b).abs() < 0.25
2221 }
2222
2223 fn scope(size: f32) -> DrawScopeDefault {
2224 DrawScopeDefault::new(Size::new(size, size))
2225 }
2226
2227 #[test]
2228 fn draw_rect_stroked_records_scope_rect_and_stroke() {
2229 let mut scope = scope(20.0);
2230 scope.draw_rect_stroked(
2231 Brush::solid(Color::RED),
2232 Stroke::new(3.0).with_join(StrokeJoin::Bevel),
2233 );
2234
2235 let primitives = scope.into_primitives();
2236 assert_eq!(primitives.len(), 1);
2237 match &primitives[0] {
2238 DrawPrimitive::Rect {
2239 rect,
2240 stroke: Some(stroke),
2241 ..
2242 } => {
2243 assert_eq!(*rect, Rect::from_size(Size::new(20.0, 20.0)));
2244 assert_eq!(stroke.width, 3.0);
2245 assert_eq!(stroke.join, StrokeJoin::Bevel);
2246 }
2247 other => panic!("expected stroked rect, got {other:?}"),
2248 }
2249 }
2250
2251 #[test]
2252 fn draw_rect_at_stroked_records_requested_rect() {
2253 let mut scope = scope(50.0);
2254 let rect = Rect {
2255 x: 4.0,
2256 y: 6.0,
2257 width: 12.0,
2258 height: 9.0,
2259 };
2260 scope.draw_rect_at_stroked(rect, Brush::solid(Color::BLUE), Stroke::new(2.0));
2261 match &scope.into_primitives()[0] {
2262 DrawPrimitive::Rect {
2263 rect: actual,
2264 stroke: Some(stroke),
2265 ..
2266 } => {
2267 assert_eq!(*actual, rect);
2268 assert_eq!(stroke.width, 2.0);
2269 }
2270 other => panic!("expected stroked rect, got {other:?}"),
2271 }
2272 }
2273
2274 #[test]
2275 fn draw_round_rect_stroked_keeps_radii_and_stroke() {
2276 let mut scope = scope(30.0);
2277 scope.draw_round_rect_stroked(
2278 Brush::solid(Color::GREEN),
2279 CornerRadii::uniform(5.0),
2280 Stroke::new(4.0).with_join(StrokeJoin::Round),
2281 );
2282 match &scope.into_primitives()[0] {
2283 DrawPrimitive::RoundRect {
2284 rect,
2285 radii,
2286 stroke: Some(stroke),
2287 ..
2288 } => {
2289 assert_eq!(*rect, Rect::from_size(Size::new(30.0, 30.0)));
2290 assert_eq!(*radii, CornerRadii::uniform(5.0));
2291 assert_eq!(stroke.width, 4.0);
2292 assert_eq!(stroke.join, StrokeJoin::Round);
2293 }
2294 other => panic!("expected stroked round rect, got {other:?}"),
2295 }
2296 }
2297
2298 #[test]
2299 fn draw_round_rect_at_stroked_records_requested_rect() {
2300 let mut scope = scope(60.0);
2301 let rect = Rect {
2302 x: 1.0,
2303 y: 2.0,
2304 width: 20.0,
2305 height: 10.0,
2306 };
2307 scope.draw_round_rect_at_stroked(
2308 rect,
2309 Brush::solid(Color::WHITE),
2310 CornerRadii::uniform(3.0),
2311 Stroke::new(1.5),
2312 );
2313 match &scope.into_primitives()[0] {
2314 DrawPrimitive::RoundRect {
2315 rect: actual,
2316 radii,
2317 stroke: Some(stroke),
2318 ..
2319 } => {
2320 assert_eq!(*actual, rect);
2321 assert_eq!(*radii, CornerRadii::uniform(3.0));
2322 assert_eq!(stroke.width, 1.5);
2323 }
2324 other => panic!("expected stroked round rect, got {other:?}"),
2325 }
2326 }
2327
2328 #[test]
2329 fn draw_circle_stroked_lowers_to_stroked_round_rect() {
2330 let mut scope = scope(40.0);
2331 scope.draw_circle_stroked(
2332 Brush::solid(Color::BLUE),
2333 Point::new(12.0, 16.0),
2334 5.0,
2335 Stroke::new(2.0),
2336 );
2337 match &scope.into_primitives()[0] {
2338 DrawPrimitive::RoundRect {
2339 rect,
2340 radii,
2341 stroke: Some(stroke),
2342 ..
2343 } => {
2344 assert_eq!(
2345 *rect,
2346 Rect {
2347 x: 7.0,
2348 y: 11.0,
2349 width: 10.0,
2350 height: 10.0,
2351 }
2352 );
2353 assert_eq!(*radii, CornerRadii::uniform(5.0));
2354 assert_eq!(stroke.width, 2.0);
2355 }
2356 other => panic!("expected stroked circular round rect, got {other:?}"),
2357 }
2358 }
2359
2360 #[test]
2361 fn draw_arc_records_arc_primitive_with_tight_bounds() {
2362 let mut scope = scope(200.0);
2363 scope.draw_arc(
2364 Brush::solid(Color::RED),
2365 Point::new(100.0, 100.0),
2366 50.0,
2367 0.0,
2368 FRAC_PI_2,
2369 Stroke::new(10.0),
2370 );
2371 let primitives = scope.into_primitives();
2372 assert_eq!(primitives.len(), 1);
2373 match &primitives[0] {
2374 DrawPrimitive::Arc {
2375 rect,
2376 center,
2377 radius,
2378 start_angle,
2379 sweep_angle,
2380 stroke: Some(stroke),
2381 inner_radius,
2382 ..
2383 } => {
2384 assert_eq!(*center, Point::new(100.0, 100.0));
2385 assert_eq!(*radius, 50.0);
2386 assert_eq!(*start_angle, 0.0);
2387 assert!(approx(*sweep_angle, FRAC_PI_2));
2388 assert_eq!(stroke.width, 10.0);
2389 assert_eq!(*inner_radius, 0.0);
2390 assert!(approx(rect.x, 100.0), "{rect:?}");
2391 assert!(approx(rect.y, 100.0), "{rect:?}");
2392 assert!(approx(rect.width, 55.0), "{rect:?}");
2393 assert!(approx(rect.height, 55.0), "{rect:?}");
2394 }
2395 other => panic!("expected arc primitive, got {other:?}"),
2396 }
2397 }
2398
2399 #[test]
2400 fn draw_arc_bounds_cover_a_quadrant_spanning_sweep() {
2401 let mut scope = scope(200.0);
2402 scope.draw_arc(
2403 Brush::solid(Color::RED),
2404 Point::new(100.0, 100.0),
2405 50.0,
2406 0.0,
2407 3.0 * FRAC_PI_2,
2408 Stroke::new(4.0),
2409 );
2410 let DrawPrimitive::Arc { rect, .. } = &scope.into_primitives()[0] else {
2411 panic!("expected arc primitive");
2412 };
2413 assert!(approx(rect.x, 48.0), "{rect:?}");
2414 assert!(approx(rect.y, 48.0), "{rect:?}");
2415 assert!(approx(rect.width, 104.0), "{rect:?}");
2416 assert!(approx(rect.height, 104.0), "{rect:?}");
2417 }
2418
2419 #[test]
2420 fn draw_annular_sector_records_inner_radius_and_no_stroke() {
2421 let mut scope = scope(200.0);
2422 scope.draw_annular_sector(
2423 Brush::solid(Color::WHITE),
2424 Point::new(100.0, 100.0),
2425 30.0,
2426 50.0,
2427 0.0,
2428 PI,
2429 );
2430 match &scope.into_primitives()[0] {
2431 DrawPrimitive::Arc {
2432 rect,
2433 center,
2434 radius,
2435 inner_radius,
2436 stroke,
2437 sweep_angle,
2438 ..
2439 } => {
2440 assert!(stroke.is_none(), "annular sectors are filled, not stroked");
2441 assert_eq!(*center, Point::new(100.0, 100.0));
2442 assert_eq!(*radius, 50.0);
2443 assert_eq!(*inner_radius, 30.0);
2444 assert!(approx(*sweep_angle, PI));
2445 assert!(approx(rect.x, 50.0), "{rect:?}");
2446 assert!(approx(rect.y, 100.0), "{rect:?}");
2447 assert!(approx(rect.width, 100.0), "{rect:?}");
2448 assert!(approx(rect.height, 50.0), "{rect:?}");
2449 }
2450 other => panic!("expected arc primitive, got {other:?}"),
2451 }
2452 }
2453
2454 #[test]
2455 fn draw_arc_blend_wraps_non_default_modes() {
2456 let mut scope = scope(100.0);
2457 scope.draw_arc_blend(
2458 Brush::solid(Color::RED),
2459 Point::new(50.0, 50.0),
2460 20.0,
2461 0.0,
2462 1.0,
2463 Stroke::new(2.0),
2464 BlendMode::DstOut,
2465 );
2466 match &scope.into_primitives()[0] {
2467 DrawPrimitive::Blend {
2468 primitive,
2469 blend_mode,
2470 } => {
2471 assert_eq!(*blend_mode, BlendMode::DstOut);
2472 assert!(matches!(**primitive, DrawPrimitive::Arc { .. }));
2473 }
2474 other => panic!("expected blended arc, got {other:?}"),
2475 }
2476 }
2477
2478 #[test]
2479 fn draw_annular_sector_blend_wraps_non_default_modes() {
2480 let mut scope = scope(100.0);
2481 scope.draw_annular_sector_blend(
2482 Brush::solid(Color::RED),
2483 Point::new(50.0, 50.0),
2484 5.0,
2485 20.0,
2486 0.0,
2487 1.0,
2488 BlendMode::Plus,
2489 );
2490 assert!(matches!(
2491 &scope.into_primitives()[0],
2492 DrawPrimitive::Blend {
2493 blend_mode: BlendMode::Plus,
2494 ..
2495 }
2496 ));
2497 }
2498
2499 #[test]
2500 fn stroked_blend_variants_wrap_non_default_modes() {
2501 let mut scope = scope(20.0);
2502 scope.draw_rect_stroked_blend(
2503 Brush::solid(Color::RED),
2504 Stroke::new(2.0),
2505 BlendMode::DstOut,
2506 );
2507 scope.draw_round_rect_stroked_blend(
2508 Brush::solid(Color::RED),
2509 CornerRadii::uniform(2.0),
2510 Stroke::new(2.0),
2511 BlendMode::DstOut,
2512 );
2513 scope.draw_circle_stroked_blend(
2514 Brush::solid(Color::RED),
2515 Point::new(10.0, 10.0),
2516 5.0,
2517 Stroke::new(2.0),
2518 BlendMode::DstOut,
2519 );
2520 let primitives = scope.into_primitives();
2521 assert_eq!(primitives.len(), 3);
2522 for primitive in &primitives {
2523 assert!(
2524 matches!(
2525 primitive,
2526 DrawPrimitive::Blend {
2527 blend_mode: BlendMode::DstOut,
2528 ..
2529 }
2530 ),
2531 "expected blended primitive, got {primitive:?}"
2532 );
2533 }
2534 }
2535
2536 #[test]
2537 fn negative_sweeps_and_overlong_sweeps_produce_finite_bounds() {
2538 let mut scope = scope(200.0);
2539 scope.draw_arc(
2540 Brush::solid(Color::RED),
2541 Point::new(100.0, 100.0),
2542 40.0,
2543 FRAC_PI_2,
2544 -FRAC_PI_2,
2545 Stroke::new(4.0),
2546 );
2547 scope.draw_arc(
2548 Brush::solid(Color::RED),
2549 Point::new(100.0, 100.0),
2550 40.0,
2551 0.3,
2552 crate::stroke::TAU * 4.0,
2553 Stroke::new(4.0),
2554 );
2555 let primitives = scope.into_primitives();
2556 assert_eq!(primitives.len(), 2);
2557
2558 let DrawPrimitive::Arc { rect: negative, .. } = &primitives[0] else {
2559 panic!("expected arc");
2560 };
2561 assert!(approx(negative.x, 100.0), "{negative:?}");
2562 assert!(approx(negative.y, 100.0), "{negative:?}");
2563 assert!(approx(negative.width, 42.0), "{negative:?}");
2564
2565 let DrawPrimitive::Arc { rect: full, .. } = &primitives[1] else {
2566 panic!("expected arc");
2567 };
2568 assert!(approx(full.x, 58.0), "{full:?}");
2569 assert!(approx(full.width, 84.0), "{full:?}");
2570 assert!(approx(full.height, 84.0), "{full:?}");
2571 }
2572
2573 #[test]
2574 fn degenerate_stroke_and_arc_inputs_emit_nothing_and_never_panic() {
2575 let mut scope = scope(50.0);
2576 let brush = Brush::solid(Color::RED);
2577 let center = Point::new(25.0, 25.0);
2578
2579 scope.draw_rect_stroked(brush.clone(), Stroke::new(0.0));
2580 scope.draw_rect_stroked(brush.clone(), Stroke::new(-4.0));
2581 scope.draw_rect_stroked(brush.clone(), Stroke::new(f32::NAN));
2582 scope.draw_round_rect_stroked(brush.clone(), CornerRadii::uniform(2.0), Stroke::new(0.0));
2583 scope.draw_circle_stroked(brush.clone(), center, 10.0, Stroke::new(0.0));
2584 scope.draw_circle_stroked(brush.clone(), center, f32::NAN, Stroke::new(2.0));
2585 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 0.0, Stroke::new(2.0));
2586 scope.draw_arc(brush.clone(), center, 10.0, 0.0, f32::NAN, Stroke::new(2.0));
2587 scope.draw_arc(
2588 brush.clone(),
2589 center,
2590 f32::INFINITY,
2591 0.0,
2592 1.0,
2593 Stroke::new(2.0),
2594 );
2595 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 1.0, Stroke::new(0.0));
2596 scope.draw_arc(brush.clone(), center, 0.0, 0.0, 1.0, Stroke::new(0.0));
2597 scope.draw_annular_sector(brush.clone(), center, 10.0, 10.0, 0.0, 1.0);
2598 scope.draw_annular_sector(brush.clone(), center, 20.0, 10.0, 0.0, 1.0);
2599 scope.draw_annular_sector(brush.clone(), center, 0.0, 0.0, 0.0, 1.0);
2600 scope.draw_annular_sector(brush.clone(), center, 0.0, 10.0, 0.0, 0.0);
2601 scope.draw_annular_sector(brush, center, f32::NAN, 10.0, 0.0, 1.0);
2602
2603 assert!(
2604 scope.into_primitives().is_empty(),
2605 "degenerate stroke/arc requests must not reach the renderer"
2606 );
2607 }
2608
2609 #[test]
2610 fn zero_radius_arc_with_positive_width_stays_finite() {
2611 let mut scope = scope(50.0);
2612 scope.draw_arc(
2613 Brush::solid(Color::RED),
2614 Point::new(25.0, 25.0),
2615 0.0,
2616 0.0,
2617 FRAC_PI_2,
2618 Stroke::new(6.0).with_cap(StrokeCap::Round),
2619 );
2620 let primitives = scope.into_primitives();
2621 assert_eq!(primitives.len(), 1);
2622 let DrawPrimitive::Arc { rect, .. } = &primitives[0] else {
2623 panic!("expected arc");
2624 };
2625 for value in [rect.x, rect.y, rect.width, rect.height] {
2626 assert!(value.is_finite(), "{rect:?}");
2627 }
2628 assert!(rect.width > 0.0 && rect.height > 0.0, "{rect:?}");
2629 }
2630
2631 struct FixedAdvanceTextMeasurer {
2632 advance: f32,
2633 line_height: f32,
2634 calls: std::cell::Cell<usize>,
2635 }
2636
2637 impl FixedAdvanceTextMeasurer {
2638 fn shared(advance: f32, line_height: f32) -> Rc<Self> {
2639 Rc::new(Self {
2640 advance,
2641 line_height,
2642 calls: std::cell::Cell::new(0),
2643 })
2644 }
2645 }
2646
2647 impl DrawTextMeasurer for FixedAdvanceTextMeasurer {
2648 fn measure_text(&self, text: &str, _style: &DrawTextStyle) -> TextMeasurement {
2649 self.calls.set(self.calls.get() + 1);
2650 let lines: Vec<&str> = text.split('\n').collect();
2651 let width = lines
2652 .iter()
2653 .map(|line| line.chars().count() as f32 * self.advance)
2654 .fold(0.0_f32, f32::max);
2655 TextMeasurement {
2656 size: Size::new(width, lines.len() as f32 * self.line_height),
2657 line_height: self.line_height,
2658 first_baseline: self.line_height * 0.75,
2659 line_count: lines.len(),
2660 }
2661 }
2662 }
2663
2664 fn text_scope(size: Size) -> (DrawScopeDefault, Rc<FixedAdvanceTextMeasurer>) {
2665 let measurer = FixedAdvanceTextMeasurer::shared(10.0, 20.0);
2666 (
2667 DrawScopeDefault::with_text_measurer(size, measurer.clone()),
2668 measurer,
2669 )
2670 }
2671
2672 fn unwrap_text(primitive: &DrawPrimitive) -> &TextPrimitive {
2673 match primitive {
2674 DrawPrimitive::Text(text) => text,
2675 other => panic!("expected text primitive, got {other:?}"),
2676 }
2677 }
2678
2679 #[test]
2680 fn drawn_text_occupies_exactly_the_box_measure_text_reported() {
2681 let (mut scope, _) = text_scope(Size::new(200.0, 100.0));
2682 let style = DrawTextStyle::new(16.0);
2683 let measured = scope.measure_text("ABCD", &style);
2684
2685 scope.draw_text_from(
2686 Point::new(7.0, 11.0),
2687 Brush::solid(Color::WHITE),
2688 "ABCD",
2689 &style,
2690 );
2691
2692 let primitives = scope.into_primitives();
2693 assert_eq!(primitives.len(), 1);
2694 let text = unwrap_text(&primitives[0]);
2695 assert_eq!(
2696 text.rect,
2697 Rect {
2698 x: 7.0,
2699 y: 11.0,
2700 width: measured.size.width,
2701 height: measured.size.height,
2702 },
2703 "the drawn block must be the measured block, or callers cannot center text"
2704 );
2705 assert_eq!(&*text.text, "ABCD");
2706 assert_eq!(text.color, Color::WHITE);
2707 }
2708
2709 #[test]
2710 fn text_alignment_positions_the_measured_block_inside_the_box() {
2711 let box_rect = Rect {
2712 x: 100.0,
2713 y: 50.0,
2714 width: 200.0,
2715 height: 80.0,
2716 };
2717 let cases = [
2718 (TextAlign::Left, TextVerticalAlign::Top, 100.0, 50.0),
2719 (TextAlign::Center, TextVerticalAlign::Center, 190.0, 80.0),
2720 (TextAlign::Right, TextVerticalAlign::Bottom, 280.0, 110.0),
2721 ];
2722 for (align, vertical_align, expected_x, expected_y) in cases {
2723 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2724 let style = DrawTextStyle::new(16.0)
2725 .with_align(align)
2726 .with_vertical_align(vertical_align);
2727 scope.draw_text_at(box_rect, Brush::solid(Color::WHITE), "AB", &style);
2728 let primitives = scope.into_primitives();
2729 let text = unwrap_text(&primitives[0]);
2730 assert!(
2731 approx(text.rect.x, expected_x) && approx(text.rect.y, expected_y),
2732 "{align:?}/{vertical_align:?} placed the block at {:?}",
2733 text.rect
2734 );
2735 assert!(approx(text.rect.width, 20.0) && approx(text.rect.height, 20.0));
2736 }
2737 }
2738
2739 #[test]
2740 fn baseline_aligned_text_hangs_above_the_box_edge() {
2741 let (mut scope, _) = text_scope(Size::new(200.0, 200.0));
2742 let style = DrawTextStyle::new(16.0).with_vertical_align(TextVerticalAlign::Baseline);
2743 let measured = scope.measure_text("Ag", &style);
2744 scope.draw_text_at(
2745 Rect {
2746 x: 0.0,
2747 y: 100.0,
2748 width: 200.0,
2749 height: 0.0,
2750 },
2751 Brush::solid(Color::WHITE),
2752 "Ag",
2753 &style,
2754 );
2755 let primitives = scope.into_primitives();
2756 let text = unwrap_text(&primitives[0]);
2757 assert!(
2758 approx(text.rect.y, 100.0 - measured.first_baseline),
2759 "{:?}",
2760 text.rect
2761 );
2762 }
2763
2764 #[test]
2765 fn draw_text_fills_the_whole_scope_rect() {
2766 let (mut scope, _) = text_scope(Size::new(120.0, 60.0));
2767 let style = DrawTextStyle::new(16.0)
2768 .with_align(TextAlign::Right)
2769 .with_vertical_align(TextVerticalAlign::Bottom);
2770 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
2771 let primitives = scope.into_primitives();
2772 let text = unwrap_text(&primitives[0]);
2773 assert!(
2774 approx(text.rect.x, 100.0) && approx(text.rect.y, 40.0),
2775 "{:?}",
2776 text.rect
2777 );
2778 }
2779
2780 #[test]
2781 fn draw_text_from_ignores_alignment_and_anchors_the_top_left() {
2782 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2783 let style = DrawTextStyle::new(16.0)
2784 .with_align(TextAlign::Center)
2785 .with_vertical_align(TextVerticalAlign::Bottom);
2786 scope.draw_text_from(
2787 Point::new(30.0, 40.0),
2788 Brush::solid(Color::WHITE),
2789 "AB",
2790 &style,
2791 );
2792 let primitives = scope.into_primitives();
2793 let text = unwrap_text(&primitives[0]);
2794 assert!(
2795 approx(text.rect.x, 30.0) && approx(text.rect.y, 40.0),
2796 "{:?}",
2797 text.rect
2798 );
2799 }
2800
2801 #[test]
2802 fn multiline_text_measures_the_widest_line_and_stacks_the_lines() {
2803 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
2804 let style = DrawTextStyle::new(16.0);
2805 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "AB\nABCDE", &style);
2806 let primitives = scope.into_primitives();
2807 let text = unwrap_text(&primitives[0]);
2808 assert!(approx(text.rect.width, 50.0), "{:?}", text.rect);
2809 assert!(approx(text.rect.height, 40.0), "{:?}", text.rect);
2810 }
2811
2812 #[test]
2813 fn empty_text_draws_nothing_and_never_measures() {
2814 let (mut scope, measurer) = text_scope(Size::new(100.0, 100.0));
2815 scope.draw_text(Brush::solid(Color::WHITE), "", &DrawTextStyle::new(16.0));
2816 scope.draw_text_at(
2817 Rect::from_size(Size::new(10.0, 10.0)),
2818 Brush::solid(Color::WHITE),
2819 "",
2820 &DrawTextStyle::new(16.0),
2821 );
2822 scope.draw_text_from(
2823 Point::ZERO,
2824 Brush::solid(Color::WHITE),
2825 "",
2826 &DrawTextStyle::new(16.0),
2827 );
2828 assert!(scope.into_primitives().is_empty());
2829 assert_eq!(
2830 measurer.calls.get(),
2831 0,
2832 "an empty string must not cost a measurement"
2833 );
2834 }
2835
2836 #[test]
2837 fn invisible_text_draws_nothing() {
2838 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2839 let style = DrawTextStyle::new(16.0);
2840 scope.draw_text(Brush::solid(Color(1.0, 1.0, 1.0, 0.0)), "AB", &style);
2841 scope.draw_text(
2842 Brush::LinearGradient {
2843 colors: Vec::new(),
2844 stops: None,
2845 start: Point::ZERO,
2846 end: Point::new(1.0, 1.0),
2847 tile_mode: crate::render_effect::TileMode::Clamp,
2848 },
2849 "AB",
2850 &style,
2851 );
2852 assert!(scope.into_primitives().is_empty());
2853 }
2854
2855 #[test]
2856 fn gradient_text_brushes_fall_back_to_their_first_stop() {
2857 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2858 scope.draw_text(
2859 Brush::linear_gradient(vec![Color::RED, Color::BLUE]),
2860 "AB",
2861 &DrawTextStyle::new(16.0),
2862 );
2863 let primitives = scope.into_primitives();
2864 assert_eq!(unwrap_text(&primitives[0]).color, Color::RED);
2865 }
2866
2867 #[test]
2868 fn a_scope_without_a_measurer_falls_back_to_the_font_free_estimate() {
2869 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2870 let style = DrawTextStyle::new(16.0);
2871 assert_eq!(
2872 scope.measure_text("ABC", &style),
2873 crate::estimate_text_measurement("ABC", &style)
2874 );
2875 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "ABC", &style);
2876 let primitives = scope.into_primitives();
2877 let text = unwrap_text(&primitives[0]);
2878 assert!(text.rect.width > 0.0 && text.rect.height > 0.0);
2879 }
2880
2881 #[test]
2882 fn degenerate_text_geometry_emits_nothing_and_never_panics() {
2883 struct DegenerateTextMeasurer;
2884 impl DrawTextMeasurer for DegenerateTextMeasurer {
2885 fn measure_text(&self, _text: &str, _style: &DrawTextStyle) -> TextMeasurement {
2886 TextMeasurement {
2887 size: Size::new(f32::NAN, 0.0),
2888 line_height: f32::NAN,
2889 first_baseline: f32::NAN,
2890 line_count: 1,
2891 }
2892 }
2893 }
2894
2895 let mut scope = DrawScopeDefault::with_text_measurer(
2896 Size::new(50.0, 50.0),
2897 Rc::new(DegenerateTextMeasurer),
2898 );
2899 scope.draw_text(Brush::solid(Color::WHITE), "AB", &DrawTextStyle::new(16.0));
2900 scope.draw_text_at(
2901 Rect {
2902 x: f32::NAN,
2903 y: 0.0,
2904 width: 10.0,
2905 height: 10.0,
2906 },
2907 Brush::solid(Color::WHITE),
2908 "AB",
2909 &DrawTextStyle::new(16.0),
2910 );
2911 assert!(
2912 scope.into_primitives().is_empty(),
2913 "unmeasurable text must not reach the renderer"
2914 );
2915 }
2916
2917 #[test]
2918 fn text_style_survives_lowering_into_the_primitive() {
2919 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
2920 let style = DrawTextStyle::new(21.0)
2921 .with_font_family("Fira Sans")
2922 .with_weight(FontWeight::BOLD)
2923 .with_style(FontStyle::Italic)
2924 .with_letter_spacing(2.0)
2925 .with_line_height(26.0);
2926 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
2927 let primitives = scope.into_primitives();
2928 assert_eq!(unwrap_text(&primitives[0]).style, style);
2929 }
2930
2931 #[test]
2932 fn a_layers_composite_alpha_is_a_truncated_byte() {
2933 for byte in 0..=255u32 {
2934 let exact = byte as f32 / 255.0;
2935 assert!(
2936 (GraphicsLayer::composite_alpha_8bit(exact) - exact).abs() < 1e-6,
2937 "byte {byte} moved"
2938 );
2939 if byte < 255 {
2940 let nearly_next = (byte as f32 + 0.999) / 255.0;
2941 assert!(
2942 (GraphicsLayer::composite_alpha_8bit(nearly_next) - exact).abs() < 1e-6,
2943 "byte {byte} + 0.999 did not truncate"
2944 );
2945 }
2946 }
2947 assert_eq!(GraphicsLayer::composite_alpha_8bit(1.0), 1.0);
2948 assert_eq!(GraphicsLayer::composite_alpha_8bit(0.0), 0.0);
2949 assert_eq!(GraphicsLayer::composite_alpha_8bit(-3.0), 0.0);
2950 assert_eq!(GraphicsLayer::composite_alpha_8bit(7.0), 1.0);
2951 }
2952}