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