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 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
19pub(crate) struct VectorPathMaskCache {
20 entries: Vec<(u64, ImageBitmap)>,
21 bytes: usize,
22}
23
24impl VectorPathMaskCache {
25 pub(crate) 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
56fn vector_path_mask_key(
57 path: &crate::VectorPath,
58 origin: Point,
59 mask_size: (usize, usize),
60 rgb: [u8; 3],
61 alpha: f32,
62) -> u64 {
63 use std::hash::Hasher;
64 let mut hasher = crate::fx_hash::FxHasher::default();
65 hasher.write_u8(path.fill_rule() as u8);
66 hasher.write_u32(origin.x.to_bits());
67 hasher.write_u32(origin.y.to_bits());
68 hasher.write_usize(mask_size.0);
69 hasher.write_usize(mask_size.1);
70 hasher.write(&rgb);
71 hasher.write_u32(alpha.to_bits());
72 for subpath in path.subpaths() {
73 hasher.write_usize(subpath.len());
74 for point in subpath {
75 hasher.write_u32(point.x.to_bits());
76 hasher.write_u32(point.y.to_bits());
77 }
78 }
79 hasher.finish()
80}
81
82fn vector_path_mask_cache_get(key: u64) -> Option<ImageBitmap> {
83 crate::thread_state::with(|state| state.vector_path_masks.borrow_mut().get(key))
84}
85
86fn vector_path_mask_cache_put(key: u64, image: ImageBitmap) {
87 crate::thread_state::with(|state| state.vector_path_masks.borrow_mut().put(key, image));
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Default)]
91pub struct Point {
92 pub x: f32,
93 pub y: f32,
94}
95
96impl Point {
97 pub const fn new(x: f32, y: f32) -> Self {
98 Self { x, y }
99 }
100
101 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Default)]
105pub struct Size {
106 pub width: f32,
107 pub height: f32,
108}
109
110impl Size {
111 pub const fn new(width: f32, height: f32) -> Self {
112 Self { width, height }
113 }
114
115 pub const ZERO: Size = Size {
116 width: 0.0,
117 height: 0.0,
118 };
119}
120
121#[derive(Clone, Copy, Debug, PartialEq)]
122pub struct Rect {
123 pub x: f32,
124 pub y: f32,
125 pub width: f32,
126 pub height: f32,
127}
128
129impl Rect {
130 pub const EMPTY: Rect = Rect {
134 x: 0.0,
135 y: 0.0,
136 width: 0.0,
137 height: 0.0,
138 };
139
140 pub fn from_origin_size(origin: Point, size: Size) -> Self {
141 Self {
142 x: origin.x,
143 y: origin.y,
144 width: size.width,
145 height: size.height,
146 }
147 }
148
149 pub fn from_size(size: Size) -> Self {
150 Self {
151 x: 0.0,
152 y: 0.0,
153 width: size.width,
154 height: size.height,
155 }
156 }
157
158 pub fn translate(&self, dx: f32, dy: f32) -> Self {
159 Self {
160 x: self.x + dx,
161 y: self.y + dy,
162 width: self.width,
163 height: self.height,
164 }
165 }
166
167 pub fn contains(&self, x: f32, y: f32) -> bool {
168 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
169 }
170
171 pub fn is_empty(&self) -> bool {
173 self.width <= 0.0 || self.height <= 0.0
174 }
175
176 pub fn intersect(&self, other: Rect) -> Option<Rect> {
178 let left = self.x.max(other.x);
179 let top = self.y.max(other.y);
180 let right = (self.x + self.width).min(other.x + other.width);
181 let bottom = (self.y + self.height).min(other.y + other.height);
182 let width = right - left;
183 let height = bottom - top;
184 if width <= 0.0 || height <= 0.0 {
185 None
186 } else {
187 Some(Rect {
188 x: left,
189 y: top,
190 width,
191 height,
192 })
193 }
194 }
195
196 pub fn union(&self, other: Rect) -> Rect {
197 let left = self.x.min(other.x);
198 let top = self.y.min(other.y);
199 let right = (self.x + self.width).max(other.x + other.width);
200 let bottom = (self.y + self.height).max(other.y + other.height);
201 Rect {
202 x: left,
203 y: top,
204 width: (right - left).max(0.0),
205 height: (bottom - top).max(0.0),
206 }
207 }
208}
209
210#[derive(Clone, Copy, Debug, Default, PartialEq)]
212pub struct EdgeInsets {
213 pub left: f32,
214 pub top: f32,
215 pub right: f32,
216 pub bottom: f32,
217}
218
219impl EdgeInsets {
220 pub fn uniform(all: f32) -> Self {
221 Self {
222 left: all,
223 top: all,
224 right: all,
225 bottom: all,
226 }
227 }
228
229 pub fn horizontal(horizontal: f32) -> Self {
230 Self {
231 left: horizontal,
232 right: horizontal,
233 ..Self::default()
234 }
235 }
236
237 pub fn vertical(vertical: f32) -> Self {
238 Self {
239 top: vertical,
240 bottom: vertical,
241 ..Self::default()
242 }
243 }
244
245 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
246 Self {
247 left: horizontal,
248 right: horizontal,
249 top: vertical,
250 bottom: vertical,
251 }
252 }
253
254 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
255 Self {
256 left,
257 top,
258 right,
259 bottom,
260 }
261 }
262
263 pub fn is_zero(&self) -> bool {
264 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
265 }
266
267 pub fn horizontal_sum(&self) -> f32 {
268 self.left + self.right
269 }
270
271 pub fn vertical_sum(&self) -> f32 {
272 self.top + self.bottom
273 }
274
275 pub fn inset_rect(&self, size: Size) -> Rect {
278 Rect {
279 x: self.left,
280 y: self.top,
281 width: (size.width - self.horizontal_sum()).max(0.0),
282 height: (size.height - self.vertical_sum()).max(0.0),
283 }
284 }
285}
286
287impl AddAssign for EdgeInsets {
288 fn add_assign(&mut self, rhs: Self) {
289 self.left += rhs.left;
290 self.top += rhs.top;
291 self.right += rhs.right;
292 self.bottom += rhs.bottom;
293 }
294}
295
296#[derive(Clone, Copy, Debug, Default, PartialEq)]
297pub struct CornerRadii {
298 pub top_left: f32,
299 pub top_right: f32,
300 pub bottom_right: f32,
301 pub bottom_left: f32,
302}
303
304impl CornerRadii {
305 pub fn uniform(radius: f32) -> Self {
306 Self {
307 top_left: radius,
308 top_right: radius,
309 bottom_right: radius,
310 bottom_left: radius,
311 }
312 }
313}
314
315#[derive(Clone, Copy, Debug, PartialEq)]
316pub struct RoundedCornerShape {
317 radii: CornerRadii,
318}
319
320impl RoundedCornerShape {
321 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
322 Self {
323 radii: CornerRadii {
324 top_left,
325 top_right,
326 bottom_right,
327 bottom_left,
328 },
329 }
330 }
331
332 pub fn uniform(radius: f32) -> Self {
333 Self {
334 radii: CornerRadii::uniform(radius),
335 }
336 }
337
338 pub fn with_radii(radii: CornerRadii) -> Self {
339 Self { radii }
340 }
341
342 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
343 let mut resolved = self.radii;
344 let max_width = (width / 2.0).max(0.0);
345 let max_height = (height / 2.0).max(0.0);
346 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
347 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
348 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
349 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
350 resolved
351 }
352
353 pub fn radii(&self) -> CornerRadii {
354 self.radii
355 }
356}
357
358#[derive(Clone, Copy, Debug, PartialEq)]
359pub struct TransformOrigin {
360 pub pivot_fraction_x: f32,
361 pub pivot_fraction_y: f32,
362}
363
364impl TransformOrigin {
365 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
366 Self {
367 pivot_fraction_x,
368 pivot_fraction_y,
369 }
370 }
371
372 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
373}
374
375impl Default for TransformOrigin {
376 fn default() -> Self {
377 Self::CENTER
378 }
379}
380
381#[derive(Clone, Copy, Debug, Default, PartialEq)]
382pub enum LayerShape {
383 #[default]
384 Rectangle,
385 Rounded(RoundedCornerShape),
386}
387
388#[derive(Clone, Debug, PartialEq)]
389pub struct GraphicsLayer {
390 pub alpha: f32,
391 pub scale: f32,
392 pub scale_x: f32,
393 pub scale_y: f32,
394 pub rotation_x: f32,
395 pub rotation_y: f32,
396 pub rotation_z: f32,
397 pub camera_distance: f32,
398 pub transform_origin: TransformOrigin,
399 pub translation_x: f32,
400 pub translation_y: f32,
401 pub shadow_elevation: f32,
402 pub ambient_shadow_color: Color,
403 pub spot_shadow_color: Color,
404 pub shape: LayerShape,
405 pub clip: bool,
406 pub compositing_strategy: CompositingStrategy,
407 pub blend_mode: BlendMode,
408 pub color_filter: Option<ColorFilter>,
409 pub render_effect: Option<crate::render_effect::RenderEffect>,
410 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
414}
415
416impl GraphicsLayer {
417 pub const DEFAULT: Self = Self {
419 alpha: 1.0,
420 scale: 1.0,
421 scale_x: 1.0,
422 scale_y: 1.0,
423 rotation_x: 0.0,
424 rotation_y: 0.0,
425 rotation_z: 0.0,
426 camera_distance: 8.0,
427 transform_origin: TransformOrigin::CENTER,
428 translation_x: 0.0,
429 translation_y: 0.0,
430 shadow_elevation: 0.0,
431 ambient_shadow_color: Color::BLACK,
432 spot_shadow_color: Color::BLACK,
433 shape: LayerShape::Rectangle,
434 clip: false,
435 compositing_strategy: CompositingStrategy::Auto,
436 blend_mode: BlendMode::SrcOver,
437 color_filter: None,
438 render_effect: None,
439 backdrop_effect: None,
440 };
441
442 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
466 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
467 }
468}
469
470impl Default for GraphicsLayer {
471 fn default() -> Self {
472 Self::DEFAULT
473 }
474}
475
476#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
481pub enum BlendMode {
482 Clear,
483 Src,
484 Dst,
485 #[default]
486 SrcOver,
487 DstOver,
488 SrcIn,
489 DstIn,
490 SrcOut,
491 DstOut,
492 SrcAtop,
493 DstAtop,
494 Xor,
495 Plus,
496 Modulate,
497 Screen,
498 Overlay,
499 Darken,
500 Lighten,
501 ColorDodge,
502 ColorBurn,
503 HardLight,
504 SoftLight,
505 Difference,
506 Exclusion,
507 Multiply,
508 Hue,
509 Saturation,
510 Color,
511 Luminosity,
512}
513
514impl BlendMode {
516 pub const ALL: [BlendMode; 29] = [
518 BlendMode::Clear,
519 BlendMode::Src,
520 BlendMode::Dst,
521 BlendMode::SrcOver,
522 BlendMode::DstOver,
523 BlendMode::SrcIn,
524 BlendMode::DstIn,
525 BlendMode::SrcOut,
526 BlendMode::DstOut,
527 BlendMode::SrcAtop,
528 BlendMode::DstAtop,
529 BlendMode::Xor,
530 BlendMode::Plus,
531 BlendMode::Modulate,
532 BlendMode::Screen,
533 BlendMode::Overlay,
534 BlendMode::Darken,
535 BlendMode::Lighten,
536 BlendMode::ColorDodge,
537 BlendMode::ColorBurn,
538 BlendMode::HardLight,
539 BlendMode::SoftLight,
540 BlendMode::Difference,
541 BlendMode::Exclusion,
542 BlendMode::Multiply,
543 BlendMode::Hue,
544 BlendMode::Saturation,
545 BlendMode::Color,
546 BlendMode::Luminosity,
547 ];
548}
549
550#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
551pub enum CompositingStrategy {
552 #[default]
554 Auto,
555 Offscreen,
557 ModulateAlpha,
559}
560
561#[derive(Clone, Debug, PartialEq)]
562pub enum DrawPrimitive {
563 Content,
566 Blend {
568 primitive: Box<DrawPrimitive>,
569 blend_mode: BlendMode,
570 },
571 Rect {
572 rect: Rect,
573 brush: Brush,
574 stroke: Option<Stroke>,
577 },
578 RoundRect {
579 rect: Rect,
580 brush: Brush,
581 radii: CornerRadii,
582 stroke: Option<Stroke>,
585 },
586 Arc {
596 rect: Rect,
600 brush: Brush,
601 center: Point,
602 radius: f32,
603 start_angle: f32,
604 sweep_angle: f32,
605 stroke: Option<Stroke>,
606 inner_radius: f32,
608 },
609 Line {
612 rect: Rect,
615 brush: Brush,
616 start: Point,
617 end: Point,
618 stroke: Stroke,
619 },
620 Trapezoid {
623 rect: Rect,
626 brush: Brush,
627 trapezoid: Trapezoid,
628 },
629 Image {
630 rect: Rect,
631 image: ImageBitmap,
632 alpha: f32,
633 color_filter: Option<ColorFilter>,
634 sampling: ImageSampling,
635 src_rect: Option<Rect>,
639 },
640 Text(Box<TextPrimitive>),
642 Shadow(ShadowPrimitive),
645}
646
647#[derive(Clone, Debug, PartialEq)]
656pub struct TextPrimitive {
657 pub rect: Rect,
660 pub text: std::rc::Rc<str>,
663 pub style: DrawTextStyle,
664 pub color: Color,
668}
669
670fn shared_text_str(text: &str) -> Rc<str> {
680 use std::hash::{Hash, Hasher};
681
682 const POOL_CAPACITY: usize = 256;
683
684 let mut hasher = crate::FxHasher::default();
685 text.hash(&mut hasher);
686 let key = hasher.finish();
687
688 crate::thread_state::with(|state| {
689 let mut pool = state.shared_texts.borrow_mut();
690 if let Some(shared) = pool.get(&key)
691 && &**shared == text
692 {
693 return Rc::clone(shared);
694 }
695 let shared: Rc<str> = Rc::from(text);
696 if pool.len() >= POOL_CAPACITY {
697 pool.clear();
698 }
699 pool.insert(key, Rc::clone(&shared));
700 shared
701 })
702}
703
704impl DrawPrimitive {
706 pub fn translate(self, dx: f32, dy: f32) -> Self {
709 match self {
710 DrawPrimitive::Content => DrawPrimitive::Content,
711 DrawPrimitive::Blend {
712 primitive,
713 blend_mode,
714 } => DrawPrimitive::Blend {
715 primitive: Box::new(primitive.translate(dx, dy)),
716 blend_mode,
717 },
718 DrawPrimitive::Rect {
719 rect,
720 brush,
721 stroke,
722 } => DrawPrimitive::Rect {
723 rect: rect.translate(dx, dy),
724 brush,
725 stroke,
726 },
727 DrawPrimitive::RoundRect {
728 rect,
729 brush,
730 radii,
731 stroke,
732 } => DrawPrimitive::RoundRect {
733 rect: rect.translate(dx, dy),
734 brush,
735 radii,
736 stroke,
737 },
738 DrawPrimitive::Arc {
739 rect,
740 brush,
741 center,
742 radius,
743 start_angle,
744 sweep_angle,
745 stroke,
746 inner_radius,
747 } => DrawPrimitive::Arc {
748 rect: rect.translate(dx, dy),
749 brush,
750 center: Point::new(center.x + dx, center.y + dy),
751 radius,
752 start_angle,
753 sweep_angle,
754 stroke,
755 inner_radius,
756 },
757 DrawPrimitive::Line {
758 rect,
759 brush,
760 start,
761 end,
762 stroke,
763 } => DrawPrimitive::Line {
764 rect: rect.translate(dx, dy),
765 brush,
766 start: Point::new(start.x + dx, start.y + dy),
767 end: Point::new(end.x + dx, end.y + dy),
768 stroke,
769 },
770 DrawPrimitive::Trapezoid {
771 rect,
772 brush,
773 trapezoid,
774 } => DrawPrimitive::Trapezoid {
775 rect: rect.translate(dx, dy),
776 brush,
777 trapezoid: trapezoid.translate(dx, dy),
778 },
779 DrawPrimitive::Image {
780 rect,
781 image,
782 alpha,
783 color_filter,
784 sampling,
785 src_rect,
786 } => DrawPrimitive::Image {
787 rect: rect.translate(dx, dy),
788 image,
789 alpha,
790 color_filter,
791 sampling,
792 src_rect,
793 },
794 DrawPrimitive::Text(mut text) => {
795 text.rect = text.rect.translate(dx, dy);
796 DrawPrimitive::Text(text)
797 }
798 DrawPrimitive::Shadow(ShadowPrimitive::Drop {
799 shape,
800 cutout,
801 blur_radius,
802 blend_mode,
803 }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
804 shape: Box::new(shape.translate(dx, dy)),
805 cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
806 blur_radius,
807 blend_mode,
808 }),
809 DrawPrimitive::Shadow(ShadowPrimitive::Inner {
810 fill,
811 cutout,
812 blur_radius,
813 blend_mode,
814 clip_rect,
815 }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
816 fill: Box::new(fill.translate(dx, dy)),
817 cutout: Box::new(cutout.translate(dx, dy)),
818 blur_radius,
819 blend_mode,
820 clip_rect: clip_rect.translate(dx, dy),
821 }),
822 }
823 }
824}
825
826#[derive(Clone, Debug, PartialEq)]
827pub enum ShadowPrimitive {
828 Drop {
832 shape: Box<DrawPrimitive>,
833 cutout: Option<Box<DrawPrimitive>>,
834 blur_radius: f32,
835 blend_mode: BlendMode,
836 },
837 Inner {
839 fill: Box<DrawPrimitive>,
840 cutout: Box<DrawPrimitive>,
841 blur_radius: f32,
842 blend_mode: BlendMode,
843 clip_rect: Rect,
845 },
846}
847
848pub trait DrawScope {
849 fn size(&self) -> Size;
850 fn draw_content(&mut self);
851 fn draw_rect(&mut self, brush: Brush);
852 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
853 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
855 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
856 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
857 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
858 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
860 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
861 fn draw_circle_blend(
862 &mut self,
863 brush: Brush,
864 center: Point,
865 radius: f32,
866 blend_mode: BlendMode,
867 );
868
869 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
871 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
872 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
874 fn draw_rect_at_stroked_blend(
875 &mut self,
876 rect: Rect,
877 brush: Brush,
878 stroke: Stroke,
879 blend_mode: BlendMode,
880 );
881 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
883 fn draw_round_rect_stroked_blend(
884 &mut self,
885 brush: Brush,
886 radii: CornerRadii,
887 stroke: Stroke,
888 blend_mode: BlendMode,
889 );
890 fn draw_round_rect_at_stroked(
892 &mut self,
893 rect: Rect,
894 brush: Brush,
895 radii: CornerRadii,
896 stroke: Stroke,
897 );
898 fn draw_round_rect_at_stroked_blend(
899 &mut self,
900 rect: Rect,
901 brush: Brush,
902 radii: CornerRadii,
903 stroke: Stroke,
904 blend_mode: BlendMode,
905 );
906 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
909 fn draw_circle_stroked_blend(
910 &mut self,
911 brush: Brush,
912 center: Point,
913 radius: f32,
914 stroke: Stroke,
915 blend_mode: BlendMode,
916 );
917
918 fn draw_arc(
931 &mut self,
932 brush: Brush,
933 center: Point,
934 radius: f32,
935 start_angle: f32,
936 sweep_angle: f32,
937 stroke: Stroke,
938 );
939 #[expect(clippy::too_many_arguments)]
940 fn draw_arc_blend(
941 &mut self,
942 brush: Brush,
943 center: Point,
944 radius: f32,
945 start_angle: f32,
946 sweep_angle: f32,
947 stroke: Stroke,
948 blend_mode: BlendMode,
949 );
950
951 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke);
957 fn draw_line_blend(
958 &mut self,
959 brush: Brush,
960 start: Point,
961 end: Point,
962 stroke: Stroke,
963 blend_mode: BlendMode,
964 );
965
966 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle);
975 fn draw_path_blend(
976 &mut self,
977 path: &Path,
978 brush: Brush,
979 style: DrawStyle,
980 blend_mode: BlendMode,
981 );
982
983 fn draw_annular_sector(
992 &mut self,
993 brush: Brush,
994 center: Point,
995 inner_radius: f32,
996 outer_radius: f32,
997 start_angle: f32,
998 sweep_angle: f32,
999 );
1000 #[expect(clippy::too_many_arguments)]
1001 fn draw_annular_sector_blend(
1002 &mut self,
1003 brush: Brush,
1004 center: Point,
1005 inner_radius: f32,
1006 outer_radius: f32,
1007 start_angle: f32,
1008 sweep_angle: f32,
1009 blend_mode: BlendMode,
1010 );
1011
1012 fn draw_image(&mut self, image: ImageBitmap);
1013 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
1014 fn draw_image_at(
1015 &mut self,
1016 rect: Rect,
1017 image: ImageBitmap,
1018 alpha: f32,
1019 color_filter: Option<ColorFilter>,
1020 );
1021 fn draw_image_at_sampled(
1022 &mut self,
1023 rect: Rect,
1024 image: ImageBitmap,
1025 alpha: f32,
1026 color_filter: Option<ColorFilter>,
1027 sampling: ImageSampling,
1028 );
1029 fn draw_image_at_blend(
1030 &mut self,
1031 rect: Rect,
1032 image: ImageBitmap,
1033 alpha: f32,
1034 color_filter: Option<ColorFilter>,
1035 blend_mode: BlendMode,
1036 );
1037 fn draw_image_src(
1040 &mut self,
1041 image: ImageBitmap,
1042 src_rect: Rect,
1043 dst_rect: Rect,
1044 alpha: f32,
1045 color_filter: Option<ColorFilter>,
1046 );
1047 fn draw_image_src_sampled(
1048 &mut self,
1049 image: ImageBitmap,
1050 src_rect: Rect,
1051 dst_rect: Rect,
1052 alpha: f32,
1053 color_filter: Option<ColorFilter>,
1054 sampling: ImageSampling,
1055 );
1056 fn draw_image_src_blend(
1057 &mut self,
1058 image: ImageBitmap,
1059 src_rect: Rect,
1060 dst_rect: Rect,
1061 alpha: f32,
1062 color_filter: Option<ColorFilter>,
1063 blend_mode: BlendMode,
1064 );
1065 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1074 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1080 if let Ok(path) = crate::VectorPath::parse(d) {
1081 self.draw_vector_path(&path, brush);
1082 }
1083 }
1084
1085 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1092
1093 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1096 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1097 }
1098
1099 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1106
1107 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1113 if text.is_empty() {
1114 return;
1115 }
1116 let measurement = self.measure_text(text, style);
1117 self.draw_text_at(
1118 Rect::from_origin_size(top_left, measurement.size),
1119 brush,
1120 text,
1121 &DrawTextStyle {
1122 align: TextAlign::Left,
1123 vertical_align: TextVerticalAlign::Top,
1124 ..style.clone()
1125 },
1126 );
1127 }
1128
1129 fn into_primitives(self) -> Vec<DrawPrimitive>;
1130}
1131
1132pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1138 let x = match style.align {
1139 TextAlign::Left => rect.x,
1140 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1141 TextAlign::Right => rect.x + rect.width - measurement.size.width,
1142 };
1143 let y = match style.vertical_align {
1144 TextVerticalAlign::Top => rect.y,
1145 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1146 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1147 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1148 };
1149 Point::new(x, y)
1150}
1151
1152#[derive(Default)]
1153pub struct DrawScopeDefault {
1154 size: Size,
1155 recording: CommandRecorder,
1156 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1157}
1158
1159impl DrawScopeDefault {
1160 pub fn new(size: Size) -> Self {
1161 Self::with_storage(size, None, CommandRecording::default())
1162 }
1163
1164 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1169 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1170 }
1171
1172 pub fn with_text_measurer_reusing(
1175 size: Size,
1176 text_measurer: Rc<dyn DrawTextMeasurer>,
1177 storage: CommandRecording,
1178 ) -> Self {
1179 Self::with_storage(size, Some(text_measurer), storage)
1180 }
1181
1182 fn with_storage(
1183 size: Size,
1184 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1185 recording: CommandRecording,
1186 ) -> Self {
1187 let recording = CommandRecorder::reusing(recording);
1188 Self {
1189 size,
1190 recording,
1191 text_measurer,
1192 }
1193 }
1194
1195 pub fn content_marker_count(&self) -> u32 {
1197 self.recording.content_markers()
1198 }
1199
1200 pub fn inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault)) {
1204 let inner = insets.inset_rect(self.size);
1205 let mut scope = Self::with_storage(
1206 Size {
1207 width: inner.width,
1208 height: inner.height,
1209 },
1210 self.text_measurer.clone(),
1211 CommandRecording::default(),
1212 );
1213 draw(&mut scope);
1214 let recording = scope.finish();
1215 self.push_recorded(
1216 recording
1217 .primitives_with_markers()
1218 .map(|primitive| primitive.translate(inner.x, inner.y)),
1219 );
1220 }
1221
1222 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1224 for primitive in primitives {
1225 self.recording.push_primitive(primitive);
1226 }
1227 }
1228
1229 pub fn finish(self) -> CommandRecording {
1232 self.recording.finish()
1233 }
1234
1235 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1236 if blend_mode != BlendMode::SrcOver {
1237 self.recording.push_other(DrawPrimitive::Blend {
1238 primitive: Box::new(primitive),
1239 blend_mode,
1240 });
1241 return;
1242 }
1243 self.recording.push_other(primitive);
1244 }
1245
1246 #[expect(clippy::too_many_arguments)]
1247 #[inline]
1248 fn push_arc(
1249 &mut self,
1250 brush: Brush,
1251 center: Point,
1252 radius: f32,
1253 start_angle: f32,
1254 sweep_angle: f32,
1255 stroke: Option<Stroke>,
1256 inner_radius: f32,
1257 blend_mode: BlendMode,
1258 ) {
1259 let args = ArcRecordArgs {
1260 brush: &brush,
1261 center,
1262 radius,
1263 start_angle,
1264 sweep_angle,
1265 stroke,
1266 inner_radius,
1267 blend_mode,
1268 };
1269 let geometry = normalized_band(&args);
1270 if geometry.is_degenerate() {
1271 return;
1272 }
1273 self.recording.push_scope_arc(&args, &geometry);
1274 }
1275
1276 fn push_path_fill(&mut self, path: &Path, brush: &Brush, blend_mode: BlendMode) {
1280 let brush_rect = Rect::from_size(self.size);
1281 let recording = &mut self.recording;
1282 let sliced = crate::thread_state::with(|state| {
1283 let mut slicer = state.path_slicer.borrow_mut();
1284 let contours = path
1285 .contours()
1286 .iter()
1287 .map(|contour| contour.points.as_slice());
1288 slicer
1289 .slice(contours, PathFillRule::NonZero)
1290 .map(|slices| recording.push_trapezoids(brush_rect, slices, brush, blend_mode))
1291 .is_some()
1292 });
1293 if !sliced {
1294 self.push_vector_path(
1295 &path.to_vector_path(PathFillRule::NonZero),
1296 brush,
1297 blend_mode,
1298 );
1299 }
1300 }
1301
1302 fn push_vector_path(&mut self, path: &crate::VectorPath, brush: &Brush, blend_mode: BlendMode) {
1305 const SUPERSAMPLE: f32 = 2.0;
1306 const MAX_MASK_PIXELS: f32 = 4096.0;
1307
1308 if path.is_empty() {
1309 return;
1310 }
1311 let bounds = path.bounds();
1312 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1313 return;
1314 }
1315
1316 let color = match brush {
1317 Brush::Solid(color) => *color,
1318 Brush::LinearGradient { colors, .. }
1319 | Brush::RadialGradient { colors, .. }
1320 | Brush::SweepGradient { colors, .. } => match colors.first() {
1321 Some(color) => *color,
1322 None => return,
1323 },
1324 };
1325 if color.3 <= 0.0 {
1326 return;
1327 }
1328
1329 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1330 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1331 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1332 let mask_width = (rect_width * SUPERSAMPLE)
1333 .ceil()
1334 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1335 let mask_height = (rect_height * SUPERSAMPLE)
1336 .ceil()
1337 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1338
1339 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1340 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1341 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1342 let alpha = color.3.clamp(0.0, 1.0);
1343 let key = vector_path_mask_key(
1344 path,
1345 origin,
1346 (mask_width, mask_height),
1347 [red, green, blue],
1348 alpha,
1349 );
1350 let cached = vector_path_mask_cache_get(key);
1351 let image = match cached {
1352 Some(image) => image,
1353 None => {
1354 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE, alpha);
1355 let Ok(image) = ImageBitmap::from_alpha8(
1356 mask_width as u32,
1357 mask_height as u32,
1358 [red, green, blue],
1359 mask,
1360 ) else {
1361 return;
1362 };
1363 vector_path_mask_cache_put(key, image.clone());
1364 image
1365 }
1366 };
1367
1368 let image = DrawPrimitive::Image {
1369 rect: Rect {
1370 x: origin.x,
1371 y: origin.y,
1372 width: rect_width,
1373 height: rect_height,
1374 },
1375 image,
1376 alpha: 1.0,
1377 color_filter: None,
1378 sampling: ImageSampling::Linear,
1379 src_rect: None,
1380 };
1381 self.recording
1382 .push_other(if blend_mode == BlendMode::SrcOver {
1383 image
1384 } else {
1385 DrawPrimitive::Blend {
1386 primitive: Box::new(image),
1387 blend_mode,
1388 }
1389 });
1390 }
1391}
1392
1393impl DrawScope for DrawScopeDefault {
1394 fn size(&self) -> Size {
1395 self.size
1396 }
1397
1398 fn draw_content(&mut self) {
1399 self.recording.push_content();
1400 }
1401
1402 fn draw_rect(&mut self, brush: Brush) {
1403 self.draw_rect_blend(brush, BlendMode::SrcOver);
1404 }
1405
1406 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1407 self.recording
1408 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1409 }
1410
1411 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1412 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1413 }
1414
1415 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1416 self.recording.push_rect(rect, &brush, None, blend_mode);
1417 }
1418
1419 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1420 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1421 }
1422
1423 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1424 self.recording
1425 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1426 }
1427
1428 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1429 self.recording
1430 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1431 }
1432
1433 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1434 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1435 }
1436
1437 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1438 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1439 }
1440
1441 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1442 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1443 }
1444
1445 fn draw_rect_at_stroked_blend(
1446 &mut self,
1447 rect: Rect,
1448 brush: Brush,
1449 stroke: Stroke,
1450 blend_mode: BlendMode,
1451 ) {
1452 if !stroke.is_visible() {
1453 return;
1454 }
1455 self.recording
1456 .push_rect(rect, &brush, Some(stroke), blend_mode);
1457 }
1458
1459 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1460 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1461 }
1462
1463 fn draw_round_rect_stroked_blend(
1464 &mut self,
1465 brush: Brush,
1466 radii: CornerRadii,
1467 stroke: Stroke,
1468 blend_mode: BlendMode,
1469 ) {
1470 self.draw_round_rect_at_stroked_blend(
1471 Rect::from_size(self.size),
1472 brush,
1473 radii,
1474 stroke,
1475 blend_mode,
1476 );
1477 }
1478
1479 fn draw_round_rect_at_stroked(
1480 &mut self,
1481 rect: Rect,
1482 brush: Brush,
1483 radii: CornerRadii,
1484 stroke: Stroke,
1485 ) {
1486 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1487 }
1488
1489 fn draw_round_rect_at_stroked_blend(
1490 &mut self,
1491 rect: Rect,
1492 brush: Brush,
1493 radii: CornerRadii,
1494 stroke: Stroke,
1495 blend_mode: BlendMode,
1496 ) {
1497 if !stroke.is_visible() {
1498 return;
1499 }
1500 self.recording
1501 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1502 }
1503
1504 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1505 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1506 }
1507
1508 fn draw_circle_stroked_blend(
1509 &mut self,
1510 brush: Brush,
1511 center: Point,
1512 radius: f32,
1513 stroke: Stroke,
1514 blend_mode: BlendMode,
1515 ) {
1516 if !stroke.is_visible() || !radius.is_finite() {
1517 return;
1518 }
1519 let radius = radius.max(0.0);
1520 let diameter = radius * 2.0;
1521 self.draw_round_rect_at_stroked_blend(
1522 Rect {
1523 x: center.x - radius,
1524 y: center.y - radius,
1525 width: diameter,
1526 height: diameter,
1527 },
1528 brush,
1529 CornerRadii::uniform(radius),
1530 stroke,
1531 blend_mode,
1532 );
1533 }
1534
1535 fn draw_arc(
1536 &mut self,
1537 brush: Brush,
1538 center: Point,
1539 radius: f32,
1540 start_angle: f32,
1541 sweep_angle: f32,
1542 stroke: Stroke,
1543 ) {
1544 self.draw_arc_blend(
1545 brush,
1546 center,
1547 radius,
1548 start_angle,
1549 sweep_angle,
1550 stroke,
1551 BlendMode::SrcOver,
1552 );
1553 }
1554
1555 fn draw_arc_blend(
1556 &mut self,
1557 brush: Brush,
1558 center: Point,
1559 radius: f32,
1560 start_angle: f32,
1561 sweep_angle: f32,
1562 stroke: Stroke,
1563 blend_mode: BlendMode,
1564 ) {
1565 if !stroke.is_visible() {
1566 return;
1567 }
1568 self.push_arc(
1569 brush,
1570 center,
1571 radius,
1572 start_angle,
1573 sweep_angle,
1574 Some(stroke),
1575 0.0,
1576 blend_mode,
1577 );
1578 }
1579
1580 fn draw_line(&mut self, brush: Brush, start: Point, end: Point, stroke: Stroke) {
1581 self.draw_line_blend(brush, start, end, stroke, BlendMode::SrcOver);
1582 }
1583
1584 fn draw_line_blend(
1585 &mut self,
1586 brush: Brush,
1587 start: Point,
1588 end: Point,
1589 stroke: Stroke,
1590 blend_mode: BlendMode,
1591 ) {
1592 let line = LineGeometry::new(start, end, stroke);
1593 if line.is_degenerate() {
1594 return;
1595 }
1596 self.recording.push_line(&line, &brush, stroke, blend_mode);
1597 }
1598
1599 fn draw_path(&mut self, path: &Path, brush: Brush, style: DrawStyle) {
1600 self.draw_path_blend(path, brush, style, BlendMode::SrcOver);
1601 }
1602
1603 fn draw_path_blend(
1604 &mut self,
1605 path: &Path,
1606 brush: Brush,
1607 style: DrawStyle,
1608 blend_mode: BlendMode,
1609 ) {
1610 let (stroke, dash) = match style {
1611 DrawStyle::Fill => {
1612 self.push_path_fill(path, &brush, blend_mode);
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;