1use std::{ops::AddAssign, rc::Rc};
4
5use crate::{
6 ArcRecordArgs, Brush, Color, ColorFilter, CommandRecorder, CommandRecording, ImageBitmap,
7 ImageSampling, normalized_band,
8 path::{DrawStyle, Path},
9 stroke::{LineGeometry, Stroke},
10 typography::{
11 DrawTextMeasurer, DrawTextStyle, TextAlign, TextMeasurement, TextVerticalAlign,
12 estimate_text_measurement,
13 },
14};
15
16const VECTOR_PATH_MASK_CACHE_ENTRIES: usize = 96;
17const VECTOR_PATH_MASK_CACHE_BYTES: usize = 8 * 1024 * 1024;
18
19struct VectorPathMaskCache {
20 entries: Vec<(u64, ImageBitmap)>,
21 bytes: usize,
22}
23
24impl VectorPathMaskCache {
25 const fn new() -> Self {
26 Self {
27 entries: Vec::new(),
28 bytes: 0,
29 }
30 }
31
32 fn get(&mut self, key: u64) -> Option<ImageBitmap> {
33 let index = self.entries.iter().position(|(seen, _)| *seen == key)?;
34 let entry = self.entries.remove(index);
35 let image = entry.1.clone();
36 self.entries.push(entry);
37 Some(image)
38 }
39
40 fn put(&mut self, key: u64, image: ImageBitmap) {
41 let bytes = image.pixels().len();
42 if bytes > VECTOR_PATH_MASK_CACHE_BYTES {
43 return;
44 }
45 self.bytes += bytes;
46 self.entries.push((key, image));
47 while self.entries.len() > VECTOR_PATH_MASK_CACHE_ENTRIES
48 || self.bytes > VECTOR_PATH_MASK_CACHE_BYTES
49 {
50 let (_, dropped) = self.entries.remove(0);
51 self.bytes = self.bytes.saturating_sub(dropped.pixels().len());
52 }
53 }
54}
55
56thread_local! {
57 static VECTOR_PATH_MASKS: std::cell::RefCell<VectorPathMaskCache> =
58 const { std::cell::RefCell::new(VectorPathMaskCache::new()) };
59}
60
61fn vector_path_mask_key(
62 path: &crate::VectorPath,
63 origin: Point,
64 mask_size: (usize, usize),
65 rgb: [u8; 3],
66 alpha: f32,
67) -> u64 {
68 use std::hash::Hasher;
69 let mut hasher = crate::fx_hash::FxHasher::default();
70 hasher.write_u8(path.fill_rule() as u8);
71 hasher.write_u32(origin.x.to_bits());
72 hasher.write_u32(origin.y.to_bits());
73 hasher.write_usize(mask_size.0);
74 hasher.write_usize(mask_size.1);
75 hasher.write(&rgb);
76 hasher.write_u32(alpha.to_bits());
77 for subpath in path.subpaths() {
78 hasher.write_usize(subpath.len());
79 for point in subpath {
80 hasher.write_u32(point.x.to_bits());
81 hasher.write_u32(point.y.to_bits());
82 }
83 }
84 hasher.finish()
85}
86
87fn vector_path_mask_cache_get(key: u64) -> Option<ImageBitmap> {
88 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().get(key))
89}
90
91fn vector_path_mask_cache_put(key: u64, image: ImageBitmap) {
92 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().put(key, image));
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Default)]
96pub struct Point {
97 pub x: f32,
98 pub y: f32,
99}
100
101impl Point {
102 pub const fn new(x: f32, y: f32) -> Self {
103 Self { x, y }
104 }
105
106 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Default)]
110pub struct Size {
111 pub width: f32,
112 pub height: f32,
113}
114
115impl Size {
116 pub const fn new(width: f32, height: f32) -> Self {
117 Self { width, height }
118 }
119
120 pub const ZERO: Size = Size {
121 width: 0.0,
122 height: 0.0,
123 };
124}
125
126#[derive(Clone, Copy, Debug, PartialEq)]
127pub struct Rect {
128 pub x: f32,
129 pub y: f32,
130 pub width: f32,
131 pub height: f32,
132}
133
134impl Rect {
135 pub const EMPTY: Rect = Rect {
139 x: 0.0,
140 y: 0.0,
141 width: 0.0,
142 height: 0.0,
143 };
144
145 pub fn from_origin_size(origin: Point, size: Size) -> Self {
146 Self {
147 x: origin.x,
148 y: origin.y,
149 width: size.width,
150 height: size.height,
151 }
152 }
153
154 pub fn from_size(size: Size) -> Self {
155 Self {
156 x: 0.0,
157 y: 0.0,
158 width: size.width,
159 height: size.height,
160 }
161 }
162
163 pub fn translate(&self, dx: f32, dy: f32) -> Self {
164 Self {
165 x: self.x + dx,
166 y: self.y + dy,
167 width: self.width,
168 height: self.height,
169 }
170 }
171
172 pub fn contains(&self, x: f32, y: f32) -> bool {
173 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
174 }
175
176 pub fn is_empty(&self) -> bool {
178 self.width <= 0.0 || self.height <= 0.0
179 }
180
181 pub fn intersect(&self, other: Rect) -> Option<Rect> {
183 let left = self.x.max(other.x);
184 let top = self.y.max(other.y);
185 let right = (self.x + self.width).min(other.x + other.width);
186 let bottom = (self.y + self.height).min(other.y + other.height);
187 let width = right - left;
188 let height = bottom - top;
189 if width <= 0.0 || height <= 0.0 {
190 None
191 } else {
192 Some(Rect {
193 x: left,
194 y: top,
195 width,
196 height,
197 })
198 }
199 }
200
201 pub fn union(&self, other: Rect) -> Rect {
202 let left = self.x.min(other.x);
203 let top = self.y.min(other.y);
204 let right = (self.x + self.width).max(other.x + other.width);
205 let bottom = (self.y + self.height).max(other.y + other.height);
206 Rect {
207 x: left,
208 y: top,
209 width: (right - left).max(0.0),
210 height: (bottom - top).max(0.0),
211 }
212 }
213}
214
215#[derive(Clone, Copy, Debug, Default, PartialEq)]
217pub struct EdgeInsets {
218 pub left: f32,
219 pub top: f32,
220 pub right: f32,
221 pub bottom: f32,
222}
223
224impl EdgeInsets {
225 pub fn uniform(all: f32) -> Self {
226 Self {
227 left: all,
228 top: all,
229 right: all,
230 bottom: all,
231 }
232 }
233
234 pub fn horizontal(horizontal: f32) -> Self {
235 Self {
236 left: horizontal,
237 right: horizontal,
238 ..Self::default()
239 }
240 }
241
242 pub fn vertical(vertical: f32) -> Self {
243 Self {
244 top: vertical,
245 bottom: vertical,
246 ..Self::default()
247 }
248 }
249
250 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
251 Self {
252 left: horizontal,
253 right: horizontal,
254 top: vertical,
255 bottom: vertical,
256 }
257 }
258
259 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
260 Self {
261 left,
262 top,
263 right,
264 bottom,
265 }
266 }
267
268 pub fn is_zero(&self) -> bool {
269 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
270 }
271
272 pub fn horizontal_sum(&self) -> f32 {
273 self.left + self.right
274 }
275
276 pub fn vertical_sum(&self) -> f32 {
277 self.top + self.bottom
278 }
279
280 pub fn inset_rect(&self, size: Size) -> Rect {
283 Rect {
284 x: self.left,
285 y: self.top,
286 width: (size.width - self.horizontal_sum()).max(0.0),
287 height: (size.height - self.vertical_sum()).max(0.0),
288 }
289 }
290}
291
292impl AddAssign for EdgeInsets {
293 fn add_assign(&mut self, rhs: Self) {
294 self.left += rhs.left;
295 self.top += rhs.top;
296 self.right += rhs.right;
297 self.bottom += rhs.bottom;
298 }
299}
300
301#[derive(Clone, Copy, Debug, Default, PartialEq)]
302pub struct CornerRadii {
303 pub top_left: f32,
304 pub top_right: f32,
305 pub bottom_right: f32,
306 pub bottom_left: f32,
307}
308
309impl CornerRadii {
310 pub fn uniform(radius: f32) -> Self {
311 Self {
312 top_left: radius,
313 top_right: radius,
314 bottom_right: radius,
315 bottom_left: radius,
316 }
317 }
318}
319
320#[derive(Clone, Copy, Debug, PartialEq)]
321pub struct RoundedCornerShape {
322 radii: CornerRadii,
323}
324
325impl RoundedCornerShape {
326 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
327 Self {
328 radii: CornerRadii {
329 top_left,
330 top_right,
331 bottom_right,
332 bottom_left,
333 },
334 }
335 }
336
337 pub fn uniform(radius: f32) -> Self {
338 Self {
339 radii: CornerRadii::uniform(radius),
340 }
341 }
342
343 pub fn with_radii(radii: CornerRadii) -> Self {
344 Self { radii }
345 }
346
347 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
348 let mut resolved = self.radii;
349 let max_width = (width / 2.0).max(0.0);
350 let max_height = (height / 2.0).max(0.0);
351 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
352 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
353 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
354 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
355 resolved
356 }
357
358 pub fn radii(&self) -> CornerRadii {
359 self.radii
360 }
361}
362
363#[derive(Clone, Copy, Debug, PartialEq)]
364pub struct TransformOrigin {
365 pub pivot_fraction_x: f32,
366 pub pivot_fraction_y: f32,
367}
368
369impl TransformOrigin {
370 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
371 Self {
372 pivot_fraction_x,
373 pivot_fraction_y,
374 }
375 }
376
377 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
378}
379
380impl Default for TransformOrigin {
381 fn default() -> Self {
382 Self::CENTER
383 }
384}
385
386#[derive(Clone, Copy, Debug, Default, PartialEq)]
387pub enum LayerShape {
388 #[default]
389 Rectangle,
390 Rounded(RoundedCornerShape),
391}
392
393#[derive(Clone, Debug, PartialEq)]
394pub struct GraphicsLayer {
395 pub alpha: f32,
396 pub scale: f32,
397 pub scale_x: f32,
398 pub scale_y: f32,
399 pub rotation_x: f32,
400 pub rotation_y: f32,
401 pub rotation_z: f32,
402 pub camera_distance: f32,
403 pub transform_origin: TransformOrigin,
404 pub translation_x: f32,
405 pub translation_y: f32,
406 pub shadow_elevation: f32,
407 pub ambient_shadow_color: Color,
408 pub spot_shadow_color: Color,
409 pub shape: LayerShape,
410 pub clip: bool,
411 pub compositing_strategy: CompositingStrategy,
412 pub blend_mode: BlendMode,
413 pub color_filter: Option<ColorFilter>,
414 pub render_effect: Option<crate::render_effect::RenderEffect>,
415 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
416}
417
418impl GraphicsLayer {
419 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
443 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
444 }
445}
446
447impl Default for GraphicsLayer {
448 fn default() -> Self {
449 Self {
450 alpha: 1.0,
451 scale: 1.0,
452 scale_x: 1.0,
453 scale_y: 1.0,
454 rotation_x: 0.0,
455 rotation_y: 0.0,
456 rotation_z: 0.0,
457 camera_distance: 8.0,
458 transform_origin: TransformOrigin::CENTER,
459 translation_x: 0.0,
460 translation_y: 0.0,
461 shadow_elevation: 0.0,
462 ambient_shadow_color: Color::BLACK,
463 spot_shadow_color: Color::BLACK,
464 shape: LayerShape::Rectangle,
465 clip: false,
466 compositing_strategy: CompositingStrategy::Auto,
467 blend_mode: BlendMode::SrcOver,
468 color_filter: None,
469 render_effect: None,
470 backdrop_effect: None,
471 }
472 }
473}
474
475#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
480pub enum BlendMode {
481 Clear,
482 Src,
483 Dst,
484 #[default]
485 SrcOver,
486 DstOver,
487 SrcIn,
488 DstIn,
489 SrcOut,
490 DstOut,
491 SrcAtop,
492 DstAtop,
493 Xor,
494 Plus,
495 Modulate,
496 Screen,
497 Overlay,
498 Darken,
499 Lighten,
500 ColorDodge,
501 ColorBurn,
502 HardLight,
503 SoftLight,
504 Difference,
505 Exclusion,
506 Multiply,
507 Hue,
508 Saturation,
509 Color,
510 Luminosity,
511}
512
513impl BlendMode {
515 pub const ALL: [BlendMode; 29] = [
517 BlendMode::Clear,
518 BlendMode::Src,
519 BlendMode::Dst,
520 BlendMode::SrcOver,
521 BlendMode::DstOver,
522 BlendMode::SrcIn,
523 BlendMode::DstIn,
524 BlendMode::SrcOut,
525 BlendMode::DstOut,
526 BlendMode::SrcAtop,
527 BlendMode::DstAtop,
528 BlendMode::Xor,
529 BlendMode::Plus,
530 BlendMode::Modulate,
531 BlendMode::Screen,
532 BlendMode::Overlay,
533 BlendMode::Darken,
534 BlendMode::Lighten,
535 BlendMode::ColorDodge,
536 BlendMode::ColorBurn,
537 BlendMode::HardLight,
538 BlendMode::SoftLight,
539 BlendMode::Difference,
540 BlendMode::Exclusion,
541 BlendMode::Multiply,
542 BlendMode::Hue,
543 BlendMode::Saturation,
544 BlendMode::Color,
545 BlendMode::Luminosity,
546 ];
547}
548
549#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
550pub enum CompositingStrategy {
551 #[default]
553 Auto,
554 Offscreen,
556 ModulateAlpha,
558}
559
560#[derive(Clone, Debug, PartialEq)]
561pub enum DrawPrimitive {
562 Content,
565 Blend {
567 primitive: Box<DrawPrimitive>,
568 blend_mode: BlendMode,
569 },
570 Rect {
571 rect: Rect,
572 brush: Brush,
573 stroke: Option<Stroke>,
576 },
577 RoundRect {
578 rect: Rect,
579 brush: Brush,
580 radii: CornerRadii,
581 stroke: Option<Stroke>,
584 },
585 Arc {
595 rect: Rect,
599 brush: Brush,
600 center: Point,
601 radius: f32,
602 start_angle: f32,
603 sweep_angle: f32,
604 stroke: Option<Stroke>,
605 inner_radius: f32,
607 },
608 Line {
611 rect: Rect,
614 brush: Brush,
615 start: Point,
616 end: Point,
617 stroke: Stroke,
618 },
619 Image {
620 rect: Rect,
621 image: ImageBitmap,
622 alpha: f32,
623 color_filter: Option<ColorFilter>,
624 sampling: ImageSampling,
625 src_rect: Option<Rect>,
629 },
630 Text(Box<TextPrimitive>),
632 Shadow(ShadowPrimitive),
635}
636
637#[derive(Clone, Debug, PartialEq)]
646pub struct TextPrimitive {
647 pub rect: Rect,
650 pub text: std::rc::Rc<str>,
653 pub style: DrawTextStyle,
654 pub color: Color,
658}
659
660fn shared_text_str(text: &str) -> Rc<str> {
670 use std::{
671 cell::RefCell,
672 collections::HashMap,
673 hash::{Hash, Hasher},
674 };
675
676 const POOL_CAPACITY: usize = 256;
677 thread_local! {
678 static POOL: RefCell<HashMap<u64, Rc<str>, crate::FxBuildHasher>> =
679 RefCell::new(HashMap::default());
680 }
681
682 let mut hasher = crate::FxHasher::default();
683 text.hash(&mut hasher);
684 let key = hasher.finish();
685
686 POOL.with(|pool| {
687 let mut pool = pool.borrow_mut();
688 if let Some(shared) = pool.get(&key)
689 && &**shared == text
690 {
691 return Rc::clone(shared);
692 }
693 let shared: Rc<str> = Rc::from(text);
694 if pool.len() >= POOL_CAPACITY {
695 pool.clear();
696 }
697 pool.insert(key, Rc::clone(&shared));
698 shared
699 })
700}
701
702impl DrawPrimitive {
704 pub fn translate(self, dx: f32, dy: f32) -> Self {
707 match self {
708 DrawPrimitive::Content => DrawPrimitive::Content,
709 DrawPrimitive::Blend {
710 primitive,
711 blend_mode,
712 } => DrawPrimitive::Blend {
713 primitive: Box::new(primitive.translate(dx, dy)),
714 blend_mode,
715 },
716 DrawPrimitive::Rect {
717 rect,
718 brush,
719 stroke,
720 } => DrawPrimitive::Rect {
721 rect: rect.translate(dx, dy),
722 brush,
723 stroke,
724 },
725 DrawPrimitive::RoundRect {
726 rect,
727 brush,
728 radii,
729 stroke,
730 } => DrawPrimitive::RoundRect {
731 rect: rect.translate(dx, dy),
732 brush,
733 radii,
734 stroke,
735 },
736 DrawPrimitive::Arc {
737 rect,
738 brush,
739 center,
740 radius,
741 start_angle,
742 sweep_angle,
743 stroke,
744 inner_radius,
745 } => DrawPrimitive::Arc {
746 rect: rect.translate(dx, dy),
747 brush,
748 center: Point::new(center.x + dx, center.y + dy),
749 radius,
750 start_angle,
751 sweep_angle,
752 stroke,
753 inner_radius,
754 },
755 DrawPrimitive::Line {
756 rect,
757 brush,
758 start,
759 end,
760 stroke,
761 } => DrawPrimitive::Line {
762 rect: rect.translate(dx, dy),
763 brush,
764 start: Point::new(start.x + dx, start.y + dy),
765 end: Point::new(end.x + dx, end.y + dy),
766 stroke,
767 },
768 DrawPrimitive::Image {
769 rect,
770 image,
771 alpha,
772 color_filter,
773 sampling,
774 src_rect,
775 } => DrawPrimitive::Image {
776 rect: rect.translate(dx, dy),
777 image,
778 alpha,
779 color_filter,
780 sampling,
781 src_rect,
782 },
783 DrawPrimitive::Text(mut text) => {
784 text.rect = text.rect.translate(dx, dy);
785 DrawPrimitive::Text(text)
786 }
787 DrawPrimitive::Shadow(ShadowPrimitive::Drop {
788 shape,
789 cutout,
790 blur_radius,
791 blend_mode,
792 }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
793 shape: Box::new(shape.translate(dx, dy)),
794 cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
795 blur_radius,
796 blend_mode,
797 }),
798 DrawPrimitive::Shadow(ShadowPrimitive::Inner {
799 fill,
800 cutout,
801 blur_radius,
802 blend_mode,
803 clip_rect,
804 }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
805 fill: Box::new(fill.translate(dx, dy)),
806 cutout: Box::new(cutout.translate(dx, dy)),
807 blur_radius,
808 blend_mode,
809 clip_rect: clip_rect.translate(dx, dy),
810 }),
811 }
812 }
813}
814
815#[derive(Clone, Debug, PartialEq)]
816pub enum ShadowPrimitive {
817 Drop {
821 shape: Box<DrawPrimitive>,
822 cutout: Option<Box<DrawPrimitive>>,
823 blur_radius: f32,
824 blend_mode: BlendMode,
825 },
826 Inner {
828 fill: Box<DrawPrimitive>,
829 cutout: Box<DrawPrimitive>,
830 blur_radius: f32,
831 blend_mode: BlendMode,
832 clip_rect: Rect,
834 },
835}
836
837pub trait DrawScope {
838 fn size(&self) -> Size;
839 fn draw_content(&mut self);
840 fn draw_rect(&mut self, brush: Brush);
841 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
842 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
844 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
845 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
846 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
847 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
849 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
850 fn draw_circle_blend(
851 &mut self,
852 brush: Brush,
853 center: Point,
854 radius: f32,
855 blend_mode: BlendMode,
856 );
857
858 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
860 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
861 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
863 fn draw_rect_at_stroked_blend(
864 &mut self,
865 rect: Rect,
866 brush: Brush,
867 stroke: Stroke,
868 blend_mode: BlendMode,
869 );
870 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
872 fn draw_round_rect_stroked_blend(
873 &mut self,
874 brush: Brush,
875 radii: CornerRadii,
876 stroke: Stroke,
877 blend_mode: BlendMode,
878 );
879 fn draw_round_rect_at_stroked(
881 &mut self,
882 rect: Rect,
883 brush: Brush,
884 radii: CornerRadii,
885 stroke: Stroke,
886 );
887 fn draw_round_rect_at_stroked_blend(
888 &mut self,
889 rect: Rect,
890 brush: Brush,
891 radii: CornerRadii,
892 stroke: Stroke,
893 blend_mode: BlendMode,
894 );
895 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
898 fn draw_circle_stroked_blend(
899 &mut self,
900 brush: Brush,
901 center: Point,
902 radius: f32,
903 stroke: Stroke,
904 blend_mode: BlendMode,
905 );
906
907 fn draw_arc(
920 &mut self,
921 brush: Brush,
922 center: Point,
923 radius: f32,
924 start_angle: f32,
925 sweep_angle: f32,
926 stroke: Stroke,
927 );
928 #[expect(clippy::too_many_arguments)]
929 fn draw_arc_blend(
930 &mut self,
931 brush: Brush,
932 center: Point,
933 radius: f32,
934 start_angle: f32,
935 sweep_angle: f32,
936 stroke: Stroke,
937 blend_mode: BlendMode,
938 );
939
940 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke);
946 fn draw_line_blend(
947 &mut self,
948 brush: Brush,
949 start: Point,
950 end: Point,
951 stroke: Stroke,
952 blend_mode: BlendMode,
953 );
954
955 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle);
964 fn draw_path_blend(
965 &mut self,
966 path: &Path,
967 brush: Brush,
968 style: DrawStyle,
969 blend_mode: BlendMode,
970 );
971
972 fn draw_annular_sector(
981 &mut self,
982 brush: Brush,
983 center: Point,
984 inner_radius: f32,
985 outer_radius: f32,
986 start_angle: f32,
987 sweep_angle: f32,
988 );
989 #[expect(clippy::too_many_arguments)]
990 fn draw_annular_sector_blend(
991 &mut self,
992 brush: Brush,
993 center: Point,
994 inner_radius: f32,
995 outer_radius: f32,
996 start_angle: f32,
997 sweep_angle: f32,
998 blend_mode: BlendMode,
999 );
1000
1001 fn draw_image(&mut self, image: ImageBitmap);
1002 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
1003 fn draw_image_at(
1004 &mut self,
1005 rect: Rect,
1006 image: ImageBitmap,
1007 alpha: f32,
1008 color_filter: Option<ColorFilter>,
1009 );
1010 fn draw_image_at_sampled(
1011 &mut self,
1012 rect: Rect,
1013 image: ImageBitmap,
1014 alpha: f32,
1015 color_filter: Option<ColorFilter>,
1016 sampling: ImageSampling,
1017 );
1018 fn draw_image_at_blend(
1019 &mut self,
1020 rect: Rect,
1021 image: ImageBitmap,
1022 alpha: f32,
1023 color_filter: Option<ColorFilter>,
1024 blend_mode: BlendMode,
1025 );
1026 fn draw_image_src(
1029 &mut self,
1030 image: ImageBitmap,
1031 src_rect: Rect,
1032 dst_rect: Rect,
1033 alpha: f32,
1034 color_filter: Option<ColorFilter>,
1035 );
1036 fn draw_image_src_sampled(
1037 &mut self,
1038 image: ImageBitmap,
1039 src_rect: Rect,
1040 dst_rect: Rect,
1041 alpha: f32,
1042 color_filter: Option<ColorFilter>,
1043 sampling: ImageSampling,
1044 );
1045 fn draw_image_src_blend(
1046 &mut self,
1047 image: ImageBitmap,
1048 src_rect: Rect,
1049 dst_rect: Rect,
1050 alpha: f32,
1051 color_filter: Option<ColorFilter>,
1052 blend_mode: BlendMode,
1053 );
1054 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1063 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1069 if let Ok(path) = crate::VectorPath::parse(d) {
1070 self.draw_vector_path(&path, brush);
1071 }
1072 }
1073
1074 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1081
1082 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1085 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1086 }
1087
1088 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1095
1096 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1102 if text.is_empty() {
1103 return;
1104 }
1105 let measurement = self.measure_text(text, style);
1106 self.draw_text_at(
1107 Rect::from_origin_size(top_left, measurement.size),
1108 brush,
1109 text,
1110 &DrawTextStyle {
1111 align: TextAlign::Left,
1112 vertical_align: TextVerticalAlign::Top,
1113 ..style.clone()
1114 },
1115 );
1116 }
1117
1118 fn into_primitives(self) -> Vec<DrawPrimitive>;
1119}
1120
1121pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1127 let x = match style.align {
1128 TextAlign::Left => rect.x,
1129 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1130 TextAlign::Right => rect.x + rect.width - measurement.size.width,
1131 };
1132 let y = match style.vertical_align {
1133 TextVerticalAlign::Top => rect.y,
1134 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1135 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1136 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1137 };
1138 Point::new(x, y)
1139}
1140
1141#[derive(Default)]
1142pub struct DrawScopeDefault {
1143 size: Size,
1144 recording: CommandRecorder,
1145 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1146}
1147
1148const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1149
1150thread_local! {
1151 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<
1152 std::collections::HashMap<(u32, u32), usize, crate::FxBuildHasher>,
1153 > = std::cell::RefCell::new(std::collections::HashMap::default());
1154}
1155
1156fn recorded_primitive_capacity(size: Size) -> usize {
1157 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1158 counts
1159 .borrow()
1160 .get(&(size.width.to_bits(), size.height.to_bits()))
1161 .copied()
1162 .unwrap_or(0)
1163 })
1164}
1165
1166fn note_recorded_primitive_count(size: Size, count: usize) {
1167 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1168 let mut counts = counts.borrow_mut();
1169 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1170 counts.clear();
1171 }
1172 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1173 });
1174}
1175
1176impl DrawScopeDefault {
1177 pub fn new(size: Size) -> Self {
1178 Self::with_storage(size, None, CommandRecording::default())
1179 }
1180
1181 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1186 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1187 }
1188
1189 pub fn with_text_measurer_reusing(
1192 size: Size,
1193 text_measurer: Rc<dyn DrawTextMeasurer>,
1194 storage: CommandRecording,
1195 ) -> Self {
1196 Self::with_storage(size, Some(text_measurer), storage)
1197 }
1198
1199 fn with_storage(
1200 size: Size,
1201 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1202 recording: CommandRecording,
1203 ) -> Self {
1204 let mut recording = CommandRecorder::reusing(recording);
1205 recording.reserve_shapes(recorded_primitive_capacity(size));
1206 Self {
1207 size,
1208 recording,
1209 text_measurer,
1210 }
1211 }
1212
1213 pub fn content_marker_count(&self) -> u32 {
1215 self.recording.content_markers()
1216 }
1217
1218 pub fn inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault)) {
1222 let inner = insets.inset_rect(self.size);
1223 let mut scope = Self::with_storage(
1224 Size {
1225 width: inner.width,
1226 height: inner.height,
1227 },
1228 self.text_measurer.clone(),
1229 CommandRecording::default(),
1230 );
1231 draw(&mut scope);
1232 let primitives = scope.into_primitives();
1233 self.push_recorded(
1234 primitives
1235 .into_iter()
1236 .map(|primitive| primitive.translate(inner.x, inner.y)),
1237 );
1238 }
1239
1240 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1242 for primitive in primitives {
1243 self.recording.push_primitive(primitive);
1244 }
1245 }
1246
1247 pub fn finish(self) -> CommandRecording {
1250 note_recorded_primitive_count(self.size, self.recording.len());
1251 self.recording.finish()
1252 }
1253
1254 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1255 if blend_mode != BlendMode::SrcOver {
1256 self.recording.push_other(DrawPrimitive::Blend {
1257 primitive: Box::new(primitive),
1258 blend_mode,
1259 });
1260 return;
1261 }
1262 self.recording.push_other(primitive);
1263 }
1264
1265 #[expect(clippy::too_many_arguments)]
1266 #[inline]
1267 fn push_arc(
1268 &mut self,
1269 brush: Brush,
1270 center: Point,
1271 radius: f32,
1272 start_angle: f32,
1273 sweep_angle: f32,
1274 stroke: Option<Stroke>,
1275 inner_radius: f32,
1276 blend_mode: BlendMode,
1277 ) {
1278 let args = ArcRecordArgs {
1279 brush: &brush,
1280 center,
1281 radius,
1282 start_angle,
1283 sweep_angle,
1284 stroke,
1285 inner_radius,
1286 blend_mode,
1287 };
1288 let geometry = normalized_band(&args);
1289 if geometry.is_degenerate() {
1290 return;
1291 }
1292 self.recording.push_scope_arc(&args, &geometry);
1293 }
1294
1295 fn push_vector_path(&mut self, path: &crate::VectorPath, brush: &Brush, blend_mode: BlendMode) {
1298 const SUPERSAMPLE: f32 = 2.0;
1299 const MAX_MASK_PIXELS: f32 = 4096.0;
1300
1301 if path.is_empty() {
1302 return;
1303 }
1304 let bounds = path.bounds();
1305 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1306 return;
1307 }
1308
1309 let color = match brush {
1310 Brush::Solid(color) => *color,
1311 Brush::LinearGradient { colors, .. }
1312 | Brush::RadialGradient { colors, .. }
1313 | Brush::SweepGradient { colors, .. } => match colors.first() {
1314 Some(color) => *color,
1315 None => return,
1316 },
1317 };
1318 if color.3 <= 0.0 {
1319 return;
1320 }
1321
1322 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1323 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1324 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1325 let mask_width = (rect_width * SUPERSAMPLE)
1326 .ceil()
1327 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1328 let mask_height = (rect_height * SUPERSAMPLE)
1329 .ceil()
1330 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1331
1332 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1333 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1334 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1335 let alpha = color.3.clamp(0.0, 1.0);
1336 let key = vector_path_mask_key(
1337 path,
1338 origin,
1339 (mask_width, mask_height),
1340 [red, green, blue],
1341 alpha,
1342 );
1343 let cached = vector_path_mask_cache_get(key);
1344 let image = match cached {
1345 Some(image) => image,
1346 None => {
1347 let mut mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1348 for coverage in &mut mask {
1349 *coverage = (alpha * *coverage as f32 + 0.5) as u8;
1350 }
1351 let Ok(image) = ImageBitmap::from_alpha8(
1352 mask_width as u32,
1353 mask_height as u32,
1354 [red, green, blue],
1355 mask,
1356 ) else {
1357 return;
1358 };
1359 vector_path_mask_cache_put(key, image.clone());
1360 image
1361 }
1362 };
1363
1364 let image = DrawPrimitive::Image {
1365 rect: Rect {
1366 x: origin.x,
1367 y: origin.y,
1368 width: rect_width,
1369 height: rect_height,
1370 },
1371 image,
1372 alpha: 1.0,
1373 color_filter: None,
1374 sampling: ImageSampling::Linear,
1375 src_rect: None,
1376 };
1377 self.recording
1378 .push_other(if blend_mode == BlendMode::SrcOver {
1379 image
1380 } else {
1381 DrawPrimitive::Blend {
1382 primitive: Box::new(image),
1383 blend_mode,
1384 }
1385 });
1386 }
1387}
1388
1389impl DrawScope for DrawScopeDefault {
1390 fn size(&self) -> Size {
1391 self.size
1392 }
1393
1394 fn draw_content(&mut self) {
1395 self.recording.push_content();
1396 }
1397
1398 fn draw_rect(&mut self, brush: Brush) {
1399 self.draw_rect_blend(brush, BlendMode::SrcOver);
1400 }
1401
1402 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1403 self.recording
1404 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1405 }
1406
1407 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1408 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1409 }
1410
1411 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1412 self.recording.push_rect(rect, &brush, None, blend_mode);
1413 }
1414
1415 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1416 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1417 }
1418
1419 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1420 self.recording
1421 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1422 }
1423
1424 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1425 self.recording
1426 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1427 }
1428
1429 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1430 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1431 }
1432
1433 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1434 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1435 }
1436
1437 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1438 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1439 }
1440
1441 fn draw_rect_at_stroked_blend(
1442 &mut self,
1443 rect: Rect,
1444 brush: Brush,
1445 stroke: Stroke,
1446 blend_mode: BlendMode,
1447 ) {
1448 if !stroke.is_visible() {
1449 return;
1450 }
1451 self.recording
1452 .push_rect(rect, &brush, Some(stroke), blend_mode);
1453 }
1454
1455 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1456 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1457 }
1458
1459 fn draw_round_rect_stroked_blend(
1460 &mut self,
1461 brush: Brush,
1462 radii: CornerRadii,
1463 stroke: Stroke,
1464 blend_mode: BlendMode,
1465 ) {
1466 self.draw_round_rect_at_stroked_blend(
1467 Rect::from_size(self.size),
1468 brush,
1469 radii,
1470 stroke,
1471 blend_mode,
1472 );
1473 }
1474
1475 fn draw_round_rect_at_stroked(
1476 &mut self,
1477 rect: Rect,
1478 brush: Brush,
1479 radii: CornerRadii,
1480 stroke: Stroke,
1481 ) {
1482 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1483 }
1484
1485 fn draw_round_rect_at_stroked_blend(
1486 &mut self,
1487 rect: Rect,
1488 brush: Brush,
1489 radii: CornerRadii,
1490 stroke: Stroke,
1491 blend_mode: BlendMode,
1492 ) {
1493 if !stroke.is_visible() {
1494 return;
1495 }
1496 self.recording
1497 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1498 }
1499
1500 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1501 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1502 }
1503
1504 fn draw_circle_stroked_blend(
1505 &mut self,
1506 brush: Brush,
1507 center: Point,
1508 radius: f32,
1509 stroke: Stroke,
1510 blend_mode: BlendMode,
1511 ) {
1512 if !stroke.is_visible() || !radius.is_finite() {
1513 return;
1514 }
1515 let radius = radius.max(0.0);
1516 let diameter = radius * 2.0;
1517 self.draw_round_rect_at_stroked_blend(
1518 Rect {
1519 x: center.x - radius,
1520 y: center.y - radius,
1521 width: diameter,
1522 height: diameter,
1523 },
1524 brush,
1525 CornerRadii::uniform(radius),
1526 stroke,
1527 blend_mode,
1528 );
1529 }
1530
1531 fn draw_arc(
1532 &mut self,
1533 brush: Brush,
1534 center: Point,
1535 radius: f32,
1536 start_angle: f32,
1537 sweep_angle: f32,
1538 stroke: Stroke,
1539 ) {
1540 self.draw_arc_blend(
1541 brush,
1542 center,
1543 radius,
1544 start_angle,
1545 sweep_angle,
1546 stroke,
1547 BlendMode::SrcOver,
1548 );
1549 }
1550
1551 fn draw_arc_blend(
1552 &mut self,
1553 brush: Brush,
1554 center: Point,
1555 radius: f32,
1556 start_angle: f32,
1557 sweep_angle: f32,
1558 stroke: Stroke,
1559 blend_mode: BlendMode,
1560 ) {
1561 if !stroke.is_visible() {
1562 return;
1563 }
1564 self.push_arc(
1565 brush,
1566 center,
1567 radius,
1568 start_angle,
1569 sweep_angle,
1570 Some(stroke),
1571 0.0,
1572 blend_mode,
1573 );
1574 }
1575
1576 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke) {
1577 self.draw_line_blend(brush, start, end, stroke, BlendMode::SrcOver);
1578 }
1579
1580 fn draw_line_blend(
1581 &mut self,
1582 brush: Brush,
1583 start: Point,
1584 end: Point,
1585 stroke: Stroke,
1586 blend_mode: BlendMode,
1587 ) {
1588 let line = LineGeometry::new(start, end, stroke);
1589 if line.is_degenerate() {
1590 return;
1591 }
1592 self.recording.push_line(&line, &brush, stroke, blend_mode);
1593 }
1594
1595 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle) {
1596 self.draw_path_blend(path, brush, style, BlendMode::SrcOver);
1597 }
1598
1599 fn draw_path_blend(
1600 &mut self,
1601 path: &Path,
1602 brush: Brush,
1603 style: DrawStyle,
1604 blend_mode: BlendMode,
1605 ) {
1606 let (stroke, dash) = match style {
1607 DrawStyle::Fill => {
1608 self.push_vector_path(
1609 &path.to_vector_path(crate::PathFillRule::NonZero),
1610 &brush,
1611 blend_mode,
1612 );
1613 return;
1614 }
1615 DrawStyle::Stroke(stroke) => (stroke, None),
1616 DrawStyle::DashedStroke(stroke, dash) => (stroke, Some(dash)),
1617 };
1618 if !stroke.is_visible() {
1619 return;
1620 }
1621 let recording = &mut self.recording;
1622 let mut push = |line: LineGeometry| {
1623 if !line.is_degenerate() {
1624 let stroke = Stroke {
1625 cap: line.cap,
1626 ..stroke
1627 };
1628 recording.push_line(&line, &brush, stroke, blend_mode);
1629 }
1630 };
1631 for contour in path.contours() {
1632 let Some(dash) = dash else {
1633 crate::for_each_stroke_line(&contour.points, contour.closed, stroke, &mut push);
1634 continue;
1635 };
1636 let closing = contour
1637 .closed
1638 .then(|| contour.points.first().copied())
1639 .flatten();
1640 let points: std::borrow::Cow<'_, [Point]> = match closing {
1641 Some(first) => {
1642 std::borrow::Cow::Owned(contour.points.iter().copied().chain([first]).collect())
1643 }
1644 None => std::borrow::Cow::Borrowed(&contour.points),
1645 };
1646 dash.for_each_dash(&points, |piece| {
1647 crate::for_each_stroke_line(piece, false, stroke, &mut push);
1648 });
1649 }
1650 }
1651
1652 fn draw_annular_sector(
1653 &mut self,
1654 brush: Brush,
1655 center: Point,
1656 inner_radius: f32,
1657 outer_radius: f32,
1658 start_angle: f32,
1659 sweep_angle: f32,
1660 ) {
1661 self.draw_annular_sector_blend(
1662 brush,
1663 center,
1664 inner_radius,
1665 outer_radius,
1666 start_angle,
1667 sweep_angle,
1668 BlendMode::SrcOver,
1669 );
1670 }
1671
1672 fn draw_annular_sector_blend(
1673 &mut self,
1674 brush: Brush,
1675 center: Point,
1676 inner_radius: f32,
1677 outer_radius: f32,
1678 start_angle: f32,
1679 sweep_angle: f32,
1680 blend_mode: BlendMode,
1681 ) {
1682 self.push_arc(
1683 brush,
1684 center,
1685 outer_radius,
1686 start_angle,
1687 sweep_angle,
1688 None,
1689 inner_radius,
1690 blend_mode,
1691 );
1692 }
1693
1694 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1695 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1696 }
1697
1698 fn draw_circle_blend(
1699 &mut self,
1700 brush: Brush,
1701 center: Point,
1702 radius: f32,
1703 blend_mode: BlendMode,
1704 ) {
1705 let radius = radius.max(0.0);
1706 let diameter = radius * 2.0;
1707 self.recording.push_round_rect(
1708 Rect {
1709 x: center.x - radius,
1710 y: center.y - radius,
1711 width: diameter,
1712 height: diameter,
1713 },
1714 &brush,
1715 CornerRadii::uniform(radius),
1716 None,
1717 blend_mode,
1718 );
1719 }
1720
1721 fn draw_image(&mut self, image: ImageBitmap) {
1722 self.draw_image_blend(image, BlendMode::SrcOver);
1723 }
1724
1725 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1726 self.push_blended_primitive(
1727 DrawPrimitive::Image {
1728 rect: Rect::from_size(self.size),
1729 image,
1730 alpha: 1.0,
1731 color_filter: None,
1732 sampling: ImageSampling::Nearest,
1733 src_rect: None,
1734 },
1735 blend_mode,
1736 );
1737 }
1738
1739 fn draw_image_at(
1740 &mut self,
1741 rect: Rect,
1742 image: ImageBitmap,
1743 alpha: f32,
1744 color_filter: Option<ColorFilter>,
1745 ) {
1746 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1747 }
1748
1749 fn draw_image_at_sampled(
1750 &mut self,
1751 rect: Rect,
1752 image: ImageBitmap,
1753 alpha: f32,
1754 color_filter: Option<ColorFilter>,
1755 sampling: ImageSampling,
1756 ) {
1757 self.push_blended_primitive(
1758 DrawPrimitive::Image {
1759 rect,
1760 image,
1761 alpha: alpha.clamp(0.0, 1.0),
1762 color_filter,
1763 sampling,
1764 src_rect: None,
1765 },
1766 BlendMode::SrcOver,
1767 );
1768 }
1769
1770 fn draw_image_at_blend(
1771 &mut self,
1772 rect: Rect,
1773 image: ImageBitmap,
1774 alpha: f32,
1775 color_filter: Option<ColorFilter>,
1776 blend_mode: BlendMode,
1777 ) {
1778 self.push_blended_primitive(
1779 DrawPrimitive::Image {
1780 rect,
1781 image,
1782 alpha: alpha.clamp(0.0, 1.0),
1783 color_filter,
1784 sampling: ImageSampling::Nearest,
1785 src_rect: None,
1786 },
1787 blend_mode,
1788 );
1789 }
1790
1791 fn draw_image_src(
1792 &mut self,
1793 image: ImageBitmap,
1794 src_rect: Rect,
1795 dst_rect: Rect,
1796 alpha: f32,
1797 color_filter: Option<ColorFilter>,
1798 ) {
1799 self.draw_image_src_blend(
1800 image,
1801 src_rect,
1802 dst_rect,
1803 alpha,
1804 color_filter,
1805 BlendMode::SrcOver,
1806 );
1807 }
1808
1809 fn draw_image_src_sampled(
1810 &mut self,
1811 image: ImageBitmap,
1812 src_rect: Rect,
1813 dst_rect: Rect,
1814 alpha: f32,
1815 color_filter: Option<ColorFilter>,
1816 sampling: ImageSampling,
1817 ) {
1818 self.push_blended_primitive(
1819 DrawPrimitive::Image {
1820 rect: dst_rect,
1821 image,
1822 alpha: alpha.clamp(0.0, 1.0),
1823 color_filter,
1824 sampling,
1825 src_rect: Some(src_rect),
1826 },
1827 BlendMode::SrcOver,
1828 );
1829 }
1830
1831 fn draw_image_src_blend(
1832 &mut self,
1833 image: ImageBitmap,
1834 src_rect: Rect,
1835 dst_rect: Rect,
1836 alpha: f32,
1837 color_filter: Option<ColorFilter>,
1838 blend_mode: BlendMode,
1839 ) {
1840 self.push_blended_primitive(
1841 DrawPrimitive::Image {
1842 rect: dst_rect,
1843 image,
1844 alpha: alpha.clamp(0.0, 1.0),
1845 color_filter,
1846 sampling: ImageSampling::Nearest,
1847 src_rect: Some(src_rect),
1848 },
1849 blend_mode,
1850 );
1851 }
1852
1853 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1854 self.push_vector_path(path, &brush, BlendMode::SrcOver);
1855 }
1856
1857 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1858 match &self.text_measurer {
1859 Some(measurer) => measurer.measure_text(text, style),
1860 None => estimate_text_measurement(text, style),
1861 }
1862 }
1863
1864 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1865 if text.is_empty() {
1866 return;
1867 }
1868 let Some(color) = solid_fill_color(&brush) else {
1869 return;
1870 };
1871 if color.3 <= 0.0 {
1872 return;
1873 }
1874 let measurement = self.measure_text(text, style);
1875 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1876 return;
1877 }
1878 let origin = align_text_block(rect, measurement, style);
1879 if !origin.x.is_finite() || !origin.y.is_finite() {
1880 return;
1881 }
1882 self.recording
1883 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1884 rect: Rect::from_origin_size(origin, measurement.size),
1885 text: shared_text_str(text),
1886 style: style.clone(),
1887 color,
1888 })));
1889 }
1890
1891 fn into_primitives(self) -> Vec<DrawPrimitive> {
1892 self.finish().into_primitives_with_markers()
1893 }
1894}
1895
1896fn solid_fill_color(brush: &Brush) -> Option<Color> {
1901 match brush {
1902 Brush::Solid(color) => Some(*color),
1903 Brush::LinearGradient { colors, .. }
1904 | Brush::RadialGradient { colors, .. }
1905 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1906 }
1907}
1908
1909#[cfg(test)]
1910#[path = "tests/geometry_tests.rs"]
1911mod tests;