1use std::{ops::AddAssign, rc::Rc};
4
5use crate::{
6 ArcRecordArgs, Brush, Color, ColorFilter, CommandRecorder, CommandRecording, ImageBitmap,
7 ImageSampling, normalized_band,
8 stroke::Stroke,
9 typography::{
10 DrawTextMeasurer, DrawTextStyle, TextAlign, TextMeasurement, TextVerticalAlign,
11 estimate_text_measurement,
12 },
13};
14
15const VECTOR_PATH_MASK_CACHE_ENTRIES: usize = 96;
16const VECTOR_PATH_MASK_CACHE_BYTES: usize = 8 * 1024 * 1024;
17
18struct VectorPathMaskCache {
19 entries: Vec<(u64, ImageBitmap)>,
20 bytes: usize,
21}
22
23impl VectorPathMaskCache {
24 const fn new() -> Self {
25 Self {
26 entries: Vec::new(),
27 bytes: 0,
28 }
29 }
30
31 fn get(&mut self, key: u64) -> Option<ImageBitmap> {
32 let index = self.entries.iter().position(|(seen, _)| *seen == key)?;
33 let entry = self.entries.remove(index);
34 let image = entry.1.clone();
35 self.entries.push(entry);
36 Some(image)
37 }
38
39 fn put(&mut self, key: u64, image: ImageBitmap) {
40 let bytes = image.width() as usize * image.height() as usize * 4;
41 if bytes > VECTOR_PATH_MASK_CACHE_BYTES {
42 return;
43 }
44 self.bytes += bytes;
45 self.entries.push((key, image));
46 while self.entries.len() > VECTOR_PATH_MASK_CACHE_ENTRIES
47 || self.bytes > VECTOR_PATH_MASK_CACHE_BYTES
48 {
49 let (_, dropped) = self.entries.remove(0);
50 self.bytes = self
51 .bytes
52 .saturating_sub(dropped.width() as usize * dropped.height() as usize * 4);
53 }
54 }
55}
56
57thread_local! {
58 static VECTOR_PATH_MASKS: std::cell::RefCell<VectorPathMaskCache> =
59 const { std::cell::RefCell::new(VectorPathMaskCache::new()) };
60}
61
62fn vector_path_mask_key(
63 path: &crate::VectorPath,
64 origin: Point,
65 mask_size: (usize, usize),
66 rgb: [u8; 3],
67 alpha: f32,
68) -> u64 {
69 use std::hash::Hasher;
70 let mut hasher = crate::fx_hash::FxHasher::default();
71 hasher.write_u8(path.fill_rule() as u8);
72 hasher.write_u32(origin.x.to_bits());
73 hasher.write_u32(origin.y.to_bits());
74 hasher.write_usize(mask_size.0);
75 hasher.write_usize(mask_size.1);
76 hasher.write(&rgb);
77 hasher.write_u32(alpha.to_bits());
78 for subpath in path.subpaths() {
79 hasher.write_usize(subpath.len());
80 for point in subpath {
81 hasher.write_u32(point.x.to_bits());
82 hasher.write_u32(point.y.to_bits());
83 }
84 }
85 hasher.finish()
86}
87
88fn vector_path_mask_cache_get(key: u64) -> Option<ImageBitmap> {
89 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().get(key))
90}
91
92fn vector_path_mask_cache_put(key: u64, image: ImageBitmap) {
93 VECTOR_PATH_MASKS.with(|cache| cache.borrow_mut().put(key, image));
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Default)]
97pub struct Point {
98 pub x: f32,
99 pub y: f32,
100}
101
102impl Point {
103 pub const fn new(x: f32, y: f32) -> Self {
104 Self { x, y }
105 }
106
107 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Default)]
111pub struct Size {
112 pub width: f32,
113 pub height: f32,
114}
115
116impl Size {
117 pub const fn new(width: f32, height: f32) -> Self {
118 Self { width, height }
119 }
120
121 pub const ZERO: Size = Size {
122 width: 0.0,
123 height: 0.0,
124 };
125}
126
127#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct Rect {
129 pub x: f32,
130 pub y: f32,
131 pub width: f32,
132 pub height: f32,
133}
134
135impl Rect {
136 pub const EMPTY: Rect = Rect {
140 x: 0.0,
141 y: 0.0,
142 width: 0.0,
143 height: 0.0,
144 };
145
146 pub fn from_origin_size(origin: Point, size: Size) -> Self {
147 Self {
148 x: origin.x,
149 y: origin.y,
150 width: size.width,
151 height: size.height,
152 }
153 }
154
155 pub fn from_size(size: Size) -> Self {
156 Self {
157 x: 0.0,
158 y: 0.0,
159 width: size.width,
160 height: size.height,
161 }
162 }
163
164 pub fn translate(&self, dx: f32, dy: f32) -> Self {
165 Self {
166 x: self.x + dx,
167 y: self.y + dy,
168 width: self.width,
169 height: self.height,
170 }
171 }
172
173 pub fn contains(&self, x: f32, y: f32) -> bool {
174 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
175 }
176
177 pub fn is_empty(&self) -> bool {
179 self.width <= 0.0 || self.height <= 0.0
180 }
181
182 pub fn intersect(&self, other: Rect) -> Option<Rect> {
184 let left = self.x.max(other.x);
185 let top = self.y.max(other.y);
186 let right = (self.x + self.width).min(other.x + other.width);
187 let bottom = (self.y + self.height).min(other.y + other.height);
188 let width = right - left;
189 let height = bottom - top;
190 if width <= 0.0 || height <= 0.0 {
191 None
192 } else {
193 Some(Rect {
194 x: left,
195 y: top,
196 width,
197 height,
198 })
199 }
200 }
201
202 pub fn union(&self, other: Rect) -> Rect {
203 let left = self.x.min(other.x);
204 let top = self.y.min(other.y);
205 let right = (self.x + self.width).max(other.x + other.width);
206 let bottom = (self.y + self.height).max(other.y + other.height);
207 Rect {
208 x: left,
209 y: top,
210 width: (right - left).max(0.0),
211 height: (bottom - top).max(0.0),
212 }
213 }
214}
215
216#[derive(Clone, Copy, Debug, Default, PartialEq)]
218pub struct EdgeInsets {
219 pub left: f32,
220 pub top: f32,
221 pub right: f32,
222 pub bottom: f32,
223}
224
225impl EdgeInsets {
226 pub fn uniform(all: f32) -> Self {
227 Self {
228 left: all,
229 top: all,
230 right: all,
231 bottom: all,
232 }
233 }
234
235 pub fn horizontal(horizontal: f32) -> Self {
236 Self {
237 left: horizontal,
238 right: horizontal,
239 ..Self::default()
240 }
241 }
242
243 pub fn vertical(vertical: f32) -> Self {
244 Self {
245 top: vertical,
246 bottom: vertical,
247 ..Self::default()
248 }
249 }
250
251 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
252 Self {
253 left: horizontal,
254 right: horizontal,
255 top: vertical,
256 bottom: vertical,
257 }
258 }
259
260 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
261 Self {
262 left,
263 top,
264 right,
265 bottom,
266 }
267 }
268
269 pub fn is_zero(&self) -> bool {
270 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
271 }
272
273 pub fn horizontal_sum(&self) -> f32 {
274 self.left + self.right
275 }
276
277 pub fn vertical_sum(&self) -> f32 {
278 self.top + self.bottom
279 }
280}
281
282impl AddAssign for EdgeInsets {
283 fn add_assign(&mut self, rhs: Self) {
284 self.left += rhs.left;
285 self.top += rhs.top;
286 self.right += rhs.right;
287 self.bottom += rhs.bottom;
288 }
289}
290
291#[derive(Clone, Copy, Debug, Default, PartialEq)]
292pub struct CornerRadii {
293 pub top_left: f32,
294 pub top_right: f32,
295 pub bottom_right: f32,
296 pub bottom_left: f32,
297}
298
299impl CornerRadii {
300 pub fn uniform(radius: f32) -> Self {
301 Self {
302 top_left: radius,
303 top_right: radius,
304 bottom_right: radius,
305 bottom_left: radius,
306 }
307 }
308}
309
310#[derive(Clone, Copy, Debug, PartialEq)]
311pub struct RoundedCornerShape {
312 radii: CornerRadii,
313}
314
315impl RoundedCornerShape {
316 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
317 Self {
318 radii: CornerRadii {
319 top_left,
320 top_right,
321 bottom_right,
322 bottom_left,
323 },
324 }
325 }
326
327 pub fn uniform(radius: f32) -> Self {
328 Self {
329 radii: CornerRadii::uniform(radius),
330 }
331 }
332
333 pub fn with_radii(radii: CornerRadii) -> Self {
334 Self { radii }
335 }
336
337 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
338 let mut resolved = self.radii;
339 let max_width = (width / 2.0).max(0.0);
340 let max_height = (height / 2.0).max(0.0);
341 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
342 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
343 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
344 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
345 resolved
346 }
347
348 pub fn radii(&self) -> CornerRadii {
349 self.radii
350 }
351}
352
353#[derive(Clone, Copy, Debug, PartialEq)]
354pub struct TransformOrigin {
355 pub pivot_fraction_x: f32,
356 pub pivot_fraction_y: f32,
357}
358
359impl TransformOrigin {
360 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
361 Self {
362 pivot_fraction_x,
363 pivot_fraction_y,
364 }
365 }
366
367 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
368}
369
370impl Default for TransformOrigin {
371 fn default() -> Self {
372 Self::CENTER
373 }
374}
375
376#[derive(Clone, Copy, Debug, Default, PartialEq)]
377pub enum LayerShape {
378 #[default]
379 Rectangle,
380 Rounded(RoundedCornerShape),
381}
382
383#[derive(Clone, Debug, PartialEq)]
384pub struct GraphicsLayer {
385 pub alpha: f32,
386 pub scale: f32,
387 pub scale_x: f32,
388 pub scale_y: f32,
389 pub rotation_x: f32,
390 pub rotation_y: f32,
391 pub rotation_z: f32,
392 pub camera_distance: f32,
393 pub transform_origin: TransformOrigin,
394 pub translation_x: f32,
395 pub translation_y: f32,
396 pub shadow_elevation: f32,
397 pub ambient_shadow_color: Color,
398 pub spot_shadow_color: Color,
399 pub shape: LayerShape,
400 pub clip: bool,
401 pub compositing_strategy: CompositingStrategy,
402 pub blend_mode: BlendMode,
403 pub color_filter: Option<ColorFilter>,
404 pub render_effect: Option<crate::render_effect::RenderEffect>,
405 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
406}
407
408impl GraphicsLayer {
409 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
433 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
434 }
435}
436
437impl Default for GraphicsLayer {
438 fn default() -> Self {
439 Self {
440 alpha: 1.0,
441 scale: 1.0,
442 scale_x: 1.0,
443 scale_y: 1.0,
444 rotation_x: 0.0,
445 rotation_y: 0.0,
446 rotation_z: 0.0,
447 camera_distance: 8.0,
448 transform_origin: TransformOrigin::CENTER,
449 translation_x: 0.0,
450 translation_y: 0.0,
451 shadow_elevation: 0.0,
452 ambient_shadow_color: Color::BLACK,
453 spot_shadow_color: Color::BLACK,
454 shape: LayerShape::Rectangle,
455 clip: false,
456 compositing_strategy: CompositingStrategy::Auto,
457 blend_mode: BlendMode::SrcOver,
458 color_filter: None,
459 render_effect: None,
460 backdrop_effect: None,
461 }
462 }
463}
464
465#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
470pub enum BlendMode {
471 Clear,
472 Src,
473 Dst,
474 #[default]
475 SrcOver,
476 DstOver,
477 SrcIn,
478 DstIn,
479 SrcOut,
480 DstOut,
481 SrcAtop,
482 DstAtop,
483 Xor,
484 Plus,
485 Modulate,
486 Screen,
487 Overlay,
488 Darken,
489 Lighten,
490 ColorDodge,
491 ColorBurn,
492 HardLight,
493 SoftLight,
494 Difference,
495 Exclusion,
496 Multiply,
497 Hue,
498 Saturation,
499 Color,
500 Luminosity,
501}
502
503impl BlendMode {
505 pub const ALL: [BlendMode; 29] = [
507 BlendMode::Clear,
508 BlendMode::Src,
509 BlendMode::Dst,
510 BlendMode::SrcOver,
511 BlendMode::DstOver,
512 BlendMode::SrcIn,
513 BlendMode::DstIn,
514 BlendMode::SrcOut,
515 BlendMode::DstOut,
516 BlendMode::SrcAtop,
517 BlendMode::DstAtop,
518 BlendMode::Xor,
519 BlendMode::Plus,
520 BlendMode::Modulate,
521 BlendMode::Screen,
522 BlendMode::Overlay,
523 BlendMode::Darken,
524 BlendMode::Lighten,
525 BlendMode::ColorDodge,
526 BlendMode::ColorBurn,
527 BlendMode::HardLight,
528 BlendMode::SoftLight,
529 BlendMode::Difference,
530 BlendMode::Exclusion,
531 BlendMode::Multiply,
532 BlendMode::Hue,
533 BlendMode::Saturation,
534 BlendMode::Color,
535 BlendMode::Luminosity,
536 ];
537}
538
539#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
540pub enum CompositingStrategy {
541 #[default]
543 Auto,
544 Offscreen,
546 ModulateAlpha,
548}
549
550#[derive(Clone, Debug, PartialEq)]
551pub enum DrawPrimitive {
552 Content,
555 Blend {
557 primitive: Box<DrawPrimitive>,
558 blend_mode: BlendMode,
559 },
560 Rect {
561 rect: Rect,
562 brush: Brush,
563 stroke: Option<Stroke>,
566 },
567 RoundRect {
568 rect: Rect,
569 brush: Brush,
570 radii: CornerRadii,
571 stroke: Option<Stroke>,
574 },
575 Arc {
585 rect: Rect,
589 brush: Brush,
590 center: Point,
591 radius: f32,
592 start_angle: f32,
593 sweep_angle: f32,
594 stroke: Option<Stroke>,
595 inner_radius: f32,
597 },
598 Image {
599 rect: Rect,
600 image: ImageBitmap,
601 alpha: f32,
602 color_filter: Option<ColorFilter>,
603 sampling: ImageSampling,
604 src_rect: Option<Rect>,
608 },
609 Text(Box<TextPrimitive>),
611 Shadow(ShadowPrimitive),
614}
615
616#[derive(Clone, Debug, PartialEq)]
625pub struct TextPrimitive {
626 pub rect: Rect,
629 pub text: std::rc::Rc<str>,
632 pub style: DrawTextStyle,
633 pub color: Color,
637}
638
639fn shared_text_str(text: &str) -> Rc<str> {
649 use std::{
650 cell::RefCell,
651 collections::HashMap,
652 hash::{Hash, Hasher},
653 };
654
655 const POOL_CAPACITY: usize = 256;
656 thread_local! {
657 static POOL: RefCell<HashMap<u64, Rc<str>>> = RefCell::new(HashMap::new());
658 }
659
660 let mut hasher = crate::FxHasher::default();
661 text.hash(&mut hasher);
662 let key = hasher.finish();
663
664 POOL.with(|pool| {
665 let mut pool = pool.borrow_mut();
666 if let Some(shared) = pool.get(&key)
667 && &**shared == text
668 {
669 return Rc::clone(shared);
670 }
671 let shared: Rc<str> = Rc::from(text);
672 if pool.len() >= POOL_CAPACITY {
673 pool.clear();
674 }
675 pool.insert(key, Rc::clone(&shared));
676 shared
677 })
678}
679
680#[derive(Clone, Debug, PartialEq)]
682pub enum ShadowPrimitive {
683 Drop {
687 shape: Box<DrawPrimitive>,
688 cutout: Option<Box<DrawPrimitive>>,
689 blur_radius: f32,
690 blend_mode: BlendMode,
691 },
692 Inner {
694 fill: Box<DrawPrimitive>,
695 cutout: Box<DrawPrimitive>,
696 blur_radius: f32,
697 blend_mode: BlendMode,
698 clip_rect: Rect,
700 },
701}
702
703pub trait DrawScope {
704 fn size(&self) -> Size;
705 fn draw_content(&mut self);
706 fn draw_rect(&mut self, brush: Brush);
707 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
708 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
710 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
711 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
712 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
713 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
715 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
716 fn draw_circle_blend(
717 &mut self,
718 brush: Brush,
719 center: Point,
720 radius: f32,
721 blend_mode: BlendMode,
722 );
723
724 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
726 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
727 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
729 fn draw_rect_at_stroked_blend(
730 &mut self,
731 rect: Rect,
732 brush: Brush,
733 stroke: Stroke,
734 blend_mode: BlendMode,
735 );
736 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
738 fn draw_round_rect_stroked_blend(
739 &mut self,
740 brush: Brush,
741 radii: CornerRadii,
742 stroke: Stroke,
743 blend_mode: BlendMode,
744 );
745 fn draw_round_rect_at_stroked(
747 &mut self,
748 rect: Rect,
749 brush: Brush,
750 radii: CornerRadii,
751 stroke: Stroke,
752 );
753 fn draw_round_rect_at_stroked_blend(
754 &mut self,
755 rect: Rect,
756 brush: Brush,
757 radii: CornerRadii,
758 stroke: Stroke,
759 blend_mode: BlendMode,
760 );
761 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
764 fn draw_circle_stroked_blend(
765 &mut self,
766 brush: Brush,
767 center: Point,
768 radius: f32,
769 stroke: Stroke,
770 blend_mode: BlendMode,
771 );
772
773 fn draw_arc(
786 &mut self,
787 brush: Brush,
788 center: Point,
789 radius: f32,
790 start_angle: f32,
791 sweep_angle: f32,
792 stroke: Stroke,
793 );
794 #[expect(clippy::too_many_arguments)]
795 fn draw_arc_blend(
796 &mut self,
797 brush: Brush,
798 center: Point,
799 radius: f32,
800 start_angle: f32,
801 sweep_angle: f32,
802 stroke: Stroke,
803 blend_mode: BlendMode,
804 );
805
806 fn draw_annular_sector(
815 &mut self,
816 brush: Brush,
817 center: Point,
818 inner_radius: f32,
819 outer_radius: f32,
820 start_angle: f32,
821 sweep_angle: f32,
822 );
823 #[expect(clippy::too_many_arguments)]
824 fn draw_annular_sector_blend(
825 &mut self,
826 brush: Brush,
827 center: Point,
828 inner_radius: f32,
829 outer_radius: f32,
830 start_angle: f32,
831 sweep_angle: f32,
832 blend_mode: BlendMode,
833 );
834
835 fn draw_image(&mut self, image: ImageBitmap);
836 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
837 fn draw_image_at(
838 &mut self,
839 rect: Rect,
840 image: ImageBitmap,
841 alpha: f32,
842 color_filter: Option<ColorFilter>,
843 );
844 fn draw_image_at_sampled(
845 &mut self,
846 rect: Rect,
847 image: ImageBitmap,
848 alpha: f32,
849 color_filter: Option<ColorFilter>,
850 sampling: ImageSampling,
851 );
852 fn draw_image_at_blend(
853 &mut self,
854 rect: Rect,
855 image: ImageBitmap,
856 alpha: f32,
857 color_filter: Option<ColorFilter>,
858 blend_mode: BlendMode,
859 );
860 fn draw_image_src(
863 &mut self,
864 image: ImageBitmap,
865 src_rect: Rect,
866 dst_rect: Rect,
867 alpha: f32,
868 color_filter: Option<ColorFilter>,
869 );
870 fn draw_image_src_sampled(
871 &mut self,
872 image: ImageBitmap,
873 src_rect: Rect,
874 dst_rect: Rect,
875 alpha: f32,
876 color_filter: Option<ColorFilter>,
877 sampling: ImageSampling,
878 );
879 fn draw_image_src_blend(
880 &mut self,
881 image: ImageBitmap,
882 src_rect: Rect,
883 dst_rect: Rect,
884 alpha: f32,
885 color_filter: Option<ColorFilter>,
886 blend_mode: BlendMode,
887 );
888 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
897 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
903 if let Ok(path) = crate::VectorPath::parse(d) {
904 self.draw_vector_path(&path, brush);
905 }
906 }
907
908 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
915
916 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
919 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
920 }
921
922 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
929
930 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
936 if text.is_empty() {
937 return;
938 }
939 let measurement = self.measure_text(text, style);
940 self.draw_text_at(
941 Rect::from_origin_size(top_left, measurement.size),
942 brush,
943 text,
944 &DrawTextStyle {
945 align: TextAlign::Left,
946 vertical_align: TextVerticalAlign::Top,
947 ..style.clone()
948 },
949 );
950 }
951
952 fn into_primitives(self) -> Vec<DrawPrimitive>;
953}
954
955pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
961 let x = match style.align {
962 TextAlign::Left => rect.x,
963 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
964 TextAlign::Right => rect.x + rect.width - measurement.size.width,
965 };
966 let y = match style.vertical_align {
967 TextVerticalAlign::Top => rect.y,
968 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
969 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
970 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
971 };
972 Point::new(x, y)
973}
974
975#[derive(Default)]
976pub struct DrawScopeDefault {
977 size: Size,
978 recording: CommandRecorder,
979 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
980}
981
982const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
983
984thread_local! {
985 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
986 std::cell::RefCell::new(std::collections::HashMap::new());
987}
988
989fn recorded_primitive_capacity(size: Size) -> usize {
990 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
991 counts
992 .borrow()
993 .get(&(size.width.to_bits(), size.height.to_bits()))
994 .copied()
995 .unwrap_or(0)
996 })
997}
998
999fn note_recorded_primitive_count(size: Size, count: usize) {
1000 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1001 let mut counts = counts.borrow_mut();
1002 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1003 counts.clear();
1004 }
1005 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1006 });
1007}
1008
1009impl DrawScopeDefault {
1010 pub fn new(size: Size) -> Self {
1011 Self::with_storage(size, None, CommandRecording::default())
1012 }
1013
1014 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1019 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1020 }
1021
1022 pub fn with_text_measurer_reusing(
1025 size: Size,
1026 text_measurer: Rc<dyn DrawTextMeasurer>,
1027 storage: CommandRecording,
1028 ) -> Self {
1029 Self::with_storage(size, Some(text_measurer), storage)
1030 }
1031
1032 fn with_storage(
1033 size: Size,
1034 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1035 recording: CommandRecording,
1036 ) -> Self {
1037 let mut recording = CommandRecorder::reusing(recording);
1038 recording.reserve_shapes(recorded_primitive_capacity(size));
1039 Self {
1040 size,
1041 recording,
1042 text_measurer,
1043 }
1044 }
1045
1046 pub fn content_marker_count(&self) -> u32 {
1048 self.recording.content_markers()
1049 }
1050
1051 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1053 for primitive in primitives {
1054 self.recording.push_primitive(primitive);
1055 }
1056 }
1057
1058 pub fn finish(self) -> CommandRecording {
1061 note_recorded_primitive_count(self.size, self.recording.len());
1062 self.recording.finish()
1063 }
1064
1065 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1066 if blend_mode != BlendMode::SrcOver {
1067 self.recording.push_other(DrawPrimitive::Blend {
1068 primitive: Box::new(primitive),
1069 blend_mode,
1070 });
1071 return;
1072 }
1073 self.recording.push_other(primitive);
1074 }
1075
1076 #[expect(clippy::too_many_arguments)]
1077 #[inline]
1078 fn push_arc(
1079 &mut self,
1080 brush: Brush,
1081 center: Point,
1082 radius: f32,
1083 start_angle: f32,
1084 sweep_angle: f32,
1085 stroke: Option<Stroke>,
1086 inner_radius: f32,
1087 blend_mode: BlendMode,
1088 ) {
1089 let args = ArcRecordArgs {
1090 brush: &brush,
1091 center,
1092 radius,
1093 start_angle,
1094 sweep_angle,
1095 stroke,
1096 inner_radius,
1097 blend_mode,
1098 };
1099 let geometry = normalized_band(&args);
1100 if geometry.is_degenerate() {
1101 return;
1102 }
1103 self.recording.push_scope_arc(&args, &geometry);
1104 }
1105}
1106
1107impl DrawScope for DrawScopeDefault {
1108 fn size(&self) -> Size {
1109 self.size
1110 }
1111
1112 fn draw_content(&mut self) {
1113 self.recording.push_content();
1114 }
1115
1116 fn draw_rect(&mut self, brush: Brush) {
1117 self.draw_rect_blend(brush, BlendMode::SrcOver);
1118 }
1119
1120 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1121 self.recording
1122 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1123 }
1124
1125 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1126 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1127 }
1128
1129 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1130 self.recording.push_rect(rect, &brush, None, blend_mode);
1131 }
1132
1133 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1134 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1135 }
1136
1137 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1138 self.recording
1139 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1140 }
1141
1142 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1143 self.recording
1144 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1145 }
1146
1147 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1148 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1149 }
1150
1151 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1152 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1153 }
1154
1155 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1156 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1157 }
1158
1159 fn draw_rect_at_stroked_blend(
1160 &mut self,
1161 rect: Rect,
1162 brush: Brush,
1163 stroke: Stroke,
1164 blend_mode: BlendMode,
1165 ) {
1166 if !stroke.is_visible() {
1167 return;
1168 }
1169 self.recording
1170 .push_rect(rect, &brush, Some(stroke), blend_mode);
1171 }
1172
1173 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1174 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1175 }
1176
1177 fn draw_round_rect_stroked_blend(
1178 &mut self,
1179 brush: Brush,
1180 radii: CornerRadii,
1181 stroke: Stroke,
1182 blend_mode: BlendMode,
1183 ) {
1184 self.draw_round_rect_at_stroked_blend(
1185 Rect::from_size(self.size),
1186 brush,
1187 radii,
1188 stroke,
1189 blend_mode,
1190 );
1191 }
1192
1193 fn draw_round_rect_at_stroked(
1194 &mut self,
1195 rect: Rect,
1196 brush: Brush,
1197 radii: CornerRadii,
1198 stroke: Stroke,
1199 ) {
1200 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1201 }
1202
1203 fn draw_round_rect_at_stroked_blend(
1204 &mut self,
1205 rect: Rect,
1206 brush: Brush,
1207 radii: CornerRadii,
1208 stroke: Stroke,
1209 blend_mode: BlendMode,
1210 ) {
1211 if !stroke.is_visible() {
1212 return;
1213 }
1214 self.recording
1215 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1216 }
1217
1218 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1219 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1220 }
1221
1222 fn draw_circle_stroked_blend(
1223 &mut self,
1224 brush: Brush,
1225 center: Point,
1226 radius: f32,
1227 stroke: Stroke,
1228 blend_mode: BlendMode,
1229 ) {
1230 if !stroke.is_visible() || !radius.is_finite() {
1231 return;
1232 }
1233 let radius = radius.max(0.0);
1234 let diameter = radius * 2.0;
1235 self.draw_round_rect_at_stroked_blend(
1236 Rect {
1237 x: center.x - radius,
1238 y: center.y - radius,
1239 width: diameter,
1240 height: diameter,
1241 },
1242 brush,
1243 CornerRadii::uniform(radius),
1244 stroke,
1245 blend_mode,
1246 );
1247 }
1248
1249 fn draw_arc(
1250 &mut self,
1251 brush: Brush,
1252 center: Point,
1253 radius: f32,
1254 start_angle: f32,
1255 sweep_angle: f32,
1256 stroke: Stroke,
1257 ) {
1258 self.draw_arc_blend(
1259 brush,
1260 center,
1261 radius,
1262 start_angle,
1263 sweep_angle,
1264 stroke,
1265 BlendMode::SrcOver,
1266 );
1267 }
1268
1269 fn draw_arc_blend(
1270 &mut self,
1271 brush: Brush,
1272 center: Point,
1273 radius: f32,
1274 start_angle: f32,
1275 sweep_angle: f32,
1276 stroke: Stroke,
1277 blend_mode: BlendMode,
1278 ) {
1279 if !stroke.is_visible() {
1280 return;
1281 }
1282 self.push_arc(
1283 brush,
1284 center,
1285 radius,
1286 start_angle,
1287 sweep_angle,
1288 Some(stroke),
1289 0.0,
1290 blend_mode,
1291 );
1292 }
1293
1294 fn draw_annular_sector(
1295 &mut self,
1296 brush: Brush,
1297 center: Point,
1298 inner_radius: f32,
1299 outer_radius: f32,
1300 start_angle: f32,
1301 sweep_angle: f32,
1302 ) {
1303 self.draw_annular_sector_blend(
1304 brush,
1305 center,
1306 inner_radius,
1307 outer_radius,
1308 start_angle,
1309 sweep_angle,
1310 BlendMode::SrcOver,
1311 );
1312 }
1313
1314 fn draw_annular_sector_blend(
1315 &mut self,
1316 brush: Brush,
1317 center: Point,
1318 inner_radius: f32,
1319 outer_radius: f32,
1320 start_angle: f32,
1321 sweep_angle: f32,
1322 blend_mode: BlendMode,
1323 ) {
1324 self.push_arc(
1325 brush,
1326 center,
1327 outer_radius,
1328 start_angle,
1329 sweep_angle,
1330 None,
1331 inner_radius,
1332 blend_mode,
1333 );
1334 }
1335
1336 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1337 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1338 }
1339
1340 fn draw_circle_blend(
1341 &mut self,
1342 brush: Brush,
1343 center: Point,
1344 radius: f32,
1345 blend_mode: BlendMode,
1346 ) {
1347 let radius = radius.max(0.0);
1348 let diameter = radius * 2.0;
1349 self.recording.push_round_rect(
1350 Rect {
1351 x: center.x - radius,
1352 y: center.y - radius,
1353 width: diameter,
1354 height: diameter,
1355 },
1356 &brush,
1357 CornerRadii::uniform(radius),
1358 None,
1359 blend_mode,
1360 );
1361 }
1362
1363 fn draw_image(&mut self, image: ImageBitmap) {
1364 self.draw_image_blend(image, BlendMode::SrcOver);
1365 }
1366
1367 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1368 self.push_blended_primitive(
1369 DrawPrimitive::Image {
1370 rect: Rect::from_size(self.size),
1371 image,
1372 alpha: 1.0,
1373 color_filter: None,
1374 sampling: ImageSampling::Nearest,
1375 src_rect: None,
1376 },
1377 blend_mode,
1378 );
1379 }
1380
1381 fn draw_image_at(
1382 &mut self,
1383 rect: Rect,
1384 image: ImageBitmap,
1385 alpha: f32,
1386 color_filter: Option<ColorFilter>,
1387 ) {
1388 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1389 }
1390
1391 fn draw_image_at_sampled(
1392 &mut self,
1393 rect: Rect,
1394 image: ImageBitmap,
1395 alpha: f32,
1396 color_filter: Option<ColorFilter>,
1397 sampling: ImageSampling,
1398 ) {
1399 self.push_blended_primitive(
1400 DrawPrimitive::Image {
1401 rect,
1402 image,
1403 alpha: alpha.clamp(0.0, 1.0),
1404 color_filter,
1405 sampling,
1406 src_rect: None,
1407 },
1408 BlendMode::SrcOver,
1409 );
1410 }
1411
1412 fn draw_image_at_blend(
1413 &mut self,
1414 rect: Rect,
1415 image: ImageBitmap,
1416 alpha: f32,
1417 color_filter: Option<ColorFilter>,
1418 blend_mode: BlendMode,
1419 ) {
1420 self.push_blended_primitive(
1421 DrawPrimitive::Image {
1422 rect,
1423 image,
1424 alpha: alpha.clamp(0.0, 1.0),
1425 color_filter,
1426 sampling: ImageSampling::Nearest,
1427 src_rect: None,
1428 },
1429 blend_mode,
1430 );
1431 }
1432
1433 fn draw_image_src(
1434 &mut self,
1435 image: ImageBitmap,
1436 src_rect: Rect,
1437 dst_rect: Rect,
1438 alpha: f32,
1439 color_filter: Option<ColorFilter>,
1440 ) {
1441 self.draw_image_src_blend(
1442 image,
1443 src_rect,
1444 dst_rect,
1445 alpha,
1446 color_filter,
1447 BlendMode::SrcOver,
1448 );
1449 }
1450
1451 fn draw_image_src_sampled(
1452 &mut self,
1453 image: ImageBitmap,
1454 src_rect: Rect,
1455 dst_rect: Rect,
1456 alpha: f32,
1457 color_filter: Option<ColorFilter>,
1458 sampling: ImageSampling,
1459 ) {
1460 self.push_blended_primitive(
1461 DrawPrimitive::Image {
1462 rect: dst_rect,
1463 image,
1464 alpha: alpha.clamp(0.0, 1.0),
1465 color_filter,
1466 sampling,
1467 src_rect: Some(src_rect),
1468 },
1469 BlendMode::SrcOver,
1470 );
1471 }
1472
1473 fn draw_image_src_blend(
1474 &mut self,
1475 image: ImageBitmap,
1476 src_rect: Rect,
1477 dst_rect: Rect,
1478 alpha: f32,
1479 color_filter: Option<ColorFilter>,
1480 blend_mode: BlendMode,
1481 ) {
1482 self.push_blended_primitive(
1483 DrawPrimitive::Image {
1484 rect: dst_rect,
1485 image,
1486 alpha: alpha.clamp(0.0, 1.0),
1487 color_filter,
1488 sampling: ImageSampling::Nearest,
1489 src_rect: Some(src_rect),
1490 },
1491 blend_mode,
1492 );
1493 }
1494
1495 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1496 const SUPERSAMPLE: f32 = 2.0;
1497 const MAX_MASK_PIXELS: f32 = 4096.0;
1498
1499 if path.is_empty() {
1500 return;
1501 }
1502 let bounds = path.bounds();
1503 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1504 return;
1505 }
1506
1507 let color = match &brush {
1508 Brush::Solid(color) => *color,
1509 Brush::LinearGradient { colors, .. }
1510 | Brush::RadialGradient { colors, .. }
1511 | Brush::SweepGradient { colors, .. } => match colors.first() {
1512 Some(color) => *color,
1513 None => return,
1514 },
1515 };
1516 if color.3 <= 0.0 {
1517 return;
1518 }
1519
1520 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1521 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1522 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1523 let mask_width = (rect_width * SUPERSAMPLE)
1524 .ceil()
1525 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1526 let mask_height = (rect_height * SUPERSAMPLE)
1527 .ceil()
1528 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1529
1530 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1531 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1532 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1533 let alpha = color.3.clamp(0.0, 1.0);
1534 let key = vector_path_mask_key(
1535 path,
1536 origin,
1537 (mask_width, mask_height),
1538 [red, green, blue],
1539 alpha,
1540 );
1541 let cached = vector_path_mask_cache_get(key);
1542 let image = match cached {
1543 Some(image) => image,
1544 None => {
1545 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1546 let mut pixels = Vec::with_capacity(mask.len() * 4);
1547 for coverage in mask {
1548 pixels.extend_from_slice(&[
1549 red,
1550 green,
1551 blue,
1552 (alpha * coverage as f32 + 0.5) as u8,
1553 ]);
1554 }
1555 let Ok(image) =
1556 ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
1557 else {
1558 return;
1559 };
1560 vector_path_mask_cache_put(key, image.clone());
1561 image
1562 }
1563 };
1564
1565 self.recording.push_other(DrawPrimitive::Image {
1566 rect: Rect {
1567 x: origin.x,
1568 y: origin.y,
1569 width: rect_width,
1570 height: rect_height,
1571 },
1572 image,
1573 alpha: 1.0,
1574 color_filter: None,
1575 sampling: ImageSampling::Linear,
1576 src_rect: None,
1577 });
1578 }
1579
1580 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1581 match &self.text_measurer {
1582 Some(measurer) => measurer.measure_text(text, style),
1583 None => estimate_text_measurement(text, style),
1584 }
1585 }
1586
1587 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1588 if text.is_empty() {
1589 return;
1590 }
1591 let Some(color) = solid_fill_color(&brush) else {
1592 return;
1593 };
1594 if color.3 <= 0.0 {
1595 return;
1596 }
1597 let measurement = self.measure_text(text, style);
1598 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1599 return;
1600 }
1601 let origin = align_text_block(rect, measurement, style);
1602 if !origin.x.is_finite() || !origin.y.is_finite() {
1603 return;
1604 }
1605 self.recording
1606 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1607 rect: Rect::from_origin_size(origin, measurement.size),
1608 text: shared_text_str(text),
1609 style: style.clone(),
1610 color,
1611 })));
1612 }
1613
1614 fn into_primitives(self) -> Vec<DrawPrimitive> {
1615 self.finish().into_primitives_with_markers()
1616 }
1617}
1618
1619fn solid_fill_color(brush: &Brush) -> Option<Color> {
1624 match brush {
1625 Brush::Solid(color) => Some(*color),
1626 Brush::LinearGradient { colors, .. }
1627 | Brush::RadialGradient { colors, .. }
1628 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1629 }
1630}
1631
1632#[cfg(test)]
1633#[path = "tests/geometry_tests.rs"]
1634mod tests;