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 const EMPTY: Rect = Rect {
140 x: 0.0,
141 y: 0.0,
142 width: 0.0,
143 height: 0.0,
144 };
145
146 pub fn from_origin_size(origin: Point, size: Size) -> Self {
147 Self {
148 x: origin.x,
149 y: origin.y,
150 width: size.width,
151 height: size.height,
152 }
153 }
154
155 pub fn from_size(size: Size) -> Self {
156 Self {
157 x: 0.0,
158 y: 0.0,
159 width: size.width,
160 height: size.height,
161 }
162 }
163
164 pub fn translate(&self, dx: f32, dy: f32) -> Self {
165 Self {
166 x: self.x + dx,
167 y: self.y + dy,
168 width: self.width,
169 height: self.height,
170 }
171 }
172
173 pub fn contains(&self, x: f32, y: f32) -> bool {
174 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
175 }
176
177 pub fn is_empty(&self) -> bool {
179 self.width <= 0.0 || self.height <= 0.0
180 }
181
182 pub fn intersect(&self, other: Rect) -> Option<Rect> {
184 let left = self.x.max(other.x);
185 let top = self.y.max(other.y);
186 let right = (self.x + self.width).min(other.x + other.width);
187 let bottom = (self.y + self.height).min(other.y + other.height);
188 let width = right - left;
189 let height = bottom - top;
190 if width <= 0.0 || height <= 0.0 {
191 None
192 } else {
193 Some(Rect {
194 x: left,
195 y: top,
196 width,
197 height,
198 })
199 }
200 }
201
202 pub fn union(&self, other: Rect) -> Rect {
203 let left = self.x.min(other.x);
204 let top = self.y.min(other.y);
205 let right = (self.x + self.width).max(other.x + other.width);
206 let bottom = (self.y + self.height).max(other.y + other.height);
207 Rect {
208 x: left,
209 y: top,
210 width: (right - left).max(0.0),
211 height: (bottom - top).max(0.0),
212 }
213 }
214}
215
216#[derive(Clone, Copy, Debug, Default, PartialEq)]
218pub struct EdgeInsets {
219 pub left: f32,
220 pub top: f32,
221 pub right: f32,
222 pub bottom: f32,
223}
224
225impl EdgeInsets {
226 pub fn uniform(all: f32) -> Self {
227 Self {
228 left: all,
229 top: all,
230 right: all,
231 bottom: all,
232 }
233 }
234
235 pub fn horizontal(horizontal: f32) -> Self {
236 Self {
237 left: horizontal,
238 right: horizontal,
239 ..Self::default()
240 }
241 }
242
243 pub fn vertical(vertical: f32) -> Self {
244 Self {
245 top: vertical,
246 bottom: vertical,
247 ..Self::default()
248 }
249 }
250
251 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
252 Self {
253 left: horizontal,
254 right: horizontal,
255 top: vertical,
256 bottom: vertical,
257 }
258 }
259
260 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
261 Self {
262 left,
263 top,
264 right,
265 bottom,
266 }
267 }
268
269 pub fn is_zero(&self) -> bool {
270 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
271 }
272
273 pub fn horizontal_sum(&self) -> f32 {
274 self.left + self.right
275 }
276
277 pub fn vertical_sum(&self) -> f32 {
278 self.top + self.bottom
279 }
280
281 pub fn inset_rect(&self, size: Size) -> Rect {
284 Rect {
285 x: self.left,
286 y: self.top,
287 width: (size.width - self.horizontal_sum()).max(0.0),
288 height: (size.height - self.vertical_sum()).max(0.0),
289 }
290 }
291}
292
293impl AddAssign for EdgeInsets {
294 fn add_assign(&mut self, rhs: Self) {
295 self.left += rhs.left;
296 self.top += rhs.top;
297 self.right += rhs.right;
298 self.bottom += rhs.bottom;
299 }
300}
301
302#[derive(Clone, Copy, Debug, Default, PartialEq)]
303pub struct CornerRadii {
304 pub top_left: f32,
305 pub top_right: f32,
306 pub bottom_right: f32,
307 pub bottom_left: f32,
308}
309
310impl CornerRadii {
311 pub fn uniform(radius: f32) -> Self {
312 Self {
313 top_left: radius,
314 top_right: radius,
315 bottom_right: radius,
316 bottom_left: radius,
317 }
318 }
319}
320
321#[derive(Clone, Copy, Debug, PartialEq)]
322pub struct RoundedCornerShape {
323 radii: CornerRadii,
324}
325
326impl RoundedCornerShape {
327 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
328 Self {
329 radii: CornerRadii {
330 top_left,
331 top_right,
332 bottom_right,
333 bottom_left,
334 },
335 }
336 }
337
338 pub fn uniform(radius: f32) -> Self {
339 Self {
340 radii: CornerRadii::uniform(radius),
341 }
342 }
343
344 pub fn with_radii(radii: CornerRadii) -> Self {
345 Self { radii }
346 }
347
348 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
349 let mut resolved = self.radii;
350 let max_width = (width / 2.0).max(0.0);
351 let max_height = (height / 2.0).max(0.0);
352 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
353 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
354 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
355 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
356 resolved
357 }
358
359 pub fn radii(&self) -> CornerRadii {
360 self.radii
361 }
362}
363
364#[derive(Clone, Copy, Debug, PartialEq)]
365pub struct TransformOrigin {
366 pub pivot_fraction_x: f32,
367 pub pivot_fraction_y: f32,
368}
369
370impl TransformOrigin {
371 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
372 Self {
373 pivot_fraction_x,
374 pivot_fraction_y,
375 }
376 }
377
378 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
379}
380
381impl Default for TransformOrigin {
382 fn default() -> Self {
383 Self::CENTER
384 }
385}
386
387#[derive(Clone, Copy, Debug, Default, PartialEq)]
388pub enum LayerShape {
389 #[default]
390 Rectangle,
391 Rounded(RoundedCornerShape),
392}
393
394#[derive(Clone, Debug, PartialEq)]
395pub struct GraphicsLayer {
396 pub alpha: f32,
397 pub scale: f32,
398 pub scale_x: f32,
399 pub scale_y: f32,
400 pub rotation_x: f32,
401 pub rotation_y: f32,
402 pub rotation_z: f32,
403 pub camera_distance: f32,
404 pub transform_origin: TransformOrigin,
405 pub translation_x: f32,
406 pub translation_y: f32,
407 pub shadow_elevation: f32,
408 pub ambient_shadow_color: Color,
409 pub spot_shadow_color: Color,
410 pub shape: LayerShape,
411 pub clip: bool,
412 pub compositing_strategy: CompositingStrategy,
413 pub blend_mode: BlendMode,
414 pub color_filter: Option<ColorFilter>,
415 pub render_effect: Option<crate::render_effect::RenderEffect>,
416 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
417}
418
419impl GraphicsLayer {
420 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
444 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
445 }
446}
447
448impl Default for GraphicsLayer {
449 fn default() -> Self {
450 Self {
451 alpha: 1.0,
452 scale: 1.0,
453 scale_x: 1.0,
454 scale_y: 1.0,
455 rotation_x: 0.0,
456 rotation_y: 0.0,
457 rotation_z: 0.0,
458 camera_distance: 8.0,
459 transform_origin: TransformOrigin::CENTER,
460 translation_x: 0.0,
461 translation_y: 0.0,
462 shadow_elevation: 0.0,
463 ambient_shadow_color: Color::BLACK,
464 spot_shadow_color: Color::BLACK,
465 shape: LayerShape::Rectangle,
466 clip: false,
467 compositing_strategy: CompositingStrategy::Auto,
468 blend_mode: BlendMode::SrcOver,
469 color_filter: None,
470 render_effect: None,
471 backdrop_effect: None,
472 }
473 }
474}
475
476#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
481pub enum BlendMode {
482 Clear,
483 Src,
484 Dst,
485 #[default]
486 SrcOver,
487 DstOver,
488 SrcIn,
489 DstIn,
490 SrcOut,
491 DstOut,
492 SrcAtop,
493 DstAtop,
494 Xor,
495 Plus,
496 Modulate,
497 Screen,
498 Overlay,
499 Darken,
500 Lighten,
501 ColorDodge,
502 ColorBurn,
503 HardLight,
504 SoftLight,
505 Difference,
506 Exclusion,
507 Multiply,
508 Hue,
509 Saturation,
510 Color,
511 Luminosity,
512}
513
514impl BlendMode {
516 pub const ALL: [BlendMode; 29] = [
518 BlendMode::Clear,
519 BlendMode::Src,
520 BlendMode::Dst,
521 BlendMode::SrcOver,
522 BlendMode::DstOver,
523 BlendMode::SrcIn,
524 BlendMode::DstIn,
525 BlendMode::SrcOut,
526 BlendMode::DstOut,
527 BlendMode::SrcAtop,
528 BlendMode::DstAtop,
529 BlendMode::Xor,
530 BlendMode::Plus,
531 BlendMode::Modulate,
532 BlendMode::Screen,
533 BlendMode::Overlay,
534 BlendMode::Darken,
535 BlendMode::Lighten,
536 BlendMode::ColorDodge,
537 BlendMode::ColorBurn,
538 BlendMode::HardLight,
539 BlendMode::SoftLight,
540 BlendMode::Difference,
541 BlendMode::Exclusion,
542 BlendMode::Multiply,
543 BlendMode::Hue,
544 BlendMode::Saturation,
545 BlendMode::Color,
546 BlendMode::Luminosity,
547 ];
548}
549
550#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
551pub enum CompositingStrategy {
552 #[default]
554 Auto,
555 Offscreen,
557 ModulateAlpha,
559}
560
561#[derive(Clone, Debug, PartialEq)]
562pub enum DrawPrimitive {
563 Content,
566 Blend {
568 primitive: Box<DrawPrimitive>,
569 blend_mode: BlendMode,
570 },
571 Rect {
572 rect: Rect,
573 brush: Brush,
574 stroke: Option<Stroke>,
577 },
578 RoundRect {
579 rect: Rect,
580 brush: Brush,
581 radii: CornerRadii,
582 stroke: Option<Stroke>,
585 },
586 Arc {
596 rect: Rect,
600 brush: Brush,
601 center: Point,
602 radius: f32,
603 start_angle: f32,
604 sweep_angle: f32,
605 stroke: Option<Stroke>,
606 inner_radius: f32,
608 },
609 Image {
610 rect: Rect,
611 image: ImageBitmap,
612 alpha: f32,
613 color_filter: Option<ColorFilter>,
614 sampling: ImageSampling,
615 src_rect: Option<Rect>,
619 },
620 Text(Box<TextPrimitive>),
622 Shadow(ShadowPrimitive),
625}
626
627#[derive(Clone, Debug, PartialEq)]
636pub struct TextPrimitive {
637 pub rect: Rect,
640 pub text: std::rc::Rc<str>,
643 pub style: DrawTextStyle,
644 pub color: Color,
648}
649
650fn shared_text_str(text: &str) -> Rc<str> {
660 use std::{
661 cell::RefCell,
662 collections::HashMap,
663 hash::{Hash, Hasher},
664 };
665
666 const POOL_CAPACITY: usize = 256;
667 thread_local! {
668 static POOL: RefCell<HashMap<u64, Rc<str>>> = RefCell::new(HashMap::new());
669 }
670
671 let mut hasher = crate::FxHasher::default();
672 text.hash(&mut hasher);
673 let key = hasher.finish();
674
675 POOL.with(|pool| {
676 let mut pool = pool.borrow_mut();
677 if let Some(shared) = pool.get(&key)
678 && &**shared == text
679 {
680 return Rc::clone(shared);
681 }
682 let shared: Rc<str> = Rc::from(text);
683 if pool.len() >= POOL_CAPACITY {
684 pool.clear();
685 }
686 pool.insert(key, Rc::clone(&shared));
687 shared
688 })
689}
690
691impl DrawPrimitive {
693 pub fn translate(self, dx: f32, dy: f32) -> Self {
696 match self {
697 DrawPrimitive::Content => DrawPrimitive::Content,
698 DrawPrimitive::Blend {
699 primitive,
700 blend_mode,
701 } => DrawPrimitive::Blend {
702 primitive: Box::new(primitive.translate(dx, dy)),
703 blend_mode,
704 },
705 DrawPrimitive::Rect {
706 rect,
707 brush,
708 stroke,
709 } => DrawPrimitive::Rect {
710 rect: rect.translate(dx, dy),
711 brush,
712 stroke,
713 },
714 DrawPrimitive::RoundRect {
715 rect,
716 brush,
717 radii,
718 stroke,
719 } => DrawPrimitive::RoundRect {
720 rect: rect.translate(dx, dy),
721 brush,
722 radii,
723 stroke,
724 },
725 DrawPrimitive::Arc {
726 rect,
727 brush,
728 center,
729 radius,
730 start_angle,
731 sweep_angle,
732 stroke,
733 inner_radius,
734 } => DrawPrimitive::Arc {
735 rect: rect.translate(dx, dy),
736 brush,
737 center: Point::new(center.x + dx, center.y + dy),
738 radius,
739 start_angle,
740 sweep_angle,
741 stroke,
742 inner_radius,
743 },
744 DrawPrimitive::Image {
745 rect,
746 image,
747 alpha,
748 color_filter,
749 sampling,
750 src_rect,
751 } => DrawPrimitive::Image {
752 rect: rect.translate(dx, dy),
753 image,
754 alpha,
755 color_filter,
756 sampling,
757 src_rect,
758 },
759 DrawPrimitive::Text(mut text) => {
760 text.rect = text.rect.translate(dx, dy);
761 DrawPrimitive::Text(text)
762 }
763 DrawPrimitive::Shadow(ShadowPrimitive::Drop {
764 shape,
765 cutout,
766 blur_radius,
767 blend_mode,
768 }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
769 shape: Box::new(shape.translate(dx, dy)),
770 cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
771 blur_radius,
772 blend_mode,
773 }),
774 DrawPrimitive::Shadow(ShadowPrimitive::Inner {
775 fill,
776 cutout,
777 blur_radius,
778 blend_mode,
779 clip_rect,
780 }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
781 fill: Box::new(fill.translate(dx, dy)),
782 cutout: Box::new(cutout.translate(dx, dy)),
783 blur_radius,
784 blend_mode,
785 clip_rect: clip_rect.translate(dx, dy),
786 }),
787 }
788 }
789}
790
791#[derive(Clone, Debug, PartialEq)]
792pub enum ShadowPrimitive {
793 Drop {
797 shape: Box<DrawPrimitive>,
798 cutout: Option<Box<DrawPrimitive>>,
799 blur_radius: f32,
800 blend_mode: BlendMode,
801 },
802 Inner {
804 fill: Box<DrawPrimitive>,
805 cutout: Box<DrawPrimitive>,
806 blur_radius: f32,
807 blend_mode: BlendMode,
808 clip_rect: Rect,
810 },
811}
812
813pub trait DrawScope {
814 fn size(&self) -> Size;
815 fn draw_content(&mut self);
816 fn draw_rect(&mut self, brush: Brush);
817 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
818 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
820 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
821 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
822 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
823 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
825 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
826 fn draw_circle_blend(
827 &mut self,
828 brush: Brush,
829 center: Point,
830 radius: f32,
831 blend_mode: BlendMode,
832 );
833
834 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
836 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
837 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
839 fn draw_rect_at_stroked_blend(
840 &mut self,
841 rect: Rect,
842 brush: Brush,
843 stroke: Stroke,
844 blend_mode: BlendMode,
845 );
846 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
848 fn draw_round_rect_stroked_blend(
849 &mut self,
850 brush: Brush,
851 radii: CornerRadii,
852 stroke: Stroke,
853 blend_mode: BlendMode,
854 );
855 fn draw_round_rect_at_stroked(
857 &mut self,
858 rect: Rect,
859 brush: Brush,
860 radii: CornerRadii,
861 stroke: Stroke,
862 );
863 fn draw_round_rect_at_stroked_blend(
864 &mut self,
865 rect: Rect,
866 brush: Brush,
867 radii: CornerRadii,
868 stroke: Stroke,
869 blend_mode: BlendMode,
870 );
871 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
874 fn draw_circle_stroked_blend(
875 &mut self,
876 brush: Brush,
877 center: Point,
878 radius: f32,
879 stroke: Stroke,
880 blend_mode: BlendMode,
881 );
882
883 fn draw_arc(
896 &mut self,
897 brush: Brush,
898 center: Point,
899 radius: f32,
900 start_angle: f32,
901 sweep_angle: f32,
902 stroke: Stroke,
903 );
904 #[expect(clippy::too_many_arguments)]
905 fn draw_arc_blend(
906 &mut self,
907 brush: Brush,
908 center: Point,
909 radius: f32,
910 start_angle: f32,
911 sweep_angle: f32,
912 stroke: Stroke,
913 blend_mode: BlendMode,
914 );
915
916 fn draw_annular_sector(
925 &mut self,
926 brush: Brush,
927 center: Point,
928 inner_radius: f32,
929 outer_radius: f32,
930 start_angle: f32,
931 sweep_angle: f32,
932 );
933 #[expect(clippy::too_many_arguments)]
934 fn draw_annular_sector_blend(
935 &mut self,
936 brush: Brush,
937 center: Point,
938 inner_radius: f32,
939 outer_radius: f32,
940 start_angle: f32,
941 sweep_angle: f32,
942 blend_mode: BlendMode,
943 );
944
945 fn draw_image(&mut self, image: ImageBitmap);
946 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
947 fn draw_image_at(
948 &mut self,
949 rect: Rect,
950 image: ImageBitmap,
951 alpha: f32,
952 color_filter: Option<ColorFilter>,
953 );
954 fn draw_image_at_sampled(
955 &mut self,
956 rect: Rect,
957 image: ImageBitmap,
958 alpha: f32,
959 color_filter: Option<ColorFilter>,
960 sampling: ImageSampling,
961 );
962 fn draw_image_at_blend(
963 &mut self,
964 rect: Rect,
965 image: ImageBitmap,
966 alpha: f32,
967 color_filter: Option<ColorFilter>,
968 blend_mode: BlendMode,
969 );
970 fn draw_image_src(
973 &mut self,
974 image: ImageBitmap,
975 src_rect: Rect,
976 dst_rect: Rect,
977 alpha: f32,
978 color_filter: Option<ColorFilter>,
979 );
980 fn draw_image_src_sampled(
981 &mut self,
982 image: ImageBitmap,
983 src_rect: Rect,
984 dst_rect: Rect,
985 alpha: f32,
986 color_filter: Option<ColorFilter>,
987 sampling: ImageSampling,
988 );
989 fn draw_image_src_blend(
990 &mut self,
991 image: ImageBitmap,
992 src_rect: Rect,
993 dst_rect: Rect,
994 alpha: f32,
995 color_filter: Option<ColorFilter>,
996 blend_mode: BlendMode,
997 );
998 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1007 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1013 if let Ok(path) = crate::VectorPath::parse(d) {
1014 self.draw_vector_path(&path, brush);
1015 }
1016 }
1017
1018 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1025
1026 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1029 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1030 }
1031
1032 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1039
1040 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1046 if text.is_empty() {
1047 return;
1048 }
1049 let measurement = self.measure_text(text, style);
1050 self.draw_text_at(
1051 Rect::from_origin_size(top_left, measurement.size),
1052 brush,
1053 text,
1054 &DrawTextStyle {
1055 align: TextAlign::Left,
1056 vertical_align: TextVerticalAlign::Top,
1057 ..style.clone()
1058 },
1059 );
1060 }
1061
1062 fn into_primitives(self) -> Vec<DrawPrimitive>;
1063}
1064
1065pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1071 let x = match style.align {
1072 TextAlign::Left => rect.x,
1073 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1074 TextAlign::Right => rect.x + rect.width - measurement.size.width,
1075 };
1076 let y = match style.vertical_align {
1077 TextVerticalAlign::Top => rect.y,
1078 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1079 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1080 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1081 };
1082 Point::new(x, y)
1083}
1084
1085#[derive(Default)]
1086pub struct DrawScopeDefault {
1087 size: Size,
1088 recording: CommandRecorder,
1089 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1090}
1091
1092const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1093
1094thread_local! {
1095 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
1096 std::cell::RefCell::new(std::collections::HashMap::new());
1097}
1098
1099fn recorded_primitive_capacity(size: Size) -> usize {
1100 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1101 counts
1102 .borrow()
1103 .get(&(size.width.to_bits(), size.height.to_bits()))
1104 .copied()
1105 .unwrap_or(0)
1106 })
1107}
1108
1109fn note_recorded_primitive_count(size: Size, count: usize) {
1110 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1111 let mut counts = counts.borrow_mut();
1112 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1113 counts.clear();
1114 }
1115 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1116 });
1117}
1118
1119impl DrawScopeDefault {
1120 pub fn new(size: Size) -> Self {
1121 Self::with_storage(size, None, CommandRecording::default())
1122 }
1123
1124 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1129 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1130 }
1131
1132 pub fn with_text_measurer_reusing(
1135 size: Size,
1136 text_measurer: Rc<dyn DrawTextMeasurer>,
1137 storage: CommandRecording,
1138 ) -> Self {
1139 Self::with_storage(size, Some(text_measurer), storage)
1140 }
1141
1142 fn with_storage(
1143 size: Size,
1144 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1145 recording: CommandRecording,
1146 ) -> Self {
1147 let mut recording = CommandRecorder::reusing(recording);
1148 recording.reserve_shapes(recorded_primitive_capacity(size));
1149 Self {
1150 size,
1151 recording,
1152 text_measurer,
1153 }
1154 }
1155
1156 pub fn content_marker_count(&self) -> u32 {
1158 self.recording.content_markers()
1159 }
1160
1161 pub fn inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault)) {
1165 let inner = insets.inset_rect(self.size);
1166 let mut scope = Self::with_storage(
1167 Size {
1168 width: inner.width,
1169 height: inner.height,
1170 },
1171 self.text_measurer.clone(),
1172 CommandRecording::default(),
1173 );
1174 draw(&mut scope);
1175 let primitives = scope.into_primitives();
1176 self.push_recorded(
1177 primitives
1178 .into_iter()
1179 .map(|primitive| primitive.translate(inner.x, inner.y)),
1180 );
1181 }
1182
1183 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1185 for primitive in primitives {
1186 self.recording.push_primitive(primitive);
1187 }
1188 }
1189
1190 pub fn finish(self) -> CommandRecording {
1193 note_recorded_primitive_count(self.size, self.recording.len());
1194 self.recording.finish()
1195 }
1196
1197 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1198 if blend_mode != BlendMode::SrcOver {
1199 self.recording.push_other(DrawPrimitive::Blend {
1200 primitive: Box::new(primitive),
1201 blend_mode,
1202 });
1203 return;
1204 }
1205 self.recording.push_other(primitive);
1206 }
1207
1208 #[expect(clippy::too_many_arguments)]
1209 #[inline]
1210 fn push_arc(
1211 &mut self,
1212 brush: Brush,
1213 center: Point,
1214 radius: f32,
1215 start_angle: f32,
1216 sweep_angle: f32,
1217 stroke: Option<Stroke>,
1218 inner_radius: f32,
1219 blend_mode: BlendMode,
1220 ) {
1221 let args = ArcRecordArgs {
1222 brush: &brush,
1223 center,
1224 radius,
1225 start_angle,
1226 sweep_angle,
1227 stroke,
1228 inner_radius,
1229 blend_mode,
1230 };
1231 let geometry = normalized_band(&args);
1232 if geometry.is_degenerate() {
1233 return;
1234 }
1235 self.recording.push_scope_arc(&args, &geometry);
1236 }
1237}
1238
1239impl DrawScope for DrawScopeDefault {
1240 fn size(&self) -> Size {
1241 self.size
1242 }
1243
1244 fn draw_content(&mut self) {
1245 self.recording.push_content();
1246 }
1247
1248 fn draw_rect(&mut self, brush: Brush) {
1249 self.draw_rect_blend(brush, BlendMode::SrcOver);
1250 }
1251
1252 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1253 self.recording
1254 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1255 }
1256
1257 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1258 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1259 }
1260
1261 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1262 self.recording.push_rect(rect, &brush, None, blend_mode);
1263 }
1264
1265 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1266 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1267 }
1268
1269 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1270 self.recording
1271 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1272 }
1273
1274 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1275 self.recording
1276 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1277 }
1278
1279 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1280 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1281 }
1282
1283 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1284 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1285 }
1286
1287 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1288 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1289 }
1290
1291 fn draw_rect_at_stroked_blend(
1292 &mut self,
1293 rect: Rect,
1294 brush: Brush,
1295 stroke: Stroke,
1296 blend_mode: BlendMode,
1297 ) {
1298 if !stroke.is_visible() {
1299 return;
1300 }
1301 self.recording
1302 .push_rect(rect, &brush, Some(stroke), blend_mode);
1303 }
1304
1305 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1306 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1307 }
1308
1309 fn draw_round_rect_stroked_blend(
1310 &mut self,
1311 brush: Brush,
1312 radii: CornerRadii,
1313 stroke: Stroke,
1314 blend_mode: BlendMode,
1315 ) {
1316 self.draw_round_rect_at_stroked_blend(
1317 Rect::from_size(self.size),
1318 brush,
1319 radii,
1320 stroke,
1321 blend_mode,
1322 );
1323 }
1324
1325 fn draw_round_rect_at_stroked(
1326 &mut self,
1327 rect: Rect,
1328 brush: Brush,
1329 radii: CornerRadii,
1330 stroke: Stroke,
1331 ) {
1332 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1333 }
1334
1335 fn draw_round_rect_at_stroked_blend(
1336 &mut self,
1337 rect: Rect,
1338 brush: Brush,
1339 radii: CornerRadii,
1340 stroke: Stroke,
1341 blend_mode: BlendMode,
1342 ) {
1343 if !stroke.is_visible() {
1344 return;
1345 }
1346 self.recording
1347 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1348 }
1349
1350 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1351 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1352 }
1353
1354 fn draw_circle_stroked_blend(
1355 &mut self,
1356 brush: Brush,
1357 center: Point,
1358 radius: f32,
1359 stroke: Stroke,
1360 blend_mode: BlendMode,
1361 ) {
1362 if !stroke.is_visible() || !radius.is_finite() {
1363 return;
1364 }
1365 let radius = radius.max(0.0);
1366 let diameter = radius * 2.0;
1367 self.draw_round_rect_at_stroked_blend(
1368 Rect {
1369 x: center.x - radius,
1370 y: center.y - radius,
1371 width: diameter,
1372 height: diameter,
1373 },
1374 brush,
1375 CornerRadii::uniform(radius),
1376 stroke,
1377 blend_mode,
1378 );
1379 }
1380
1381 fn draw_arc(
1382 &mut self,
1383 brush: Brush,
1384 center: Point,
1385 radius: f32,
1386 start_angle: f32,
1387 sweep_angle: f32,
1388 stroke: Stroke,
1389 ) {
1390 self.draw_arc_blend(
1391 brush,
1392 center,
1393 radius,
1394 start_angle,
1395 sweep_angle,
1396 stroke,
1397 BlendMode::SrcOver,
1398 );
1399 }
1400
1401 fn draw_arc_blend(
1402 &mut self,
1403 brush: Brush,
1404 center: Point,
1405 radius: f32,
1406 start_angle: f32,
1407 sweep_angle: f32,
1408 stroke: Stroke,
1409 blend_mode: BlendMode,
1410 ) {
1411 if !stroke.is_visible() {
1412 return;
1413 }
1414 self.push_arc(
1415 brush,
1416 center,
1417 radius,
1418 start_angle,
1419 sweep_angle,
1420 Some(stroke),
1421 0.0,
1422 blend_mode,
1423 );
1424 }
1425
1426 fn draw_annular_sector(
1427 &mut self,
1428 brush: Brush,
1429 center: Point,
1430 inner_radius: f32,
1431 outer_radius: f32,
1432 start_angle: f32,
1433 sweep_angle: f32,
1434 ) {
1435 self.draw_annular_sector_blend(
1436 brush,
1437 center,
1438 inner_radius,
1439 outer_radius,
1440 start_angle,
1441 sweep_angle,
1442 BlendMode::SrcOver,
1443 );
1444 }
1445
1446 fn draw_annular_sector_blend(
1447 &mut self,
1448 brush: Brush,
1449 center: Point,
1450 inner_radius: f32,
1451 outer_radius: f32,
1452 start_angle: f32,
1453 sweep_angle: f32,
1454 blend_mode: BlendMode,
1455 ) {
1456 self.push_arc(
1457 brush,
1458 center,
1459 outer_radius,
1460 start_angle,
1461 sweep_angle,
1462 None,
1463 inner_radius,
1464 blend_mode,
1465 );
1466 }
1467
1468 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1469 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1470 }
1471
1472 fn draw_circle_blend(
1473 &mut self,
1474 brush: Brush,
1475 center: Point,
1476 radius: f32,
1477 blend_mode: BlendMode,
1478 ) {
1479 let radius = radius.max(0.0);
1480 let diameter = radius * 2.0;
1481 self.recording.push_round_rect(
1482 Rect {
1483 x: center.x - radius,
1484 y: center.y - radius,
1485 width: diameter,
1486 height: diameter,
1487 },
1488 &brush,
1489 CornerRadii::uniform(radius),
1490 None,
1491 blend_mode,
1492 );
1493 }
1494
1495 fn draw_image(&mut self, image: ImageBitmap) {
1496 self.draw_image_blend(image, BlendMode::SrcOver);
1497 }
1498
1499 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1500 self.push_blended_primitive(
1501 DrawPrimitive::Image {
1502 rect: Rect::from_size(self.size),
1503 image,
1504 alpha: 1.0,
1505 color_filter: None,
1506 sampling: ImageSampling::Nearest,
1507 src_rect: None,
1508 },
1509 blend_mode,
1510 );
1511 }
1512
1513 fn draw_image_at(
1514 &mut self,
1515 rect: Rect,
1516 image: ImageBitmap,
1517 alpha: f32,
1518 color_filter: Option<ColorFilter>,
1519 ) {
1520 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1521 }
1522
1523 fn draw_image_at_sampled(
1524 &mut self,
1525 rect: Rect,
1526 image: ImageBitmap,
1527 alpha: f32,
1528 color_filter: Option<ColorFilter>,
1529 sampling: ImageSampling,
1530 ) {
1531 self.push_blended_primitive(
1532 DrawPrimitive::Image {
1533 rect,
1534 image,
1535 alpha: alpha.clamp(0.0, 1.0),
1536 color_filter,
1537 sampling,
1538 src_rect: None,
1539 },
1540 BlendMode::SrcOver,
1541 );
1542 }
1543
1544 fn draw_image_at_blend(
1545 &mut self,
1546 rect: Rect,
1547 image: ImageBitmap,
1548 alpha: f32,
1549 color_filter: Option<ColorFilter>,
1550 blend_mode: BlendMode,
1551 ) {
1552 self.push_blended_primitive(
1553 DrawPrimitive::Image {
1554 rect,
1555 image,
1556 alpha: alpha.clamp(0.0, 1.0),
1557 color_filter,
1558 sampling: ImageSampling::Nearest,
1559 src_rect: None,
1560 },
1561 blend_mode,
1562 );
1563 }
1564
1565 fn draw_image_src(
1566 &mut self,
1567 image: ImageBitmap,
1568 src_rect: Rect,
1569 dst_rect: Rect,
1570 alpha: f32,
1571 color_filter: Option<ColorFilter>,
1572 ) {
1573 self.draw_image_src_blend(
1574 image,
1575 src_rect,
1576 dst_rect,
1577 alpha,
1578 color_filter,
1579 BlendMode::SrcOver,
1580 );
1581 }
1582
1583 fn draw_image_src_sampled(
1584 &mut self,
1585 image: ImageBitmap,
1586 src_rect: Rect,
1587 dst_rect: Rect,
1588 alpha: f32,
1589 color_filter: Option<ColorFilter>,
1590 sampling: ImageSampling,
1591 ) {
1592 self.push_blended_primitive(
1593 DrawPrimitive::Image {
1594 rect: dst_rect,
1595 image,
1596 alpha: alpha.clamp(0.0, 1.0),
1597 color_filter,
1598 sampling,
1599 src_rect: Some(src_rect),
1600 },
1601 BlendMode::SrcOver,
1602 );
1603 }
1604
1605 fn draw_image_src_blend(
1606 &mut self,
1607 image: ImageBitmap,
1608 src_rect: Rect,
1609 dst_rect: Rect,
1610 alpha: f32,
1611 color_filter: Option<ColorFilter>,
1612 blend_mode: BlendMode,
1613 ) {
1614 self.push_blended_primitive(
1615 DrawPrimitive::Image {
1616 rect: dst_rect,
1617 image,
1618 alpha: alpha.clamp(0.0, 1.0),
1619 color_filter,
1620 sampling: ImageSampling::Nearest,
1621 src_rect: Some(src_rect),
1622 },
1623 blend_mode,
1624 );
1625 }
1626
1627 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1628 const SUPERSAMPLE: f32 = 2.0;
1629 const MAX_MASK_PIXELS: f32 = 4096.0;
1630
1631 if path.is_empty() {
1632 return;
1633 }
1634 let bounds = path.bounds();
1635 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1636 return;
1637 }
1638
1639 let color = match &brush {
1640 Brush::Solid(color) => *color,
1641 Brush::LinearGradient { colors, .. }
1642 | Brush::RadialGradient { colors, .. }
1643 | Brush::SweepGradient { colors, .. } => match colors.first() {
1644 Some(color) => *color,
1645 None => return,
1646 },
1647 };
1648 if color.3 <= 0.0 {
1649 return;
1650 }
1651
1652 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1653 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1654 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1655 let mask_width = (rect_width * SUPERSAMPLE)
1656 .ceil()
1657 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1658 let mask_height = (rect_height * SUPERSAMPLE)
1659 .ceil()
1660 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1661
1662 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1663 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1664 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1665 let alpha = color.3.clamp(0.0, 1.0);
1666 let key = vector_path_mask_key(
1667 path,
1668 origin,
1669 (mask_width, mask_height),
1670 [red, green, blue],
1671 alpha,
1672 );
1673 let cached = vector_path_mask_cache_get(key);
1674 let image = match cached {
1675 Some(image) => image,
1676 None => {
1677 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1678 let mut pixels = Vec::with_capacity(mask.len() * 4);
1679 for coverage in mask {
1680 pixels.extend_from_slice(&[
1681 red,
1682 green,
1683 blue,
1684 (alpha * coverage as f32 + 0.5) as u8,
1685 ]);
1686 }
1687 let Ok(image) =
1688 ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
1689 else {
1690 return;
1691 };
1692 vector_path_mask_cache_put(key, image.clone());
1693 image
1694 }
1695 };
1696
1697 self.recording.push_other(DrawPrimitive::Image {
1698 rect: Rect {
1699 x: origin.x,
1700 y: origin.y,
1701 width: rect_width,
1702 height: rect_height,
1703 },
1704 image,
1705 alpha: 1.0,
1706 color_filter: None,
1707 sampling: ImageSampling::Linear,
1708 src_rect: None,
1709 });
1710 }
1711
1712 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1713 match &self.text_measurer {
1714 Some(measurer) => measurer.measure_text(text, style),
1715 None => estimate_text_measurement(text, style),
1716 }
1717 }
1718
1719 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1720 if text.is_empty() {
1721 return;
1722 }
1723 let Some(color) = solid_fill_color(&brush) else {
1724 return;
1725 };
1726 if color.3 <= 0.0 {
1727 return;
1728 }
1729 let measurement = self.measure_text(text, style);
1730 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1731 return;
1732 }
1733 let origin = align_text_block(rect, measurement, style);
1734 if !origin.x.is_finite() || !origin.y.is_finite() {
1735 return;
1736 }
1737 self.recording
1738 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1739 rect: Rect::from_origin_size(origin, measurement.size),
1740 text: shared_text_str(text),
1741 style: style.clone(),
1742 color,
1743 })));
1744 }
1745
1746 fn into_primitives(self) -> Vec<DrawPrimitive> {
1747 self.finish().into_primitives_with_markers()
1748 }
1749}
1750
1751fn solid_fill_color(brush: &Brush) -> Option<Color> {
1756 match brush {
1757 Brush::Solid(color) => Some(*color),
1758 Brush::LinearGradient { colors, .. }
1759 | Brush::RadialGradient { colors, .. }
1760 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1761 }
1762}
1763
1764#[cfg(test)]
1765#[path = "tests/geometry_tests.rs"]
1766mod tests;