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 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>,
417}
418
419impl GraphicsLayer {
420 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
444 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
445 }
446}
447
448impl Default for GraphicsLayer {
449 fn default() -> Self {
450 Self {
451 alpha: 1.0,
452 scale: 1.0,
453 scale_x: 1.0,
454 scale_y: 1.0,
455 rotation_x: 0.0,
456 rotation_y: 0.0,
457 rotation_z: 0.0,
458 camera_distance: 8.0,
459 transform_origin: TransformOrigin::CENTER,
460 translation_x: 0.0,
461 translation_y: 0.0,
462 shadow_elevation: 0.0,
463 ambient_shadow_color: Color::BLACK,
464 spot_shadow_color: Color::BLACK,
465 shape: LayerShape::Rectangle,
466 clip: false,
467 compositing_strategy: CompositingStrategy::Auto,
468 blend_mode: BlendMode::SrcOver,
469 color_filter: None,
470 render_effect: None,
471 backdrop_effect: None,
472 }
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 Image {
610 rect: Rect,
611 image: ImageBitmap,
612 alpha: f32,
613 color_filter: Option<ColorFilter>,
614 sampling: ImageSampling,
615 src_rect: Option<Rect>,
619 },
620 Text(Box<TextPrimitive>),
622 Shadow(ShadowPrimitive),
625}
626
627#[derive(Clone, Debug, PartialEq)]
636pub struct TextPrimitive {
637 pub rect: Rect,
640 pub text: std::rc::Rc<str>,
643 pub style: DrawTextStyle,
644 pub color: Color,
648}
649
650fn shared_text_str(text: &str) -> Rc<str> {
660 use std::{
661 cell::RefCell,
662 collections::HashMap,
663 hash::{Hash, Hasher},
664 };
665
666 const POOL_CAPACITY: usize = 256;
667 thread_local! {
668 static POOL: RefCell<HashMap<u64, Rc<str>, crate::FxBuildHasher>> =
669 RefCell::new(HashMap::default());
670 }
671
672 let mut hasher = crate::FxHasher::default();
673 text.hash(&mut hasher);
674 let key = hasher.finish();
675
676 POOL.with(|pool| {
677 let mut pool = pool.borrow_mut();
678 if let Some(shared) = pool.get(&key)
679 && &**shared == text
680 {
681 return Rc::clone(shared);
682 }
683 let shared: Rc<str> = Rc::from(text);
684 if pool.len() >= POOL_CAPACITY {
685 pool.clear();
686 }
687 pool.insert(key, Rc::clone(&shared));
688 shared
689 })
690}
691
692impl DrawPrimitive {
694 pub fn translate(self, dx: f32, dy: f32) -> Self {
697 match self {
698 DrawPrimitive::Content => DrawPrimitive::Content,
699 DrawPrimitive::Blend {
700 primitive,
701 blend_mode,
702 } => DrawPrimitive::Blend {
703 primitive: Box::new(primitive.translate(dx, dy)),
704 blend_mode,
705 },
706 DrawPrimitive::Rect {
707 rect,
708 brush,
709 stroke,
710 } => DrawPrimitive::Rect {
711 rect: rect.translate(dx, dy),
712 brush,
713 stroke,
714 },
715 DrawPrimitive::RoundRect {
716 rect,
717 brush,
718 radii,
719 stroke,
720 } => DrawPrimitive::RoundRect {
721 rect: rect.translate(dx, dy),
722 brush,
723 radii,
724 stroke,
725 },
726 DrawPrimitive::Arc {
727 rect,
728 brush,
729 center,
730 radius,
731 start_angle,
732 sweep_angle,
733 stroke,
734 inner_radius,
735 } => DrawPrimitive::Arc {
736 rect: rect.translate(dx, dy),
737 brush,
738 center: Point::new(center.x + dx, center.y + dy),
739 radius,
740 start_angle,
741 sweep_angle,
742 stroke,
743 inner_radius,
744 },
745 DrawPrimitive::Image {
746 rect,
747 image,
748 alpha,
749 color_filter,
750 sampling,
751 src_rect,
752 } => DrawPrimitive::Image {
753 rect: rect.translate(dx, dy),
754 image,
755 alpha,
756 color_filter,
757 sampling,
758 src_rect,
759 },
760 DrawPrimitive::Text(mut text) => {
761 text.rect = text.rect.translate(dx, dy);
762 DrawPrimitive::Text(text)
763 }
764 DrawPrimitive::Shadow(ShadowPrimitive::Drop {
765 shape,
766 cutout,
767 blur_radius,
768 blend_mode,
769 }) => DrawPrimitive::Shadow(ShadowPrimitive::Drop {
770 shape: Box::new(shape.translate(dx, dy)),
771 cutout: cutout.map(|cutout| Box::new(cutout.translate(dx, dy))),
772 blur_radius,
773 blend_mode,
774 }),
775 DrawPrimitive::Shadow(ShadowPrimitive::Inner {
776 fill,
777 cutout,
778 blur_radius,
779 blend_mode,
780 clip_rect,
781 }) => DrawPrimitive::Shadow(ShadowPrimitive::Inner {
782 fill: Box::new(fill.translate(dx, dy)),
783 cutout: Box::new(cutout.translate(dx, dy)),
784 blur_radius,
785 blend_mode,
786 clip_rect: clip_rect.translate(dx, dy),
787 }),
788 }
789 }
790}
791
792#[derive(Clone, Debug, PartialEq)]
793pub enum ShadowPrimitive {
794 Drop {
798 shape: Box<DrawPrimitive>,
799 cutout: Option<Box<DrawPrimitive>>,
800 blur_radius: f32,
801 blend_mode: BlendMode,
802 },
803 Inner {
805 fill: Box<DrawPrimitive>,
806 cutout: Box<DrawPrimitive>,
807 blur_radius: f32,
808 blend_mode: BlendMode,
809 clip_rect: Rect,
811 },
812}
813
814pub trait DrawScope {
815 fn size(&self) -> Size;
816 fn draw_content(&mut self);
817 fn draw_rect(&mut self, brush: Brush);
818 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
819 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
821 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
822 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
823 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
824 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
826 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
827 fn draw_circle_blend(
828 &mut self,
829 brush: Brush,
830 center: Point,
831 radius: f32,
832 blend_mode: BlendMode,
833 );
834
835 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
837 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
838 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
840 fn draw_rect_at_stroked_blend(
841 &mut self,
842 rect: Rect,
843 brush: Brush,
844 stroke: Stroke,
845 blend_mode: BlendMode,
846 );
847 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
849 fn draw_round_rect_stroked_blend(
850 &mut self,
851 brush: Brush,
852 radii: CornerRadii,
853 stroke: Stroke,
854 blend_mode: BlendMode,
855 );
856 fn draw_round_rect_at_stroked(
858 &mut self,
859 rect: Rect,
860 brush: Brush,
861 radii: CornerRadii,
862 stroke: Stroke,
863 );
864 fn draw_round_rect_at_stroked_blend(
865 &mut self,
866 rect: Rect,
867 brush: Brush,
868 radii: CornerRadii,
869 stroke: Stroke,
870 blend_mode: BlendMode,
871 );
872 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
875 fn draw_circle_stroked_blend(
876 &mut self,
877 brush: Brush,
878 center: Point,
879 radius: f32,
880 stroke: Stroke,
881 blend_mode: BlendMode,
882 );
883
884 fn draw_arc(
897 &mut self,
898 brush: Brush,
899 center: Point,
900 radius: f32,
901 start_angle: f32,
902 sweep_angle: f32,
903 stroke: Stroke,
904 );
905 #[expect(clippy::too_many_arguments)]
906 fn draw_arc_blend(
907 &mut self,
908 brush: Brush,
909 center: Point,
910 radius: f32,
911 start_angle: f32,
912 sweep_angle: f32,
913 stroke: Stroke,
914 blend_mode: BlendMode,
915 );
916
917 fn draw_annular_sector(
926 &mut self,
927 brush: Brush,
928 center: Point,
929 inner_radius: f32,
930 outer_radius: f32,
931 start_angle: f32,
932 sweep_angle: f32,
933 );
934 #[expect(clippy::too_many_arguments)]
935 fn draw_annular_sector_blend(
936 &mut self,
937 brush: Brush,
938 center: Point,
939 inner_radius: f32,
940 outer_radius: f32,
941 start_angle: f32,
942 sweep_angle: f32,
943 blend_mode: BlendMode,
944 );
945
946 fn draw_image(&mut self, image: ImageBitmap);
947 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
948 fn draw_image_at(
949 &mut self,
950 rect: Rect,
951 image: ImageBitmap,
952 alpha: f32,
953 color_filter: Option<ColorFilter>,
954 );
955 fn draw_image_at_sampled(
956 &mut self,
957 rect: Rect,
958 image: ImageBitmap,
959 alpha: f32,
960 color_filter: Option<ColorFilter>,
961 sampling: ImageSampling,
962 );
963 fn draw_image_at_blend(
964 &mut self,
965 rect: Rect,
966 image: ImageBitmap,
967 alpha: f32,
968 color_filter: Option<ColorFilter>,
969 blend_mode: BlendMode,
970 );
971 fn draw_image_src(
974 &mut self,
975 image: ImageBitmap,
976 src_rect: Rect,
977 dst_rect: Rect,
978 alpha: f32,
979 color_filter: Option<ColorFilter>,
980 );
981 fn draw_image_src_sampled(
982 &mut self,
983 image: ImageBitmap,
984 src_rect: Rect,
985 dst_rect: Rect,
986 alpha: f32,
987 color_filter: Option<ColorFilter>,
988 sampling: ImageSampling,
989 );
990 fn draw_image_src_blend(
991 &mut self,
992 image: ImageBitmap,
993 src_rect: Rect,
994 dst_rect: Rect,
995 alpha: f32,
996 color_filter: Option<ColorFilter>,
997 blend_mode: BlendMode,
998 );
999 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
1008 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
1014 if let Ok(path) = crate::VectorPath::parse(d) {
1015 self.draw_vector_path(&path, brush);
1016 }
1017 }
1018
1019 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement;
1026
1027 fn draw_text(&mut self, brush: Brush, text: &str, style: &DrawTextStyle) {
1030 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
1031 }
1032
1033 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle);
1040
1041 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &DrawTextStyle) {
1047 if text.is_empty() {
1048 return;
1049 }
1050 let measurement = self.measure_text(text, style);
1051 self.draw_text_at(
1052 Rect::from_origin_size(top_left, measurement.size),
1053 brush,
1054 text,
1055 &DrawTextStyle {
1056 align: TextAlign::Left,
1057 vertical_align: TextVerticalAlign::Top,
1058 ..style.clone()
1059 },
1060 );
1061 }
1062
1063 fn into_primitives(self) -> Vec<DrawPrimitive>;
1064}
1065
1066pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &DrawTextStyle) -> Point {
1072 let x = match style.align {
1073 TextAlign::Left => rect.x,
1074 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
1075 TextAlign::Right => rect.x + rect.width - measurement.size.width,
1076 };
1077 let y = match style.vertical_align {
1078 TextVerticalAlign::Top => rect.y,
1079 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
1080 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
1081 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
1082 };
1083 Point::new(x, y)
1084}
1085
1086#[derive(Default)]
1087pub struct DrawScopeDefault {
1088 size: Size,
1089 recording: CommandRecorder,
1090 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1091}
1092
1093const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1094
1095thread_local! {
1096 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<
1097 std::collections::HashMap<(u32, u32), usize, crate::FxBuildHasher>,
1098 > = std::cell::RefCell::new(std::collections::HashMap::default());
1099}
1100
1101fn recorded_primitive_capacity(size: Size) -> usize {
1102 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1103 counts
1104 .borrow()
1105 .get(&(size.width.to_bits(), size.height.to_bits()))
1106 .copied()
1107 .unwrap_or(0)
1108 })
1109}
1110
1111fn note_recorded_primitive_count(size: Size, count: usize) {
1112 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1113 let mut counts = counts.borrow_mut();
1114 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1115 counts.clear();
1116 }
1117 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1118 });
1119}
1120
1121impl DrawScopeDefault {
1122 pub fn new(size: Size) -> Self {
1123 Self::with_storage(size, None, CommandRecording::default())
1124 }
1125
1126 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1131 Self::with_storage(size, Some(text_measurer), CommandRecording::default())
1132 }
1133
1134 pub fn with_text_measurer_reusing(
1137 size: Size,
1138 text_measurer: Rc<dyn DrawTextMeasurer>,
1139 storage: CommandRecording,
1140 ) -> Self {
1141 Self::with_storage(size, Some(text_measurer), storage)
1142 }
1143
1144 fn with_storage(
1145 size: Size,
1146 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1147 recording: CommandRecording,
1148 ) -> Self {
1149 let mut recording = CommandRecorder::reusing(recording);
1150 recording.reserve_shapes(recorded_primitive_capacity(size));
1151 Self {
1152 size,
1153 recording,
1154 text_measurer,
1155 }
1156 }
1157
1158 pub fn content_marker_count(&self) -> u32 {
1160 self.recording.content_markers()
1161 }
1162
1163 pub fn inset(&mut self, insets: EdgeInsets, draw: impl FnOnce(&mut DrawScopeDefault)) {
1167 let inner = insets.inset_rect(self.size);
1168 let mut scope = Self::with_storage(
1169 Size {
1170 width: inner.width,
1171 height: inner.height,
1172 },
1173 self.text_measurer.clone(),
1174 CommandRecording::default(),
1175 );
1176 draw(&mut scope);
1177 let primitives = scope.into_primitives();
1178 self.push_recorded(
1179 primitives
1180 .into_iter()
1181 .map(|primitive| primitive.translate(inner.x, inner.y)),
1182 );
1183 }
1184
1185 pub fn push_recorded(&mut self, primitives: impl IntoIterator<Item = DrawPrimitive>) {
1187 for primitive in primitives {
1188 self.recording.push_primitive(primitive);
1189 }
1190 }
1191
1192 pub fn finish(self) -> CommandRecording {
1195 note_recorded_primitive_count(self.size, self.recording.len());
1196 self.recording.finish()
1197 }
1198
1199 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1200 if blend_mode != BlendMode::SrcOver {
1201 self.recording.push_other(DrawPrimitive::Blend {
1202 primitive: Box::new(primitive),
1203 blend_mode,
1204 });
1205 return;
1206 }
1207 self.recording.push_other(primitive);
1208 }
1209
1210 #[expect(clippy::too_many_arguments)]
1211 #[inline]
1212 fn push_arc(
1213 &mut self,
1214 brush: Brush,
1215 center: Point,
1216 radius: f32,
1217 start_angle: f32,
1218 sweep_angle: f32,
1219 stroke: Option<Stroke>,
1220 inner_radius: f32,
1221 blend_mode: BlendMode,
1222 ) {
1223 let args = ArcRecordArgs {
1224 brush: &brush,
1225 center,
1226 radius,
1227 start_angle,
1228 sweep_angle,
1229 stroke,
1230 inner_radius,
1231 blend_mode,
1232 };
1233 let geometry = normalized_band(&args);
1234 if geometry.is_degenerate() {
1235 return;
1236 }
1237 self.recording.push_scope_arc(&args, &geometry);
1238 }
1239}
1240
1241impl DrawScope for DrawScopeDefault {
1242 fn size(&self) -> Size {
1243 self.size
1244 }
1245
1246 fn draw_content(&mut self) {
1247 self.recording.push_content();
1248 }
1249
1250 fn draw_rect(&mut self, brush: Brush) {
1251 self.draw_rect_blend(brush, BlendMode::SrcOver);
1252 }
1253
1254 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1255 self.recording
1256 .push_rect(Rect::from_size(self.size), &brush, None, blend_mode);
1257 }
1258
1259 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1260 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1261 }
1262
1263 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1264 self.recording.push_rect(rect, &brush, None, blend_mode);
1265 }
1266
1267 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1268 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1269 }
1270
1271 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1272 self.recording
1273 .push_round_rect(Rect::from_size(self.size), &brush, radii, None, blend_mode);
1274 }
1275
1276 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1277 self.recording
1278 .push_round_rect(rect, &brush, radii, None, BlendMode::SrcOver);
1279 }
1280
1281 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1282 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1283 }
1284
1285 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1286 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1287 }
1288
1289 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1290 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1291 }
1292
1293 fn draw_rect_at_stroked_blend(
1294 &mut self,
1295 rect: Rect,
1296 brush: Brush,
1297 stroke: Stroke,
1298 blend_mode: BlendMode,
1299 ) {
1300 if !stroke.is_visible() {
1301 return;
1302 }
1303 self.recording
1304 .push_rect(rect, &brush, Some(stroke), blend_mode);
1305 }
1306
1307 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1308 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1309 }
1310
1311 fn draw_round_rect_stroked_blend(
1312 &mut self,
1313 brush: Brush,
1314 radii: CornerRadii,
1315 stroke: Stroke,
1316 blend_mode: BlendMode,
1317 ) {
1318 self.draw_round_rect_at_stroked_blend(
1319 Rect::from_size(self.size),
1320 brush,
1321 radii,
1322 stroke,
1323 blend_mode,
1324 );
1325 }
1326
1327 fn draw_round_rect_at_stroked(
1328 &mut self,
1329 rect: Rect,
1330 brush: Brush,
1331 radii: CornerRadii,
1332 stroke: Stroke,
1333 ) {
1334 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1335 }
1336
1337 fn draw_round_rect_at_stroked_blend(
1338 &mut self,
1339 rect: Rect,
1340 brush: Brush,
1341 radii: CornerRadii,
1342 stroke: Stroke,
1343 blend_mode: BlendMode,
1344 ) {
1345 if !stroke.is_visible() {
1346 return;
1347 }
1348 self.recording
1349 .push_round_rect(rect, &brush, radii, Some(stroke), blend_mode);
1350 }
1351
1352 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1353 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1354 }
1355
1356 fn draw_circle_stroked_blend(
1357 &mut self,
1358 brush: Brush,
1359 center: Point,
1360 radius: f32,
1361 stroke: Stroke,
1362 blend_mode: BlendMode,
1363 ) {
1364 if !stroke.is_visible() || !radius.is_finite() {
1365 return;
1366 }
1367 let radius = radius.max(0.0);
1368 let diameter = radius * 2.0;
1369 self.draw_round_rect_at_stroked_blend(
1370 Rect {
1371 x: center.x - radius,
1372 y: center.y - radius,
1373 width: diameter,
1374 height: diameter,
1375 },
1376 brush,
1377 CornerRadii::uniform(radius),
1378 stroke,
1379 blend_mode,
1380 );
1381 }
1382
1383 fn draw_arc(
1384 &mut self,
1385 brush: Brush,
1386 center: Point,
1387 radius: f32,
1388 start_angle: f32,
1389 sweep_angle: f32,
1390 stroke: Stroke,
1391 ) {
1392 self.draw_arc_blend(
1393 brush,
1394 center,
1395 radius,
1396 start_angle,
1397 sweep_angle,
1398 stroke,
1399 BlendMode::SrcOver,
1400 );
1401 }
1402
1403 fn draw_arc_blend(
1404 &mut self,
1405 brush: Brush,
1406 center: Point,
1407 radius: f32,
1408 start_angle: f32,
1409 sweep_angle: f32,
1410 stroke: Stroke,
1411 blend_mode: BlendMode,
1412 ) {
1413 if !stroke.is_visible() {
1414 return;
1415 }
1416 self.push_arc(
1417 brush,
1418 center,
1419 radius,
1420 start_angle,
1421 sweep_angle,
1422 Some(stroke),
1423 0.0,
1424 blend_mode,
1425 );
1426 }
1427
1428 fn draw_annular_sector(
1429 &mut self,
1430 brush: Brush,
1431 center: Point,
1432 inner_radius: f32,
1433 outer_radius: f32,
1434 start_angle: f32,
1435 sweep_angle: f32,
1436 ) {
1437 self.draw_annular_sector_blend(
1438 brush,
1439 center,
1440 inner_radius,
1441 outer_radius,
1442 start_angle,
1443 sweep_angle,
1444 BlendMode::SrcOver,
1445 );
1446 }
1447
1448 fn draw_annular_sector_blend(
1449 &mut self,
1450 brush: Brush,
1451 center: Point,
1452 inner_radius: f32,
1453 outer_radius: f32,
1454 start_angle: f32,
1455 sweep_angle: f32,
1456 blend_mode: BlendMode,
1457 ) {
1458 self.push_arc(
1459 brush,
1460 center,
1461 outer_radius,
1462 start_angle,
1463 sweep_angle,
1464 None,
1465 inner_radius,
1466 blend_mode,
1467 );
1468 }
1469
1470 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1471 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1472 }
1473
1474 fn draw_circle_blend(
1475 &mut self,
1476 brush: Brush,
1477 center: Point,
1478 radius: f32,
1479 blend_mode: BlendMode,
1480 ) {
1481 let radius = radius.max(0.0);
1482 let diameter = radius * 2.0;
1483 self.recording.push_round_rect(
1484 Rect {
1485 x: center.x - radius,
1486 y: center.y - radius,
1487 width: diameter,
1488 height: diameter,
1489 },
1490 &brush,
1491 CornerRadii::uniform(radius),
1492 None,
1493 blend_mode,
1494 );
1495 }
1496
1497 fn draw_image(&mut self, image: ImageBitmap) {
1498 self.draw_image_blend(image, BlendMode::SrcOver);
1499 }
1500
1501 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1502 self.push_blended_primitive(
1503 DrawPrimitive::Image {
1504 rect: Rect::from_size(self.size),
1505 image,
1506 alpha: 1.0,
1507 color_filter: None,
1508 sampling: ImageSampling::Nearest,
1509 src_rect: None,
1510 },
1511 blend_mode,
1512 );
1513 }
1514
1515 fn draw_image_at(
1516 &mut self,
1517 rect: Rect,
1518 image: ImageBitmap,
1519 alpha: f32,
1520 color_filter: Option<ColorFilter>,
1521 ) {
1522 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1523 }
1524
1525 fn draw_image_at_sampled(
1526 &mut self,
1527 rect: Rect,
1528 image: ImageBitmap,
1529 alpha: f32,
1530 color_filter: Option<ColorFilter>,
1531 sampling: ImageSampling,
1532 ) {
1533 self.push_blended_primitive(
1534 DrawPrimitive::Image {
1535 rect,
1536 image,
1537 alpha: alpha.clamp(0.0, 1.0),
1538 color_filter,
1539 sampling,
1540 src_rect: None,
1541 },
1542 BlendMode::SrcOver,
1543 );
1544 }
1545
1546 fn draw_image_at_blend(
1547 &mut self,
1548 rect: Rect,
1549 image: ImageBitmap,
1550 alpha: f32,
1551 color_filter: Option<ColorFilter>,
1552 blend_mode: BlendMode,
1553 ) {
1554 self.push_blended_primitive(
1555 DrawPrimitive::Image {
1556 rect,
1557 image,
1558 alpha: alpha.clamp(0.0, 1.0),
1559 color_filter,
1560 sampling: ImageSampling::Nearest,
1561 src_rect: None,
1562 },
1563 blend_mode,
1564 );
1565 }
1566
1567 fn draw_image_src(
1568 &mut self,
1569 image: ImageBitmap,
1570 src_rect: Rect,
1571 dst_rect: Rect,
1572 alpha: f32,
1573 color_filter: Option<ColorFilter>,
1574 ) {
1575 self.draw_image_src_blend(
1576 image,
1577 src_rect,
1578 dst_rect,
1579 alpha,
1580 color_filter,
1581 BlendMode::SrcOver,
1582 );
1583 }
1584
1585 fn draw_image_src_sampled(
1586 &mut self,
1587 image: ImageBitmap,
1588 src_rect: Rect,
1589 dst_rect: Rect,
1590 alpha: f32,
1591 color_filter: Option<ColorFilter>,
1592 sampling: ImageSampling,
1593 ) {
1594 self.push_blended_primitive(
1595 DrawPrimitive::Image {
1596 rect: dst_rect,
1597 image,
1598 alpha: alpha.clamp(0.0, 1.0),
1599 color_filter,
1600 sampling,
1601 src_rect: Some(src_rect),
1602 },
1603 BlendMode::SrcOver,
1604 );
1605 }
1606
1607 fn draw_image_src_blend(
1608 &mut self,
1609 image: ImageBitmap,
1610 src_rect: Rect,
1611 dst_rect: Rect,
1612 alpha: f32,
1613 color_filter: Option<ColorFilter>,
1614 blend_mode: BlendMode,
1615 ) {
1616 self.push_blended_primitive(
1617 DrawPrimitive::Image {
1618 rect: dst_rect,
1619 image,
1620 alpha: alpha.clamp(0.0, 1.0),
1621 color_filter,
1622 sampling: ImageSampling::Nearest,
1623 src_rect: Some(src_rect),
1624 },
1625 blend_mode,
1626 );
1627 }
1628
1629 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1630 const SUPERSAMPLE: f32 = 2.0;
1631 const MAX_MASK_PIXELS: f32 = 4096.0;
1632
1633 if path.is_empty() {
1634 return;
1635 }
1636 let bounds = path.bounds();
1637 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1638 return;
1639 }
1640
1641 let color = match &brush {
1642 Brush::Solid(color) => *color,
1643 Brush::LinearGradient { colors, .. }
1644 | Brush::RadialGradient { colors, .. }
1645 | Brush::SweepGradient { colors, .. } => match colors.first() {
1646 Some(color) => *color,
1647 None => return,
1648 },
1649 };
1650 if color.3 <= 0.0 {
1651 return;
1652 }
1653
1654 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
1655 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
1656 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
1657 let mask_width = (rect_width * SUPERSAMPLE)
1658 .ceil()
1659 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1660 let mask_height = (rect_height * SUPERSAMPLE)
1661 .ceil()
1662 .clamp(1.0, MAX_MASK_PIXELS) as usize;
1663
1664 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1665 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1666 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
1667 let alpha = color.3.clamp(0.0, 1.0);
1668 let key = vector_path_mask_key(
1669 path,
1670 origin,
1671 (mask_width, mask_height),
1672 [red, green, blue],
1673 alpha,
1674 );
1675 let cached = vector_path_mask_cache_get(key);
1676 let image = match cached {
1677 Some(image) => image,
1678 None => {
1679 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
1680 let mut pixels = Vec::with_capacity(mask.len() * 4);
1681 for coverage in mask {
1682 pixels.extend_from_slice(&[
1683 red,
1684 green,
1685 blue,
1686 (alpha * coverage as f32 + 0.5) as u8,
1687 ]);
1688 }
1689 let Ok(image) =
1690 ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
1691 else {
1692 return;
1693 };
1694 vector_path_mask_cache_put(key, image.clone());
1695 image
1696 }
1697 };
1698
1699 self.recording.push_other(DrawPrimitive::Image {
1700 rect: Rect {
1701 x: origin.x,
1702 y: origin.y,
1703 width: rect_width,
1704 height: rect_height,
1705 },
1706 image,
1707 alpha: 1.0,
1708 color_filter: None,
1709 sampling: ImageSampling::Linear,
1710 src_rect: None,
1711 });
1712 }
1713
1714 fn measure_text(&self, text: &str, style: &DrawTextStyle) -> TextMeasurement {
1715 match &self.text_measurer {
1716 Some(measurer) => measurer.measure_text(text, style),
1717 None => estimate_text_measurement(text, style),
1718 }
1719 }
1720
1721 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &DrawTextStyle) {
1722 if text.is_empty() {
1723 return;
1724 }
1725 let Some(color) = solid_fill_color(&brush) else {
1726 return;
1727 };
1728 if color.3 <= 0.0 {
1729 return;
1730 }
1731 let measurement = self.measure_text(text, style);
1732 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
1733 return;
1734 }
1735 let origin = align_text_block(rect, measurement, style);
1736 if !origin.x.is_finite() || !origin.y.is_finite() {
1737 return;
1738 }
1739 self.recording
1740 .push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
1741 rect: Rect::from_origin_size(origin, measurement.size),
1742 text: shared_text_str(text),
1743 style: style.clone(),
1744 color,
1745 })));
1746 }
1747
1748 fn into_primitives(self) -> Vec<DrawPrimitive> {
1749 self.finish().into_primitives_with_markers()
1750 }
1751}
1752
1753fn solid_fill_color(brush: &Brush) -> Option<Color> {
1758 match brush {
1759 Brush::Solid(color) => Some(*color),
1760 Brush::LinearGradient { colors, .. }
1761 | Brush::RadialGradient { colors, .. }
1762 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
1763 }
1764}
1765
1766#[cfg(test)]
1767#[path = "tests/geometry_tests.rs"]
1768mod tests;