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 const DEFAULT: Self = Self {
421 alpha: 1.0,
422 scale: 1.0,
423 scale_x: 1.0,
424 scale_y: 1.0,
425 rotation_x: 0.0,
426 rotation_y: 0.0,
427 rotation_z: 0.0,
428 camera_distance: 8.0,
429 transform_origin: TransformOrigin::CENTER,
430 translation_x: 0.0,
431 translation_y: 0.0,
432 shadow_elevation: 0.0,
433 ambient_shadow_color: Color::BLACK,
434 spot_shadow_color: Color::BLACK,
435 shape: LayerShape::Rectangle,
436 clip: false,
437 compositing_strategy: CompositingStrategy::Auto,
438 blend_mode: BlendMode::SrcOver,
439 color_filter: None,
440 render_effect: None,
441 backdrop_effect: None,
442 };
443
444 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
468 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
469 }
470}
471
472impl Default for GraphicsLayer {
473 fn default() -> Self {
474 Self::DEFAULT
475 }
476}
477
478#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
483pub enum BlendMode {
484 Clear,
485 Src,
486 Dst,
487 #[default]
488 SrcOver,
489 DstOver,
490 SrcIn,
491 DstIn,
492 SrcOut,
493 DstOut,
494 SrcAtop,
495 DstAtop,
496 Xor,
497 Plus,
498 Modulate,
499 Screen,
500 Overlay,
501 Darken,
502 Lighten,
503 ColorDodge,
504 ColorBurn,
505 HardLight,
506 SoftLight,
507 Difference,
508 Exclusion,
509 Multiply,
510 Hue,
511 Saturation,
512 Color,
513 Luminosity,
514}
515
516impl BlendMode {
518 pub const ALL: [BlendMode; 29] = [
520 BlendMode::Clear,
521 BlendMode::Src,
522 BlendMode::Dst,
523 BlendMode::SrcOver,
524 BlendMode::DstOver,
525 BlendMode::SrcIn,
526 BlendMode::DstIn,
527 BlendMode::SrcOut,
528 BlendMode::DstOut,
529 BlendMode::SrcAtop,
530 BlendMode::DstAtop,
531 BlendMode::Xor,
532 BlendMode::Plus,
533 BlendMode::Modulate,
534 BlendMode::Screen,
535 BlendMode::Overlay,
536 BlendMode::Darken,
537 BlendMode::Lighten,
538 BlendMode::ColorDodge,
539 BlendMode::ColorBurn,
540 BlendMode::HardLight,
541 BlendMode::SoftLight,
542 BlendMode::Difference,
543 BlendMode::Exclusion,
544 BlendMode::Multiply,
545 BlendMode::Hue,
546 BlendMode::Saturation,
547 BlendMode::Color,
548 BlendMode::Luminosity,
549 ];
550}
551
552#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
553pub enum CompositingStrategy {
554 #[default]
556 Auto,
557 Offscreen,
559 ModulateAlpha,
561}
562
563#[derive(Clone, Debug, PartialEq)]
564pub enum DrawPrimitive {
565 Content,
568 Blend {
570 primitive: Box<DrawPrimitive>,
571 blend_mode: BlendMode,
572 },
573 Rect {
574 rect: Rect,
575 brush: Brush,
576 stroke: Option<Stroke>,
579 },
580 RoundRect {
581 rect: Rect,
582 brush: Brush,
583 radii: CornerRadii,
584 stroke: Option<Stroke>,
587 },
588 Arc {
598 rect: Rect,
602 brush: Brush,
603 center: Point,
604 radius: f32,
605 start_angle: f32,
606 sweep_angle: f32,
607 stroke: Option<Stroke>,
608 inner_radius: f32,
610 },
611 Line {
614 rect: Rect,
617 brush: Brush,
618 start: Point,
619 end: Point,
620 stroke: Stroke,
621 },
622 Image {
623 rect: Rect,
624 image: ImageBitmap,
625 alpha: f32,
626 color_filter: Option<ColorFilter>,
627 sampling: ImageSampling,
628 src_rect: Option<Rect>,
632 },
633 Text(Box<TextPrimitive>),
635 Shadow(ShadowPrimitive),
638}
639
640#[derive(Clone, Debug, PartialEq)]
649pub struct TextPrimitive {
650 pub rect: Rect,
653 pub text: std::rc::Rc<str>,
656 pub style: DrawTextStyle,
657 pub color: Color,
661}
662
663fn shared_text_str(text: &str) -> Rc<str> {
673 use std::{
674 cell::RefCell,
675 collections::HashMap,
676 hash::{Hash, Hasher},
677 };
678
679 const POOL_CAPACITY: usize = 256;
680 thread_local! {
681 static POOL: RefCell<HashMap<u64, Rc<str>, crate::FxBuildHasher>> =
682 RefCell::new(HashMap::default());
683 }
684
685 let mut hasher = crate::FxHasher::default();
686 text.hash(&mut hasher);
687 let key = hasher.finish();
688
689 POOL.with(|pool| {
690 let mut pool = pool.borrow_mut();
691 if let Some(shared) = pool.get(&key)
692 && &**shared == text
693 {
694 return Rc::clone(shared);
695 }
696 let shared: Rc<str> = Rc::from(text);
697 if pool.len() >= POOL_CAPACITY {
698 pool.clear();
699 }
700 pool.insert(key, Rc::clone(&shared));
701 shared
702 })
703}
704
705impl DrawPrimitive {
707 pub fn translate(self, dx: f32, dy: f32) -> Self {
710 match self {
711 DrawPrimitive::Content => DrawPrimitive::Content,
712 DrawPrimitive::Blend {
713 primitive,
714 blend_mode,
715 } => DrawPrimitive::Blend {
716 primitive: Box::new(primitive.translate(dx, dy)),
717 blend_mode,
718 },
719 DrawPrimitive::Rect {
720 rect,
721 brush,
722 stroke,
723 } => DrawPrimitive::Rect {
724 rect: rect.translate(dx, dy),
725 brush,
726 stroke,
727 },
728 DrawPrimitive::RoundRect {
729 rect,
730 brush,
731 radii,
732 stroke,
733 } => DrawPrimitive::RoundRect {
734 rect: rect.translate(dx, dy),
735 brush,
736 radii,
737 stroke,
738 },
739 DrawPrimitive::Arc {
740 rect,
741 brush,
742 center,
743 radius,
744 start_angle,
745 sweep_angle,
746 stroke,
747 inner_radius,
748 } => DrawPrimitive::Arc {
749 rect: rect.translate(dx, dy),
750 brush,
751 center: Point::new(center.x + dx, center.y + dy),
752 radius,
753 start_angle,
754 sweep_angle,
755 stroke,
756 inner_radius,
757 },
758 DrawPrimitive::Line {
759 rect,
760 brush,
761 start,
762 end,
763 stroke,
764 } => DrawPrimitive::Line {
765 rect: rect.translate(dx, dy),
766 brush,
767 start: Point::new(start.x + dx, start.y + dy),
768 end: Point::new(end.x + dx, end.y + dy),
769 stroke,
770 },
771 DrawPrimitive::Image {
772 rect,
773 image,
774 alpha,
775 color_filter,
776 sampling,
777 src_rect,
778 } => DrawPrimitive::Image {
779 rect: rect.translate(dx, dy),
780 image,
781 alpha,
782 color_filter,
783 sampling,
784 src_rect,
785 },
786 DrawPrimitive::Text(mut text) => {
787 text.rect = text.rect.translate(dx, dy);
788 DrawPrimitive::Text(text)
789 }
790 DrawPrimitive::Shadow(ShadowPrimitive::Drop {
791 shape,
792 cutout,
793 blur_radius,
794 blend_mode,
795 }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
796 shape: Box::new(shape.translate(dx, dy)),
797 cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
798 blur_radius,
799 blend_mode,
800 }),
801 DrawPrimitive::Shadow(ShadowPrimitive::Inner {
802 fill,
803 cutout,
804 blur_radius,
805 blend_mode,
806 clip_rect,
807 }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
808 fill: Box::new(fill.translate(dx, dy)),
809 cutout: Box::new(cutout.translate(dx, dy)),
810 blur_radius,
811 blend_mode,
812 clip_rect: clip_rect.translate(dx, dy),
813 }),
814 }
815 }
816}
817
818#[derive(Clone, Debug, PartialEq)]
819pub enum ShadowPrimitive {
820 Drop {
824 shape: Box<DrawPrimitive>,
825 cutout: Option<Box<DrawPrimitive>>,
826 blur_radius: f32,
827 blend_mode: BlendMode,
828 },
829 Inner {
831 fill: Box<DrawPrimitive>,
832 cutout: Box<DrawPrimitive>,
833 blur_radius: f32,
834 blend_mode: BlendMode,
835 clip_rect: Rect,
837 },
838}
839
840pub trait DrawScope {
841 fn size(&self) -> Size;
842 fn draw_content(&mut self);
843 fn draw_rect(&mut self, brush: Brush);
844 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
845 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
847 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
848 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
849 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
850 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
852 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
853 fn draw_circle_blend(
854 &mut self,
855 brush: Brush,
856 center: Point,
857 radius: f32,
858 blend_mode: BlendMode,
859 );
860
861 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
863 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
864 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
866 fn draw_rect_at_stroked_blend(
867 &mut self,
868 rect: Rect,
869 brush: Brush,
870 stroke: Stroke,
871 blend_mode: BlendMode,
872 );
873 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
875 fn draw_round_rect_stroked_blend(
876 &mut self,
877 brush: Brush,
878 radii: CornerRadii,
879 stroke: Stroke,
880 blend_mode: BlendMode,
881 );
882 fn draw_round_rect_at_stroked(
884 &mut self,
885 rect: Rect,
886 brush: Brush,
887 radii: CornerRadii,
888 stroke: Stroke,
889 );
890 fn draw_round_rect_at_stroked_blend(
891 &mut self,
892 rect: Rect,
893 brush: Brush,
894 radii: CornerRadii,
895 stroke: Stroke,
896 blend_mode: BlendMode,
897 );
898 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
901 fn draw_circle_stroked_blend(
902 &mut self,
903 brush: Brush,
904 center: Point,
905 radius: f32,
906 stroke: Stroke,
907 blend_mode: BlendMode,
908 );
909
910 fn draw_arc(
923 &mut self,
924 brush: Brush,
925 center: Point,
926 radius: f32,
927 start_angle: f32,
928 sweep_angle: f32,
929 stroke: Stroke,
930 );
931 #[expect(clippy::too_many_arguments)]
932 fn draw_arc_blend(
933 &mut self,
934 brush: Brush,
935 center: Point,
936 radius: f32,
937 start_angle: f32,
938 sweep_angle: f32,
939 stroke: Stroke,
940 blend_mode: BlendMode,
941 );
942
943 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke);
949 fn draw_line_blend(
950 &mut self,
951 brush: Brush,
952 start: Point,
953 end: Point,
954 stroke: Stroke,
955 blend_mode: BlendMode,
956 );
957
958 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle);
967 fn draw_path_blend(
968 &mut self,
969 path: &Path,
970 brush: Brush,
971 style: DrawStyle,
972 blend_mode: BlendMode,
973 );
974
975 fn draw_annular_sector(
984 &mut self,
985 brush: Brush,
986 center: Point,
987 inner_radius: f32,
988 outer_radius: f32,
989 start_angle: f32,
990 sweep_angle: f32,
991 );
992 #[expect(clippy::too_many_arguments)]
993 fn draw_annular_sector_blend(
994 &mut self,
995 brush: Brush,
996 center: Point,
997 inner_radius: f32,
998 outer_radius: f32,
999 start_angle: f32,
1000 sweep_angle: f32,
1001 blend_mode: BlendMode,
1002 );
1003
1004 fn draw_image(&mut self, image: ImageBitmap);
1005 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
1006 fn draw_image_at(
1007 &mut self,
1008 rect: Rect,
1009 image: ImageBitmap,
1010 alpha: f32,
1011 color_filter: Option<ColorFilter>,
1012 );
1013 fn draw_image_at_sampled(
1014 &mut self,
1015 rect: Rect,
1016 image: ImageBitmap,
1017 alpha: f32,
1018 color_filter: Option<ColorFilter>,
1019 sampling: ImageSampling,
1020 );
1021 fn draw_image_at_blend(
1022 &mut self,
1023 rect: Rect,
1024 image: ImageBitmap,
1025 alpha: f32,
1026 color_filter: Option<ColorFilter>,
1027 blend_mode: BlendMode,
1028 );
1029 fn draw_image_src(
1032 &mut self,
1033 image: ImageBitmap,
1034 src_rect: Rect,
1035 dst_rect: Rect,
1036 alpha: f32,
1037 color_filter: Option<ColorFilter>,
1038 );
1039 fn draw_image_src_sampled(
1040 &mut self,
1041 image: ImageBitmap,
1042 src_rect: Rect,
1043 dst_rect: Rect,
1044 alpha: f32,
1045 color_filter: Option<ColorFilter>,
1046 sampling: ImageSampling,
1047 );
1048 fn draw_image_src_blend(
1049 &mut self,
1050 image: ImageBitmap,
1051 src_rect: Rect,
1052 dst_rect: Rect,
1053 alpha: f32,
1054 color_filter: Option<ColorFilter>,
1055 blend_mode: BlendMode,
1056 );
1057 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1066 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1072 if let Ok(path) = crate::VectorPath::parse(d) {
1073 self.draw_vector_path(&path, brush);
1074 }
1075 }
1076
1077 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1084
1085 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1088 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1089 }
1090
1091 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1098
1099 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1105 if text.is_empty() {
1106 return;
1107 }
1108 let measurement = self.measure_text(text, style);
1109 self.draw_text_at(
1110 Rect::from_origin_size(top_left, measurement.size),
1111 brush,
1112 text,
1113 &DrawTextStyle {
1114 align: TextAlign::Left,
1115 vertical_align: TextVerticalAlign::Top,
1116 ..style.clone()
1117 },
1118 );
1119 }
1120
1121 fn into_primitives(self) -> Vec<DrawPrimitive>;
1122}
1123
1124pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1130 let x = match style.align {
1131 TextAlign::Left => rect.x,
1132 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1133 TextAlign::Right => rect.x + rect.width - measurement.size.width,
1134 };
1135 let y = match style.vertical_align {
1136 TextVerticalAlign::Top => rect.y,
1137 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1138 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1139 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1140 };
1141 Point::new(x, y)
1142}
1143
1144#[derive(Default)]
1145pub struct DrawScopeDefault {
1146 size: Size,
1147 recording: CommandRecorder,
1148 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1149}
1150
1151const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1152
1153thread_local! {
1154 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<
1155 std::collections::HashMap<(u32, u32), usize, crate::FxBuildHasher>,
1156 > = std::cell::RefCell::new(std::collections::HashMap::default());
1157}
1158
1159fn recorded_primitive_capacity(size: Size) -> usize {
1160 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1161 counts
1162 .borrow()
1163 .get(&(size.width.to_bits(), size.height.to_bits()))
1164 .copied()
1165 .unwrap_or(0)
1166 })
1167}
1168
1169fn note_recorded_primitive_count(size: Size, count: usize) {
1170 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1171 let mut counts = counts.borrow_mut();
1172 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1173 counts.clear();
1174 }
1175 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1176 });
1177}
1178
1179impl DrawScopeDefault {
1180 pub fn new(size: Size) -> Self {
1181 Self::with_storage(size, None, CommandRecording::default())
1182 }
1183
1184 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1189 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1190 }
1191
1192 pub fn with_text_measurer_reusing(
1195 size: Size,
1196 text_measurer: Rc<dyn DrawTextMeasurer>,
1197 storage: CommandRecording,
1198 ) -> Self {
1199 Self::with_storage(size, Some(text_measurer), storage)
1200 }
1201
1202 fn with_storage(
1203 size: Size,
1204 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1205 recording: CommandRecording,
1206 ) -> Self {
1207 let mut recording = CommandRecorder::reusing(recording);
1208 recording.reserve_shapes(recorded_primitive_capacity(size));
1209 Self {
1210 size,
1211 recording,
1212 text_measurer,
1213 }
1214 }
1215
1216 pub fn content_marker_count(&self) -> u32 {
1218 self.recording.content_markers()
1219 }
1220
1221 pub fn inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault)) {
1225 let inner = insets.inset_rect(self.size);
1226 let mut scope = Self::with_storage(
1227 Size {
1228 width: inner.width,
1229 height: inner.height,
1230 },
1231 self.text_measurer.clone(),
1232 CommandRecording::default(),
1233 );
1234 draw(&mut scope);
1235 let primitives = scope.into_primitives();
1236 self.push_recorded(
1237 primitives
1238 .into_iter()
1239 .map(|primitive| primitive.translate(inner.x, inner.y)),
1240 );
1241 }
1242
1243 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1245 for primitive in primitives {
1246 self.recording.push_primitive(primitive);
1247 }
1248 }
1249
1250 pub fn finish(self) -> CommandRecording {
1253 note_recorded_primitive_count(self.size, self.recording.len());
1254 self.recording.finish()
1255 }
1256
1257 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1258 if blend_mode != BlendMode::SrcOver {
1259 self.recording.push_other(DrawPrimitive::Blend {
1260 primitive: Box::new(primitive),
1261 blend_mode,
1262 });
1263 return;
1264 }
1265 self.recording.push_other(primitive);
1266 }
1267
1268 #[expect(clippy::too_many_arguments)]
1269 #[inline]
1270 fn push_arc(
1271 &mut self,
1272 brush: Brush,
1273 center: Point,
1274 radius: f32,
1275 start_angle: f32,
1276 sweep_angle: f32,
1277 stroke: Option<Stroke>,
1278 inner_radius: f32,
1279 blend_mode: BlendMode,
1280 ) {
1281 let args = ArcRecordArgs {
1282 brush: &brush,
1283 center,
1284 radius,
1285 start_angle,
1286 sweep_angle,
1287 stroke,
1288 inner_radius,
1289 blend_mode,
1290 };
1291 let geometry = normalized_band(&args);
1292 if geometry.is_degenerate() {
1293 return;
1294 }
1295 self.recording.push_scope_arc(&args, &geometry);
1296 }
1297
1298 fn push_vector_path(&mut self, path: &crate::VectorPath, brush: &Brush, blend_mode: BlendMode) {
1301 const SUPERSAMPLE: f32 = 2.0;
1302 const MAX_MASK_PIXELS: f32 = 4096.0;
1303
1304 if path.is_empty() {
1305 return;
1306 }
1307 let bounds = path.bounds();
1308 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1309 return;
1310 }
1311
1312 let color = match brush {
1313 Brush::Solid(color) => *color,
1314 Brush::LinearGradient { colors, .. }
1315 | Brush::RadialGradient { colors, .. }
1316 | Brush::SweepGradient { colors, .. } => match colors.first() {
1317 Some(color) => *color,
1318 None => return,
1319 },
1320 };
1321 if color.3 <= 0.0 {
1322 return;
1323 }
1324
1325 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1326 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1327 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1328 let mask_width = (rect_width * SUPERSAMPLE)
1329 .ceil()
1330 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1331 let mask_height = (rect_height * SUPERSAMPLE)
1332 .ceil()
1333 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1334
1335 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1336 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1337 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1338 let alpha = color.3.clamp(0.0, 1.0);
1339 let key = vector_path_mask_key(
1340 path,
1341 origin,
1342 (mask_width, mask_height),
1343 [red, green, blue],
1344 alpha,
1345 );
1346 let cached = vector_path_mask_cache_get(key);
1347 let image = match cached {
1348 Some(image) => image,
1349 None => {
1350 let mut mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1351 for coverage in &mut mask {
1352 *coverage = (alpha * *coverage as f32 + 0.5) as u8;
1353 }
1354 let Ok(image) = ImageBitmap::from_alpha8(
1355 mask_width as u32,
1356 mask_height as u32,
1357 [red, green, blue],
1358 mask,
1359 ) else {
1360 return;
1361 };
1362 vector_path_mask_cache_put(key, image.clone());
1363 image
1364 }
1365 };
1366
1367 let image = DrawPrimitive::Image {
1368 rect: Rect {
1369 x: origin.x,
1370 y: origin.y,
1371 width: rect_width,
1372 height: rect_height,
1373 },
1374 image,
1375 alpha: 1.0,
1376 color_filter: None,
1377 sampling: ImageSampling::Linear,
1378 src_rect: None,
1379 };
1380 self.recording
1381 .push_other(if blend_mode == BlendMode::SrcOver {
1382 image
1383 } else {
1384 DrawPrimitive::Blend {
1385 primitive: Box::new(image),
1386 blend_mode,
1387 }
1388 });
1389 }
1390}
1391
1392impl DrawScope for DrawScopeDefault {
1393 fn size(&self) -> Size {
1394 self.size
1395 }
1396
1397 fn draw_content(&mut self) {
1398 self.recording.push_content();
1399 }
1400
1401 fn draw_rect(&mut self, brush: Brush) {
1402 self.draw_rect_blend(brush, BlendMode::SrcOver);
1403 }
1404
1405 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1406 self.recording
1407 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1408 }
1409
1410 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1411 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1412 }
1413
1414 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1415 self.recording.push_rect(rect, &brush, None, blend_mode);
1416 }
1417
1418 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1419 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1420 }
1421
1422 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1423 self.recording
1424 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1425 }
1426
1427 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1428 self.recording
1429 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1430 }
1431
1432 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1433 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1434 }
1435
1436 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1437 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1438 }
1439
1440 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1441 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1442 }
1443
1444 fn draw_rect_at_stroked_blend(
1445 &mut self,
1446 rect: Rect,
1447 brush: Brush,
1448 stroke: Stroke,
1449 blend_mode: BlendMode,
1450 ) {
1451 if !stroke.is_visible() {
1452 return;
1453 }
1454 self.recording
1455 .push_rect(rect, &brush, Some(stroke), blend_mode);
1456 }
1457
1458 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1459 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1460 }
1461
1462 fn draw_round_rect_stroked_blend(
1463 &mut self,
1464 brush: Brush,
1465 radii: CornerRadii,
1466 stroke: Stroke,
1467 blend_mode: BlendMode,
1468 ) {
1469 self.draw_round_rect_at_stroked_blend(
1470 Rect::from_size(self.size),
1471 brush,
1472 radii,
1473 stroke,
1474 blend_mode,
1475 );
1476 }
1477
1478 fn draw_round_rect_at_stroked(
1479 &mut self,
1480 rect: Rect,
1481 brush: Brush,
1482 radii: CornerRadii,
1483 stroke: Stroke,
1484 ) {
1485 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1486 }
1487
1488 fn draw_round_rect_at_stroked_blend(
1489 &mut self,
1490 rect: Rect,
1491 brush: Brush,
1492 radii: CornerRadii,
1493 stroke: Stroke,
1494 blend_mode: BlendMode,
1495 ) {
1496 if !stroke.is_visible() {
1497 return;
1498 }
1499 self.recording
1500 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1501 }
1502
1503 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1504 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1505 }
1506
1507 fn draw_circle_stroked_blend(
1508 &mut self,
1509 brush: Brush,
1510 center: Point,
1511 radius: f32,
1512 stroke: Stroke,
1513 blend_mode: BlendMode,
1514 ) {
1515 if !stroke.is_visible() || !radius.is_finite() {
1516 return;
1517 }
1518 let radius = radius.max(0.0);
1519 let diameter = radius * 2.0;
1520 self.draw_round_rect_at_stroked_blend(
1521 Rect {
1522 x: center.x - radius,
1523 y: center.y - radius,
1524 width: diameter,
1525 height: diameter,
1526 },
1527 brush,
1528 CornerRadii::uniform(radius),
1529 stroke,
1530 blend_mode,
1531 );
1532 }
1533
1534 fn draw_arc(
1535 &mut self,
1536 brush: Brush,
1537 center: Point,
1538 radius: f32,
1539 start_angle: f32,
1540 sweep_angle: f32,
1541 stroke: Stroke,
1542 ) {
1543 self.draw_arc_blend(
1544 brush,
1545 center,
1546 radius,
1547 start_angle,
1548 sweep_angle,
1549 stroke,
1550 BlendMode::SrcOver,
1551 );
1552 }
1553
1554 fn draw_arc_blend(
1555 &mut self,
1556 brush: Brush,
1557 center: Point,
1558 radius: f32,
1559 start_angle: f32,
1560 sweep_angle: f32,
1561 stroke: Stroke,
1562 blend_mode: BlendMode,
1563 ) {
1564 if !stroke.is_visible() {
1565 return;
1566 }
1567 self.push_arc(
1568 brush,
1569 center,
1570 radius,
1571 start_angle,
1572 sweep_angle,
1573 Some(stroke),
1574 0.0,
1575 blend_mode,
1576 );
1577 }
1578
1579 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke) {
1580 self.draw_line_blend(brush, start, end, stroke, BlendMode::SrcOver);
1581 }
1582
1583 fn draw_line_blend(
1584 &mut self,
1585 brush: Brush,
1586 start: Point,
1587 end: Point,
1588 stroke: Stroke,
1589 blend_mode: BlendMode,
1590 ) {
1591 let line = LineGeometry::new(start, end, stroke);
1592 if line.is_degenerate() {
1593 return;
1594 }
1595 self.recording.push_line(&line, &brush, stroke, blend_mode);
1596 }
1597
1598 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle) {
1599 self.draw_path_blend(path, brush, style, BlendMode::SrcOver);
1600 }
1601
1602 fn draw_path_blend(
1603 &mut self,
1604 path: &Path,
1605 brush: Brush,
1606 style: DrawStyle,
1607 blend_mode: BlendMode,
1608 ) {
1609 let (stroke, dash) = match style {
1610 DrawStyle::Fill => {
1611 self.push_vector_path(
1612 &path.to_vector_path(crate::PathFillRule::NonZero),
1613 &brush,
1614 blend_mode,
1615 );
1616 return;
1617 }
1618 DrawStyle::Stroke(stroke) => (stroke, None),
1619 DrawStyle::DashedStroke(stroke, dash) => (stroke, Some(dash)),
1620 };
1621 if !stroke.is_visible() {
1622 return;
1623 }
1624 let recording = &mut self.recording;
1625 let mut push = |line: LineGeometry| {
1626 if !line.is_degenerate() {
1627 let stroke = Stroke {
1628 cap: line.cap,
1629 ..stroke
1630 };
1631 recording.push_line(&line, &brush, stroke, blend_mode);
1632 }
1633 };
1634 for contour in path.contours() {
1635 let Some(dash) = dash else {
1636 crate::for_each_stroke_line(&contour.points, contour.closed, stroke, &mut push);
1637 continue;
1638 };
1639 let closing = contour
1640 .closed
1641 .then(|| contour.points.first().copied())
1642 .flatten();
1643 let points: std::borrow::Cow<'_, [Point]> = match closing {
1644 Some(first) => {
1645 std::borrow::Cow::Owned(contour.points.iter().copied().chain([first]).collect())
1646 }
1647 None => std::borrow::Cow::Borrowed(&contour.points),
1648 };
1649 dash.for_each_dash(&points, |piece| {
1650 crate::for_each_stroke_line(piece, false, stroke, &mut push);
1651 });
1652 }
1653 }
1654
1655 fn draw_annular_sector(
1656 &mut self,
1657 brush: Brush,
1658 center: Point,
1659 inner_radius: f32,
1660 outer_radius: f32,
1661 start_angle: f32,
1662 sweep_angle: f32,
1663 ) {
1664 self.draw_annular_sector_blend(
1665 brush,
1666 center,
1667 inner_radius,
1668 outer_radius,
1669 start_angle,
1670 sweep_angle,
1671 BlendMode::SrcOver,
1672 );
1673 }
1674
1675 fn draw_annular_sector_blend(
1676 &mut self,
1677 brush: Brush,
1678 center: Point,
1679 inner_radius: f32,
1680 outer_radius: f32,
1681 start_angle: f32,
1682 sweep_angle: f32,
1683 blend_mode: BlendMode,
1684 ) {
1685 self.push_arc(
1686 brush,
1687 center,
1688 outer_radius,
1689 start_angle,
1690 sweep_angle,
1691 None,
1692 inner_radius,
1693 blend_mode,
1694 );
1695 }
1696
1697 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1698 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1699 }
1700
1701 fn draw_circle_blend(
1702 &mut self,
1703 brush: Brush,
1704 center: Point,
1705 radius: f32,
1706 blend_mode: BlendMode,
1707 ) {
1708 let radius = radius.max(0.0);
1709 let diameter = radius * 2.0;
1710 self.recording.push_round_rect(
1711 Rect {
1712 x: center.x - radius,
1713 y: center.y - radius,
1714 width: diameter,
1715 height: diameter,
1716 },
1717 &brush,
1718 CornerRadii::uniform(radius),
1719 None,
1720 blend_mode,
1721 );
1722 }
1723
1724 fn draw_image(&mut self, image: ImageBitmap) {
1725 self.draw_image_blend(image, BlendMode::SrcOver);
1726 }
1727
1728 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1729 self.push_blended_primitive(
1730 DrawPrimitive::Image {
1731 rect: Rect::from_size(self.size),
1732 image,
1733 alpha: 1.0,
1734 color_filter: None,
1735 sampling: ImageSampling::Nearest,
1736 src_rect: None,
1737 },
1738 blend_mode,
1739 );
1740 }
1741
1742 fn draw_image_at(
1743 &mut self,
1744 rect: Rect,
1745 image: ImageBitmap,
1746 alpha: f32,
1747 color_filter: Option<ColorFilter>,
1748 ) {
1749 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1750 }
1751
1752 fn draw_image_at_sampled(
1753 &mut self,
1754 rect: Rect,
1755 image: ImageBitmap,
1756 alpha: f32,
1757 color_filter: Option<ColorFilter>,
1758 sampling: ImageSampling,
1759 ) {
1760 self.push_blended_primitive(
1761 DrawPrimitive::Image {
1762 rect,
1763 image,
1764 alpha: alpha.clamp(0.0, 1.0),
1765 color_filter,
1766 sampling,
1767 src_rect: None,
1768 },
1769 BlendMode::SrcOver,
1770 );
1771 }
1772
1773 fn draw_image_at_blend(
1774 &mut self,
1775 rect: Rect,
1776 image: ImageBitmap,
1777 alpha: f32,
1778 color_filter: Option<ColorFilter>,
1779 blend_mode: BlendMode,
1780 ) {
1781 self.push_blended_primitive(
1782 DrawPrimitive::Image {
1783 rect,
1784 image,
1785 alpha: alpha.clamp(0.0, 1.0),
1786 color_filter,
1787 sampling: ImageSampling::Nearest,
1788 src_rect: None,
1789 },
1790 blend_mode,
1791 );
1792 }
1793
1794 fn draw_image_src(
1795 &mut self,
1796 image: ImageBitmap,
1797 src_rect: Rect,
1798 dst_rect: Rect,
1799 alpha: f32,
1800 color_filter: Option<ColorFilter>,
1801 ) {
1802 self.draw_image_src_blend(
1803 image,
1804 src_rect,
1805 dst_rect,
1806 alpha,
1807 color_filter,
1808 BlendMode::SrcOver,
1809 );
1810 }
1811
1812 fn draw_image_src_sampled(
1813 &mut self,
1814 image: ImageBitmap,
1815 src_rect: Rect,
1816 dst_rect: Rect,
1817 alpha: f32,
1818 color_filter: Option<ColorFilter>,
1819 sampling: ImageSampling,
1820 ) {
1821 self.push_blended_primitive(
1822 DrawPrimitive::Image {
1823 rect: dst_rect,
1824 image,
1825 alpha: alpha.clamp(0.0, 1.0),
1826 color_filter,
1827 sampling,
1828 src_rect: Some(src_rect),
1829 },
1830 BlendMode::SrcOver,
1831 );
1832 }
1833
1834 fn draw_image_src_blend(
1835 &mut self,
1836 image: ImageBitmap,
1837 src_rect: Rect,
1838 dst_rect: Rect,
1839 alpha: f32,
1840 color_filter: Option<ColorFilter>,
1841 blend_mode: BlendMode,
1842 ) {
1843 self.push_blended_primitive(
1844 DrawPrimitive::Image {
1845 rect: dst_rect,
1846 image,
1847 alpha: alpha.clamp(0.0, 1.0),
1848 color_filter,
1849 sampling: ImageSampling::Nearest,
1850 src_rect: Some(src_rect),
1851 },
1852 blend_mode,
1853 );
1854 }
1855
1856 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1857 self.push_vector_path(path, &brush, BlendMode::SrcOver);
1858 }
1859
1860 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1861 match &self.text_measurer {
1862 Some(measurer) => measurer.measure_text(text, style),
1863 None => estimate_text_measurement(text, style),
1864 }
1865 }
1866
1867 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1868 if text.is_empty() {
1869 return;
1870 }
1871 let Some(color) = solid_fill_color(&brush) else {
1872 return;
1873 };
1874 if color.3 <= 0.0 {
1875 return;
1876 }
1877 let measurement = self.measure_text(text, style);
1878 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1879 return;
1880 }
1881 let origin = align_text_block(rect, measurement, style);
1882 if !origin.x.is_finite() || !origin.y.is_finite() {
1883 return;
1884 }
1885 self.recording
1886 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1887 rect: Rect::from_origin_size(origin, measurement.size),
1888 text: shared_text_str(text),
1889 style: style.clone(),
1890 color,
1891 })));
1892 }
1893
1894 fn into_primitives(self) -> Vec<DrawPrimitive> {
1895 self.finish().into_primitives_with_markers()
1896 }
1897}
1898
1899fn solid_fill_color(brush: &Brush) -> Option<Color> {
1904 match brush {
1905 Brush::Solid(color) => Some(*color),
1906 Brush::LinearGradient { colors, .. }
1907 | Brush::RadialGradient { colors, .. }
1908 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1909 }
1910}
1911
1912#[cfg(test)]
1913#[path = "tests/geometry_tests.rs"]
1914mod tests;