1use crate::stroke::{arc_band, ArcGeometry, Stroke};
4use crate::typography::{
5 estimate_text_measurement, DrawTextMeasurer, TextAlign, TextMeasurement, TextStyle,
6 TextVerticalAlign,
7};
8use crate::{Brush, Color, ColorFilter, ImageBitmap, ImageSampling};
9use std::ops::AddAssign;
10use std::rc::Rc;
11
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
13pub struct Point {
14 pub x: f32,
15 pub y: f32,
16}
17
18impl Point {
19 pub const fn new(x: f32, y: f32) -> Self {
20 Self { x, y }
21 }
22
23 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Default)]
27pub struct Size {
28 pub width: f32,
29 pub height: f32,
30}
31
32impl Size {
33 pub const fn new(width: f32, height: f32) -> Self {
34 Self { width, height }
35 }
36
37 pub const ZERO: Size = Size {
38 width: 0.0,
39 height: 0.0,
40 };
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct Rect {
45 pub x: f32,
46 pub y: f32,
47 pub width: f32,
48 pub height: f32,
49}
50
51impl Rect {
52 pub fn from_origin_size(origin: Point, size: Size) -> Self {
53 Self {
54 x: origin.x,
55 y: origin.y,
56 width: size.width,
57 height: size.height,
58 }
59 }
60
61 pub fn from_size(size: Size) -> Self {
62 Self {
63 x: 0.0,
64 y: 0.0,
65 width: size.width,
66 height: size.height,
67 }
68 }
69
70 pub fn translate(&self, dx: f32, dy: f32) -> Self {
71 Self {
72 x: self.x + dx,
73 y: self.y + dy,
74 width: self.width,
75 height: self.height,
76 }
77 }
78
79 pub fn contains(&self, x: f32, y: f32) -> bool {
80 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
81 }
82
83 pub fn intersect(&self, other: Rect) -> Option<Rect> {
85 let left = self.x.max(other.x);
86 let top = self.y.max(other.y);
87 let right = (self.x + self.width).min(other.x + other.width);
88 let bottom = (self.y + self.height).min(other.y + other.height);
89 let width = right - left;
90 let height = bottom - top;
91 if width <= 0.0 || height <= 0.0 {
92 None
93 } else {
94 Some(Rect {
95 x: left,
96 y: top,
97 width,
98 height,
99 })
100 }
101 }
102
103 pub fn union(&self, other: Rect) -> Rect {
104 let left = self.x.min(other.x);
105 let top = self.y.min(other.y);
106 let right = (self.x + self.width).max(other.x + other.width);
107 let bottom = (self.y + self.height).max(other.y + other.height);
108 Rect {
109 x: left,
110 y: top,
111 width: (right - left).max(0.0),
112 height: (bottom - top).max(0.0),
113 }
114 }
115}
116
117#[derive(Clone, Copy, Debug, Default, PartialEq)]
119pub struct EdgeInsets {
120 pub left: f32,
121 pub top: f32,
122 pub right: f32,
123 pub bottom: f32,
124}
125
126impl EdgeInsets {
127 pub fn uniform(all: f32) -> Self {
128 Self {
129 left: all,
130 top: all,
131 right: all,
132 bottom: all,
133 }
134 }
135
136 pub fn horizontal(horizontal: f32) -> Self {
137 Self {
138 left: horizontal,
139 right: horizontal,
140 ..Self::default()
141 }
142 }
143
144 pub fn vertical(vertical: f32) -> Self {
145 Self {
146 top: vertical,
147 bottom: vertical,
148 ..Self::default()
149 }
150 }
151
152 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
153 Self {
154 left: horizontal,
155 right: horizontal,
156 top: vertical,
157 bottom: vertical,
158 }
159 }
160
161 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
162 Self {
163 left,
164 top,
165 right,
166 bottom,
167 }
168 }
169
170 pub fn is_zero(&self) -> bool {
171 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
172 }
173
174 pub fn horizontal_sum(&self) -> f32 {
175 self.left + self.right
176 }
177
178 pub fn vertical_sum(&self) -> f32 {
179 self.top + self.bottom
180 }
181}
182
183impl AddAssign for EdgeInsets {
184 fn add_assign(&mut self, rhs: Self) {
185 self.left += rhs.left;
186 self.top += rhs.top;
187 self.right += rhs.right;
188 self.bottom += rhs.bottom;
189 }
190}
191
192#[derive(Clone, Copy, Debug, Default, PartialEq)]
193pub struct CornerRadii {
194 pub top_left: f32,
195 pub top_right: f32,
196 pub bottom_right: f32,
197 pub bottom_left: f32,
198}
199
200impl CornerRadii {
201 pub fn uniform(radius: f32) -> Self {
202 Self {
203 top_left: radius,
204 top_right: radius,
205 bottom_right: radius,
206 bottom_left: radius,
207 }
208 }
209}
210
211#[derive(Clone, Copy, Debug, PartialEq)]
212pub struct RoundedCornerShape {
213 radii: CornerRadii,
214}
215
216impl RoundedCornerShape {
217 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
218 Self {
219 radii: CornerRadii {
220 top_left,
221 top_right,
222 bottom_right,
223 bottom_left,
224 },
225 }
226 }
227
228 pub fn uniform(radius: f32) -> Self {
229 Self {
230 radii: CornerRadii::uniform(radius),
231 }
232 }
233
234 pub fn with_radii(radii: CornerRadii) -> Self {
235 Self { radii }
236 }
237
238 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
239 let mut resolved = self.radii;
240 let max_width = (width / 2.0).max(0.0);
241 let max_height = (height / 2.0).max(0.0);
242 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
243 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
244 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
245 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
246 resolved
247 }
248
249 pub fn radii(&self) -> CornerRadii {
250 self.radii
251 }
252}
253
254#[derive(Clone, Copy, Debug, PartialEq)]
255pub struct TransformOrigin {
256 pub pivot_fraction_x: f32,
257 pub pivot_fraction_y: f32,
258}
259
260impl TransformOrigin {
261 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
262 Self {
263 pivot_fraction_x,
264 pivot_fraction_y,
265 }
266 }
267
268 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
269}
270
271impl Default for TransformOrigin {
272 fn default() -> Self {
273 Self::CENTER
274 }
275}
276
277#[derive(Clone, Copy, Debug, Default, PartialEq)]
278pub enum LayerShape {
279 #[default]
280 Rectangle,
281 Rounded(RoundedCornerShape),
282}
283
284#[derive(Clone, Debug, PartialEq)]
285pub struct GraphicsLayer {
286 pub alpha: f32,
287 pub scale: f32,
288 pub scale_x: f32,
289 pub scale_y: f32,
290 pub rotation_x: f32,
291 pub rotation_y: f32,
292 pub rotation_z: f32,
293 pub camera_distance: f32,
294 pub transform_origin: TransformOrigin,
295 pub translation_x: f32,
296 pub translation_y: f32,
297 pub shadow_elevation: f32,
298 pub ambient_shadow_color: Color,
299 pub spot_shadow_color: Color,
300 pub shape: LayerShape,
301 pub clip: bool,
302 pub compositing_strategy: CompositingStrategy,
303 pub blend_mode: BlendMode,
304 pub color_filter: Option<ColorFilter>,
305 pub render_effect: Option<crate::render_effect::RenderEffect>,
306 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
307}
308
309impl GraphicsLayer {
310 pub fn composite_alpha_8bit(alpha: f32) -> f32 {
334 (alpha.clamp(0.0, 1.0) * 255.0).floor() / 255.0
335 }
336}
337
338impl Default for GraphicsLayer {
339 fn default() -> Self {
340 Self {
341 alpha: 1.0,
342 scale: 1.0,
343 scale_x: 1.0,
344 scale_y: 1.0,
345 rotation_x: 0.0,
346 rotation_y: 0.0,
347 rotation_z: 0.0,
348 camera_distance: 8.0,
349 transform_origin: TransformOrigin::CENTER,
350 translation_x: 0.0,
351 translation_y: 0.0,
352 shadow_elevation: 0.0,
353 ambient_shadow_color: Color::BLACK,
354 spot_shadow_color: Color::BLACK,
355 shape: LayerShape::Rectangle,
356 clip: false,
357 compositing_strategy: CompositingStrategy::Auto,
358 blend_mode: BlendMode::SrcOver,
359 color_filter: None,
360 render_effect: None,
361 backdrop_effect: None,
362 }
363 }
364}
365
366#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
371pub enum BlendMode {
372 Clear,
373 Src,
374 Dst,
375 #[default]
376 SrcOver,
377 DstOver,
378 SrcIn,
379 DstIn,
380 SrcOut,
381 DstOut,
382 SrcAtop,
383 DstAtop,
384 Xor,
385 Plus,
386 Modulate,
387 Screen,
388 Overlay,
389 Darken,
390 Lighten,
391 ColorDodge,
392 ColorBurn,
393 HardLight,
394 SoftLight,
395 Difference,
396 Exclusion,
397 Multiply,
398 Hue,
399 Saturation,
400 Color,
401 Luminosity,
402}
403
404#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
406pub enum CompositingStrategy {
407 #[default]
409 Auto,
410 Offscreen,
412 ModulateAlpha,
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum DrawPrimitive {
418 Content,
421 Blend {
423 primitive: Box<DrawPrimitive>,
424 blend_mode: BlendMode,
425 },
426 Rect {
427 rect: Rect,
428 brush: Brush,
429 stroke: Option<Stroke>,
432 },
433 RoundRect {
434 rect: Rect,
435 brush: Brush,
436 radii: CornerRadii,
437 stroke: Option<Stroke>,
440 },
441 Arc {
451 rect: Rect,
455 brush: Brush,
456 center: Point,
457 radius: f32,
458 start_angle: f32,
459 sweep_angle: f32,
460 stroke: Option<Stroke>,
461 inner_radius: f32,
463 },
464 Image {
465 rect: Rect,
466 image: ImageBitmap,
467 alpha: f32,
468 color_filter: Option<ColorFilter>,
469 sampling: ImageSampling,
470 src_rect: Option<Rect>,
474 },
475 Text(Box<TextPrimitive>),
477 Shadow(ShadowPrimitive),
480}
481
482#[derive(Clone, Debug, PartialEq)]
491pub struct TextPrimitive {
492 pub rect: Rect,
495 pub text: std::rc::Rc<str>,
498 pub style: TextStyle,
499 pub color: Color,
503}
504
505fn shared_text_str(text: &str) -> Rc<str> {
515 use std::cell::RefCell;
516 use std::collections::HashMap;
517 use std::hash::{Hash, Hasher};
518
519 const POOL_CAPACITY: usize = 256;
520 thread_local! {
521 static POOL: RefCell<HashMap<u64, Rc<str>>> = RefCell::new(HashMap::new());
522 }
523
524 let mut hasher = crate::FxHasher::default();
525 text.hash(&mut hasher);
526 let key = hasher.finish();
527
528 POOL.with(|pool| {
529 let mut pool = pool.borrow_mut();
530 if let Some(shared) = pool.get(&key) {
531 if &**shared == text {
532 return Rc::clone(shared);
533 }
534 }
535 let shared: Rc<str> = Rc::from(text);
536 if pool.len() >= POOL_CAPACITY {
537 pool.clear();
538 }
539 pool.insert(key, Rc::clone(&shared));
540 shared
541 })
542}
543
544#[derive(Clone, Debug, PartialEq)]
546pub enum ShadowPrimitive {
547 Drop {
551 shape: Box<DrawPrimitive>,
552 cutout: Option<Box<DrawPrimitive>>,
553 blur_radius: f32,
554 blend_mode: BlendMode,
555 },
556 Inner {
558 fill: Box<DrawPrimitive>,
559 cutout: Box<DrawPrimitive>,
560 blur_radius: f32,
561 blend_mode: BlendMode,
562 clip_rect: Rect,
564 },
565}
566
567pub trait DrawScope {
568 fn size(&self) -> Size;
569 fn draw_content(&mut self);
570 fn draw_rect(&mut self, brush: Brush);
571 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
572 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
574 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
575 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
576 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
577 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
579 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
580 fn draw_circle_blend(
581 &mut self,
582 brush: Brush,
583 center: Point,
584 radius: f32,
585 blend_mode: BlendMode,
586 );
587
588 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
596 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
597 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
599 fn draw_rect_at_stroked_blend(
600 &mut self,
601 rect: Rect,
602 brush: Brush,
603 stroke: Stroke,
604 blend_mode: BlendMode,
605 );
606 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
608 fn draw_round_rect_stroked_blend(
609 &mut self,
610 brush: Brush,
611 radii: CornerRadii,
612 stroke: Stroke,
613 blend_mode: BlendMode,
614 );
615 fn draw_round_rect_at_stroked(
617 &mut self,
618 rect: Rect,
619 brush: Brush,
620 radii: CornerRadii,
621 stroke: Stroke,
622 );
623 #[allow(clippy::too_many_arguments)]
624 fn draw_round_rect_at_stroked_blend(
625 &mut self,
626 rect: Rect,
627 brush: Brush,
628 radii: CornerRadii,
629 stroke: Stroke,
630 blend_mode: BlendMode,
631 );
632 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
635 fn draw_circle_stroked_blend(
636 &mut self,
637 brush: Brush,
638 center: Point,
639 radius: f32,
640 stroke: Stroke,
641 blend_mode: BlendMode,
642 );
643
644 #[allow(clippy::too_many_arguments)]
659 fn draw_arc(
660 &mut self,
661 brush: Brush,
662 center: Point,
663 radius: f32,
664 start_angle: f32,
665 sweep_angle: f32,
666 stroke: Stroke,
667 );
668 #[allow(clippy::too_many_arguments)]
669 fn draw_arc_blend(
670 &mut self,
671 brush: Brush,
672 center: Point,
673 radius: f32,
674 start_angle: f32,
675 sweep_angle: f32,
676 stroke: Stroke,
677 blend_mode: BlendMode,
678 );
679
680 #[allow(clippy::too_many_arguments)]
689 fn draw_annular_sector(
690 &mut self,
691 brush: Brush,
692 center: Point,
693 inner_radius: f32,
694 outer_radius: f32,
695 start_angle: f32,
696 sweep_angle: f32,
697 );
698 #[allow(clippy::too_many_arguments)]
699 fn draw_annular_sector_blend(
700 &mut self,
701 brush: Brush,
702 center: Point,
703 inner_radius: f32,
704 outer_radius: f32,
705 start_angle: f32,
706 sweep_angle: f32,
707 blend_mode: BlendMode,
708 );
709
710 fn draw_image(&mut self, image: ImageBitmap);
711 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
712 fn draw_image_at(
713 &mut self,
714 rect: Rect,
715 image: ImageBitmap,
716 alpha: f32,
717 color_filter: Option<ColorFilter>,
718 );
719 fn draw_image_at_sampled(
720 &mut self,
721 rect: Rect,
722 image: ImageBitmap,
723 alpha: f32,
724 color_filter: Option<ColorFilter>,
725 sampling: ImageSampling,
726 );
727 fn draw_image_at_blend(
728 &mut self,
729 rect: Rect,
730 image: ImageBitmap,
731 alpha: f32,
732 color_filter: Option<ColorFilter>,
733 blend_mode: BlendMode,
734 );
735 fn draw_image_src(
738 &mut self,
739 image: ImageBitmap,
740 src_rect: Rect,
741 dst_rect: Rect,
742 alpha: f32,
743 color_filter: Option<ColorFilter>,
744 );
745 fn draw_image_src_sampled(
746 &mut self,
747 image: ImageBitmap,
748 src_rect: Rect,
749 dst_rect: Rect,
750 alpha: f32,
751 color_filter: Option<ColorFilter>,
752 sampling: ImageSampling,
753 );
754 fn draw_image_src_blend(
755 &mut self,
756 image: ImageBitmap,
757 src_rect: Rect,
758 dst_rect: Rect,
759 alpha: f32,
760 color_filter: Option<ColorFilter>,
761 blend_mode: BlendMode,
762 );
763 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
772 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
778 if let Ok(path) = crate::VectorPath::parse(d) {
779 self.draw_vector_path(&path, brush);
780 }
781 }
782
783 fn measure_text(&self, text: &str, style: &TextStyle) -> TextMeasurement;
803
804 fn draw_text(&mut self, brush: Brush, text: &str, style: &TextStyle) {
807 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
808 }
809
810 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &TextStyle);
817
818 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &TextStyle) {
824 if text.is_empty() {
825 return;
826 }
827 let measurement = self.measure_text(text, style);
828 self.draw_text_at(
829 Rect::from_origin_size(top_left, measurement.size),
830 brush,
831 text,
832 &TextStyle {
833 align: TextAlign::Left,
834 vertical_align: TextVerticalAlign::Top,
835 ..style.clone()
836 },
837 );
838 }
839
840 fn into_primitives(self) -> Vec<DrawPrimitive>;
841}
842
843pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &TextStyle) -> Point {
849 let x = match style.align {
850 TextAlign::Left => rect.x,
851 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
852 TextAlign::Right => rect.x + rect.width - measurement.size.width,
853 };
854 let y = match style.vertical_align {
855 TextVerticalAlign::Top => rect.y,
856 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
857 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
858 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
859 };
860 Point::new(x, y)
861}
862
863#[derive(Clone, Copy, Debug, PartialEq, Eq)]
866#[repr(u8)]
867pub(crate) enum RecordKind {
868 SolidRect,
869 SolidRoundRect,
870 SolidArc,
871 Other,
872}
873
874#[derive(Clone, Copy, Debug, PartialEq, Eq)]
879pub(crate) struct TapeRef(u32);
880
881impl TapeRef {
882 const KIND_SHIFT: u32 = 30;
883 const INDEX_MASK: u32 = (1 << Self::KIND_SHIFT) - 1;
884
885 pub(crate) fn new(kind: RecordKind, index: usize) -> Self {
886 debug_assert!(index < (1usize << Self::KIND_SHIFT));
887 Self(((kind as u32) << Self::KIND_SHIFT) | index as u32)
888 }
889
890 pub(crate) fn kind(self) -> RecordKind {
891 match self.0 >> Self::KIND_SHIFT {
892 0 => RecordKind::SolidRect,
893 1 => RecordKind::SolidRoundRect,
894 2 => RecordKind::SolidArc,
895 _ => RecordKind::Other,
896 }
897 }
898
899 pub(crate) fn index(self) -> usize {
900 (self.0 & Self::INDEX_MASK) as usize
901 }
902}
903
904#[derive(Clone, Copy, Debug, PartialEq)]
906pub struct SolidRectRecord {
907 pub rect: Rect,
908 pub color: Color,
909 pub stroke: Option<Stroke>,
910}
911
912#[derive(Clone, Copy, Debug, PartialEq)]
915pub struct SolidRoundRectRecord {
916 pub rect: Rect,
917 pub radii: CornerRadii,
918 pub color: Color,
919 pub stroke: Option<Stroke>,
920}
921
922#[derive(Clone, Copy, Debug, PartialEq)]
927pub struct SolidArcRecord {
928 pub center: Point,
929 pub radius: f32,
930 pub start_angle: f32,
931 pub sweep_angle: f32,
932 pub inner_radius: f32,
933 pub color: Color,
934 pub stroke: Option<Stroke>,
935}
936
937#[derive(Clone, Debug, Default)]
945pub struct CommandRecording {
946 pub(crate) tape: Vec<TapeRef>,
951 pub(crate) rects: Vec<SolidRectRecord>,
952 pub(crate) round_rects: Vec<SolidRoundRectRecord>,
953 pub(crate) arcs: Vec<SolidArcRecord>,
954 pub(crate) others: Vec<DrawPrimitive>,
955}
956
957impl CommandRecording {
958 pub fn len(&self) -> usize {
960 self.tape.len()
961 }
962
963 pub fn materialize_range(
968 &self,
969 tape_start: usize,
970 tape_end: usize,
971 ) -> Option<Vec<DrawPrimitive>> {
972 if tape_start > tape_end || tape_end > self.tape.len() {
973 return None;
974 }
975 let mut out = Vec::with_capacity(tape_end - tape_start);
976 for entry in &self.tape[tape_start..tape_end] {
977 match entry.kind() {
978 RecordKind::SolidRect => {
979 let record = self.rects.get(entry.index())?;
980 out.push(DrawPrimitive::Rect {
981 rect: record.rect,
982 brush: Brush::Solid(record.color),
983 stroke: record.stroke,
984 });
985 }
986 RecordKind::SolidRoundRect => {
987 let record = self.round_rects.get(entry.index())?;
988 out.push(DrawPrimitive::RoundRect {
989 rect: record.rect,
990 brush: Brush::Solid(record.color),
991 radii: record.radii,
992 stroke: record.stroke,
993 });
994 }
995 RecordKind::SolidArc => {
996 let record = self.arcs.get(entry.index())?;
997 if let Some(primitive) = materialize_solid_arc(record) {
998 out.push(primitive);
999 }
1000 }
1001 RecordKind::Other => {
1002 out.push(self.others.get(entry.index())?.clone());
1003 }
1004 }
1005 }
1006 Some(out)
1007 }
1008
1009 pub fn is_empty(&self) -> bool {
1010 self.tape.is_empty()
1011 }
1012
1013 #[doc(hidden)]
1017 pub fn tape_ptr(&self) -> *const u8 {
1018 self.tape.as_ptr() as *const u8
1019 }
1020
1021 fn clear(&mut self) {
1022 self.tape.clear();
1023 self.rects.clear();
1024 self.round_rects.clear();
1025 self.arcs.clear();
1026 self.others.clear();
1027 }
1028}
1029
1030pub struct FinishedRecording {
1034 pub primitives: Vec<DrawPrimitive>,
1035 pub content_markers: u32,
1036 pub recording: CommandRecording,
1037 pub dropped: Vec<u32>,
1042}
1043
1044#[derive(Default)]
1045pub struct DrawScopeDefault {
1046 size: Size,
1047 rec: CommandRecording,
1050 out: Vec<DrawPrimitive>,
1053 content_markers: u32,
1057 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1061}
1062
1063const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1070
1071thread_local! {
1072 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
1073 std::cell::RefCell::new(std::collections::HashMap::new());
1074}
1075
1076fn recorded_primitive_capacity(size: Size) -> usize {
1077 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1078 counts
1079 .borrow()
1080 .get(&(size.width.to_bits(), size.height.to_bits()))
1081 .copied()
1082 .unwrap_or(0)
1083 })
1084}
1085
1086fn note_recorded_primitive_count(size: Size, count: usize) {
1087 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1088 let mut counts = counts.borrow_mut();
1089 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1090 counts.clear();
1091 }
1092 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1093 });
1094}
1095
1096impl DrawScopeDefault {
1097 pub fn new(size: Size) -> Self {
1098 Self::with_recording(size, None, CommandRecording::default(), Vec::new())
1099 }
1100
1101 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1106 Self::with_recording(
1107 size,
1108 Some(text_measurer),
1109 CommandRecording::default(),
1110 Vec::new(),
1111 )
1112 }
1113
1114 pub fn with_text_measurer_reusing(
1120 size: Size,
1121 text_measurer: Rc<dyn DrawTextMeasurer>,
1122 storage: Vec<DrawPrimitive>,
1123 ) -> Self {
1124 Self::with_recording(
1125 size,
1126 Some(text_measurer),
1127 CommandRecording::default(),
1128 storage,
1129 )
1130 }
1131
1132 pub fn with_recording(
1136 size: Size,
1137 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1138 mut recording: CommandRecording,
1139 out: Vec<DrawPrimitive>,
1140 ) -> Self {
1141 recording.clear();
1142 recording.tape.reserve(recorded_primitive_capacity(size));
1143 Self {
1144 size,
1145 rec: recording,
1146 out,
1147 content_markers: 0,
1148 text_measurer,
1149 }
1150 }
1151
1152 pub fn content_marker_count(&self) -> u32 {
1156 self.content_markers
1157 }
1158
1159 pub fn recorded(&self) -> &CommandRecording {
1162 &self.rec
1163 }
1164
1165 pub fn push_recorded(&mut self, primitives: Vec<DrawPrimitive>) {
1170 self.content_markers += primitives
1171 .iter()
1172 .filter(|primitive| matches!(primitive, DrawPrimitive::Content))
1173 .count() as u32;
1174 let base = self.rec.others.len();
1175 self.rec
1176 .tape
1177 .extend((0..primitives.len()).map(|i| TapeRef::new(RecordKind::Other, base + i)));
1178 self.rec.others.extend(primitives);
1179 }
1180
1181 pub fn finish(mut self) -> FinishedRecording {
1187 if std::env::var_os("CRANPOSE_RECORD_MIX_DIAG").is_some() && self.rec.tape.len() > 400 {
1190 eprintln!(
1191 "[record-mix] tape={} rects={} round_rects={} arcs={} others={}",
1192 self.rec.tape.len(),
1193 self.rec.rects.len(),
1194 self.rec.round_rects.len(),
1195 self.rec.arcs.len(),
1196 self.rec.others.len(),
1197 );
1198 }
1199 let mut out = std::mem::take(&mut self.out);
1200 out.clear();
1201 out.reserve(self.rec.tape.len());
1202 let mut dropped: Vec<u32> = Vec::new();
1203 {
1204 let mut others = self.rec.others.drain(..);
1208 for (tape_index, entry) in self.rec.tape.iter().enumerate() {
1209 match entry.kind() {
1210 RecordKind::SolidRect => {
1211 let record = &self.rec.rects[entry.index()];
1212 out.push(DrawPrimitive::Rect {
1213 rect: record.rect,
1214 brush: Brush::Solid(record.color),
1215 stroke: record.stroke,
1216 });
1217 }
1218 RecordKind::SolidRoundRect => {
1219 let record = &self.rec.round_rects[entry.index()];
1220 out.push(DrawPrimitive::RoundRect {
1221 rect: record.rect,
1222 brush: Brush::Solid(record.color),
1223 radii: record.radii,
1224 stroke: record.stroke,
1225 });
1226 }
1227 RecordKind::SolidArc => {
1228 let record = &self.rec.arcs[entry.index()];
1229 if let Some(primitive) = materialize_solid_arc(record) {
1230 out.push(primitive);
1231 } else {
1232 dropped.push(tape_index as u32);
1233 }
1234 }
1235 RecordKind::Other => {
1236 out.push(others.next().expect("tape/others in sync"));
1237 }
1238 }
1239 }
1240 }
1241 self.rec.clear();
1242 note_recorded_primitive_count(self.size, out.len());
1243 FinishedRecording {
1244 primitives: out,
1245 content_markers: self.content_markers,
1246 recording: self.rec,
1247 dropped,
1248 }
1249 }
1250
1251 pub fn finish_replay(
1259 mut self,
1260 center: Point,
1261 outcome: crate::record_replay::ReplayOutcome,
1262 bypass: &mut dyn FnMut(u32) -> bool,
1263 ) -> (
1264 FinishedRecording,
1265 Option<crate::record_replay::CommandReplayFrame>,
1266 ) {
1267 use crate::record_replay::{CommandReplayFrame, FrameSpan, ReplayOutcome, ReplaySpan};
1268 let ReplayOutcome::Spans(replay_spans) = outcome else {
1269 return (self.finish(), None);
1270 };
1271 let tape_len = self.rec.tape.len();
1272 let mut out = std::mem::take(&mut self.out);
1273 out.clear();
1274 let mut dropped: Vec<u32> = Vec::new();
1275 let mut spans: Vec<FrameSpan> = Vec::with_capacity(replay_spans.len());
1276 let mut any_retained = false;
1277 {
1278 let mut others = self.rec.others.drain(..);
1283 let tape = &self.rec.tape;
1284 let rects = &self.rec.rects;
1285 let round_rects = &self.rec.round_rects;
1286 let arcs = &self.rec.arcs;
1287 macro_rules! materialize_range {
1291 ($start:expr, $end:expr) => {{
1292 let prim_start = out.len() as u32;
1293 for tape_index in $start..$end {
1294 let entry = tape[tape_index];
1295 match entry.kind() {
1296 RecordKind::SolidRect => {
1297 let record = &rects[entry.index()];
1298 out.push(DrawPrimitive::Rect {
1299 rect: record.rect,
1300 brush: Brush::Solid(record.color),
1301 stroke: record.stroke,
1302 });
1303 }
1304 RecordKind::SolidRoundRect => {
1305 let record = &round_rects[entry.index()];
1306 out.push(DrawPrimitive::RoundRect {
1307 rect: record.rect,
1308 brush: Brush::Solid(record.color),
1309 radii: record.radii,
1310 stroke: record.stroke,
1311 });
1312 }
1313 RecordKind::SolidArc => {
1314 let record = &arcs[entry.index()];
1315 if let Some(primitive) = materialize_solid_arc(record) {
1316 out.push(primitive);
1317 } else {
1318 dropped.push(tape_index as u32);
1319 }
1320 }
1321 RecordKind::Other => {
1322 out.push(others.next().expect("tape/others in sync"));
1323 }
1324 }
1325 }
1326 (prim_start, out.len() as u32)
1327 }};
1328 }
1329 for span in replay_spans {
1330 match span {
1331 ReplaySpan::Dynamic {
1332 tape_start,
1333 tape_end,
1334 } => {
1335 let range = materialize_range!(tape_start, tape_end);
1336 if range.1 > range.0 {
1337 spans.push(FrameSpan::Dynamic { range });
1338 }
1339 }
1340 ReplaySpan::Retained {
1341 slot,
1342 capture,
1343 slot_offset,
1344 tape_start,
1345 tape_end,
1346 transform,
1347 recolors,
1348 bounds,
1349 } => {
1350 let compact = tape[tape_start..tape_end]
1354 .iter()
1355 .all(|entry| entry.kind() != RecordKind::Other);
1356 if compact && !capture && bypass(slot) {
1357 any_retained = true;
1363 let position = out.len() as u32;
1364 spans.push(FrameSpan::Retained {
1365 slot,
1366 capture: false,
1367 slot_offset: slot_offset as u32,
1368 range: (position, position),
1369 tape_range: (tape_start as u32, tape_end as u32),
1370 transform,
1371 recolors,
1372 bounds,
1373 });
1374 continue;
1375 }
1376 let drops_before = dropped.len();
1377 let range = materialize_range!(tape_start, tape_end);
1378 if compact && dropped.len() == drops_before {
1379 any_retained = true;
1380 spans.push(FrameSpan::Retained {
1381 slot,
1382 capture,
1383 slot_offset: slot_offset as u32,
1384 range,
1385 tape_range: (tape_start as u32, tape_end as u32),
1386 transform,
1387 recolors,
1388 bounds,
1389 });
1390 } else if range.1 > range.0 {
1391 spans.push(FrameSpan::Dynamic { range });
1392 }
1393 }
1394 }
1395 }
1396 }
1397 note_recorded_primitive_count(self.size, tape_len);
1404 let frame = any_retained.then_some(CommandReplayFrame {
1408 center,
1409 spans,
1410 fallback: None,
1411 });
1412 (
1413 FinishedRecording {
1414 primitives: out,
1415 content_markers: self.content_markers,
1416 recording: self.rec,
1417 dropped,
1418 },
1419 frame,
1420 )
1421 }
1422
1423 fn push_other(&mut self, primitive: DrawPrimitive) {
1424 let idx = self.rec.others.len();
1425 self.rec.tape.push(TapeRef::new(RecordKind::Other, idx));
1426 self.rec.others.push(primitive);
1427 }
1428
1429 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1430 if blend_mode != BlendMode::SrcOver {
1431 self.push_other(DrawPrimitive::Blend {
1432 primitive: Box::new(primitive),
1433 blend_mode,
1434 });
1435 return;
1436 }
1437 match primitive {
1438 DrawPrimitive::Rect {
1439 rect,
1440 brush: Brush::Solid(color),
1441 stroke,
1442 } => {
1443 let idx = self.rec.rects.len();
1444 self.rec.tape.push(TapeRef::new(RecordKind::SolidRect, idx));
1445 self.rec.rects.push(SolidRectRecord {
1446 rect,
1447 color,
1448 stroke,
1449 });
1450 }
1451 DrawPrimitive::RoundRect {
1452 rect,
1453 brush: Brush::Solid(color),
1454 radii,
1455 stroke,
1456 } => {
1457 let idx = self.rec.round_rects.len();
1458 self.rec
1459 .tape
1460 .push(TapeRef::new(RecordKind::SolidRoundRect, idx));
1461 self.rec.round_rects.push(SolidRoundRectRecord {
1462 rect,
1463 radii,
1464 color,
1465 stroke,
1466 });
1467 }
1468 other => self.push_other(other),
1469 }
1470 }
1471
1472 #[allow(clippy::too_many_arguments)]
1479 fn push_arc(
1480 &mut self,
1481 brush: Brush,
1482 center: Point,
1483 radius: f32,
1484 start_angle: f32,
1485 sweep_angle: f32,
1486 stroke: Option<Stroke>,
1487 inner_radius: f32,
1488 blend_mode: BlendMode,
1489 ) {
1490 if blend_mode == BlendMode::SrcOver {
1494 if let Brush::Solid(color) = brush {
1495 let idx = self.rec.arcs.len();
1496 self.rec.tape.push(TapeRef::new(RecordKind::SolidArc, idx));
1497 self.rec.arcs.push(SolidArcRecord {
1498 center,
1499 radius,
1500 start_angle,
1501 sweep_angle,
1502 inner_radius,
1503 color,
1504 stroke,
1505 });
1506 return;
1507 }
1508 }
1509 let (band_inner, band_outer, cap) = arc_band(radius, inner_radius, stroke);
1510 let geometry = ArcGeometry::new(
1511 center,
1512 band_inner,
1513 band_outer,
1514 start_angle,
1515 sweep_angle,
1516 cap,
1517 );
1518 if geometry.is_degenerate() {
1519 return;
1520 }
1521 self.push_blended_primitive(
1522 DrawPrimitive::Arc {
1523 rect: geometry.bounds(),
1524 brush,
1525 center,
1526 radius,
1527 start_angle,
1528 sweep_angle,
1529 stroke,
1530 inner_radius,
1531 },
1532 blend_mode,
1533 );
1534 }
1535}
1536
1537fn materialize_solid_arc(record: &SolidArcRecord) -> Option<DrawPrimitive> {
1542 let (band_inner, band_outer, cap) = arc_band(record.radius, record.inner_radius, record.stroke);
1543 let geometry = ArcGeometry::new(
1544 record.center,
1545 band_inner,
1546 band_outer,
1547 record.start_angle,
1548 record.sweep_angle,
1549 cap,
1550 );
1551 if geometry.is_degenerate() {
1552 return None;
1553 }
1554 Some(DrawPrimitive::Arc {
1555 rect: geometry.bounds(),
1556 brush: Brush::Solid(record.color),
1557 center: record.center,
1558 radius: record.radius,
1559 start_angle: record.start_angle,
1560 sweep_angle: record.sweep_angle,
1561 stroke: record.stroke,
1562 inner_radius: record.inner_radius,
1563 })
1564}
1565
1566impl DrawScope for DrawScopeDefault {
1567 fn size(&self) -> Size {
1568 self.size
1569 }
1570
1571 fn draw_content(&mut self) {
1572 self.content_markers += 1;
1573 self.push_other(DrawPrimitive::Content);
1574 }
1575
1576 fn draw_rect(&mut self, brush: Brush) {
1577 self.draw_rect_blend(brush, BlendMode::SrcOver);
1578 }
1579
1580 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1581 self.push_blended_primitive(
1582 DrawPrimitive::Rect {
1583 rect: Rect::from_size(self.size),
1584 brush,
1585 stroke: None,
1586 },
1587 blend_mode,
1588 );
1589 }
1590
1591 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1592 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1593 }
1594
1595 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1596 self.push_blended_primitive(
1597 DrawPrimitive::Rect {
1598 rect,
1599 brush,
1600 stroke: None,
1601 },
1602 blend_mode,
1603 );
1604 }
1605
1606 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1607 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1608 }
1609
1610 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1611 self.push_blended_primitive(
1612 DrawPrimitive::RoundRect {
1613 rect: Rect::from_size(self.size),
1614 brush,
1615 radii,
1616 stroke: None,
1617 },
1618 blend_mode,
1619 );
1620 }
1621
1622 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1623 self.push_blended_primitive(
1624 DrawPrimitive::RoundRect {
1625 rect,
1626 brush,
1627 radii,
1628 stroke: None,
1629 },
1630 BlendMode::SrcOver,
1631 );
1632 }
1633
1634 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1635 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1636 }
1637
1638 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1639 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1640 }
1641
1642 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1643 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1644 }
1645
1646 fn draw_rect_at_stroked_blend(
1647 &mut self,
1648 rect: Rect,
1649 brush: Brush,
1650 stroke: Stroke,
1651 blend_mode: BlendMode,
1652 ) {
1653 if !stroke.is_visible() {
1654 return;
1655 }
1656 self.push_blended_primitive(
1657 DrawPrimitive::Rect {
1658 rect,
1659 brush,
1660 stroke: Some(stroke),
1661 },
1662 blend_mode,
1663 );
1664 }
1665
1666 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1667 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1668 }
1669
1670 fn draw_round_rect_stroked_blend(
1671 &mut self,
1672 brush: Brush,
1673 radii: CornerRadii,
1674 stroke: Stroke,
1675 blend_mode: BlendMode,
1676 ) {
1677 self.draw_round_rect_at_stroked_blend(
1678 Rect::from_size(self.size),
1679 brush,
1680 radii,
1681 stroke,
1682 blend_mode,
1683 );
1684 }
1685
1686 fn draw_round_rect_at_stroked(
1687 &mut self,
1688 rect: Rect,
1689 brush: Brush,
1690 radii: CornerRadii,
1691 stroke: Stroke,
1692 ) {
1693 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1694 }
1695
1696 fn draw_round_rect_at_stroked_blend(
1697 &mut self,
1698 rect: Rect,
1699 brush: Brush,
1700 radii: CornerRadii,
1701 stroke: Stroke,
1702 blend_mode: BlendMode,
1703 ) {
1704 if !stroke.is_visible() {
1705 return;
1706 }
1707 self.push_blended_primitive(
1708 DrawPrimitive::RoundRect {
1709 rect,
1710 brush,
1711 radii,
1712 stroke: Some(stroke),
1713 },
1714 blend_mode,
1715 );
1716 }
1717
1718 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1719 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1720 }
1721
1722 fn draw_circle_stroked_blend(
1723 &mut self,
1724 brush: Brush,
1725 center: Point,
1726 radius: f32,
1727 stroke: Stroke,
1728 blend_mode: BlendMode,
1729 ) {
1730 if !stroke.is_visible() || !radius.is_finite() {
1731 return;
1732 }
1733 let radius = radius.max(0.0);
1734 let diameter = radius * 2.0;
1735 self.draw_round_rect_at_stroked_blend(
1736 Rect {
1737 x: center.x - radius,
1738 y: center.y - radius,
1739 width: diameter,
1740 height: diameter,
1741 },
1742 brush,
1743 CornerRadii::uniform(radius),
1744 stroke,
1745 blend_mode,
1746 );
1747 }
1748
1749 fn draw_arc(
1750 &mut self,
1751 brush: Brush,
1752 center: Point,
1753 radius: f32,
1754 start_angle: f32,
1755 sweep_angle: f32,
1756 stroke: Stroke,
1757 ) {
1758 self.draw_arc_blend(
1759 brush,
1760 center,
1761 radius,
1762 start_angle,
1763 sweep_angle,
1764 stroke,
1765 BlendMode::SrcOver,
1766 );
1767 }
1768
1769 fn draw_arc_blend(
1770 &mut self,
1771 brush: Brush,
1772 center: Point,
1773 radius: f32,
1774 start_angle: f32,
1775 sweep_angle: f32,
1776 stroke: Stroke,
1777 blend_mode: BlendMode,
1778 ) {
1779 if !stroke.is_visible() {
1780 return;
1781 }
1782 self.push_arc(
1783 brush,
1784 center,
1785 radius,
1786 start_angle,
1787 sweep_angle,
1788 Some(stroke),
1789 0.0,
1790 blend_mode,
1791 );
1792 }
1793
1794 fn draw_annular_sector(
1795 &mut self,
1796 brush: Brush,
1797 center: Point,
1798 inner_radius: f32,
1799 outer_radius: f32,
1800 start_angle: f32,
1801 sweep_angle: f32,
1802 ) {
1803 self.draw_annular_sector_blend(
1804 brush,
1805 center,
1806 inner_radius,
1807 outer_radius,
1808 start_angle,
1809 sweep_angle,
1810 BlendMode::SrcOver,
1811 );
1812 }
1813
1814 fn draw_annular_sector_blend(
1815 &mut self,
1816 brush: Brush,
1817 center: Point,
1818 inner_radius: f32,
1819 outer_radius: f32,
1820 start_angle: f32,
1821 sweep_angle: f32,
1822 blend_mode: BlendMode,
1823 ) {
1824 self.push_arc(
1825 brush,
1826 center,
1827 outer_radius,
1828 start_angle,
1829 sweep_angle,
1830 None,
1831 inner_radius,
1832 blend_mode,
1833 );
1834 }
1835
1836 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1837 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1838 }
1839
1840 fn draw_circle_blend(
1841 &mut self,
1842 brush: Brush,
1843 center: Point,
1844 radius: f32,
1845 blend_mode: BlendMode,
1846 ) {
1847 let radius = radius.max(0.0);
1848 let diameter = radius * 2.0;
1849 self.push_blended_primitive(
1850 DrawPrimitive::RoundRect {
1851 rect: Rect {
1852 x: center.x - radius,
1853 y: center.y - radius,
1854 width: diameter,
1855 height: diameter,
1856 },
1857 brush,
1858 radii: CornerRadii::uniform(radius),
1859 stroke: None,
1860 },
1861 blend_mode,
1862 );
1863 }
1864
1865 fn draw_image(&mut self, image: ImageBitmap) {
1866 self.draw_image_blend(image, BlendMode::SrcOver);
1867 }
1868
1869 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1870 self.push_blended_primitive(
1871 DrawPrimitive::Image {
1872 rect: Rect::from_size(self.size),
1873 image,
1874 alpha: 1.0,
1875 color_filter: None,
1876 sampling: ImageSampling::Nearest,
1877 src_rect: None,
1878 },
1879 blend_mode,
1880 );
1881 }
1882
1883 fn draw_image_at(
1884 &mut self,
1885 rect: Rect,
1886 image: ImageBitmap,
1887 alpha: f32,
1888 color_filter: Option<ColorFilter>,
1889 ) {
1890 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1891 }
1892
1893 fn draw_image_at_sampled(
1894 &mut self,
1895 rect: Rect,
1896 image: ImageBitmap,
1897 alpha: f32,
1898 color_filter: Option<ColorFilter>,
1899 sampling: ImageSampling,
1900 ) {
1901 self.push_blended_primitive(
1902 DrawPrimitive::Image {
1903 rect,
1904 image,
1905 alpha: alpha.clamp(0.0, 1.0),
1906 color_filter,
1907 sampling,
1908 src_rect: None,
1909 },
1910 BlendMode::SrcOver,
1911 );
1912 }
1913
1914 fn draw_image_at_blend(
1915 &mut self,
1916 rect: Rect,
1917 image: ImageBitmap,
1918 alpha: f32,
1919 color_filter: Option<ColorFilter>,
1920 blend_mode: BlendMode,
1921 ) {
1922 self.push_blended_primitive(
1923 DrawPrimitive::Image {
1924 rect,
1925 image,
1926 alpha: alpha.clamp(0.0, 1.0),
1927 color_filter,
1928 sampling: ImageSampling::Nearest,
1929 src_rect: None,
1930 },
1931 blend_mode,
1932 );
1933 }
1934
1935 fn draw_image_src(
1936 &mut self,
1937 image: ImageBitmap,
1938 src_rect: Rect,
1939 dst_rect: Rect,
1940 alpha: f32,
1941 color_filter: Option<ColorFilter>,
1942 ) {
1943 self.draw_image_src_blend(
1944 image,
1945 src_rect,
1946 dst_rect,
1947 alpha,
1948 color_filter,
1949 BlendMode::SrcOver,
1950 );
1951 }
1952
1953 fn draw_image_src_sampled(
1954 &mut self,
1955 image: ImageBitmap,
1956 src_rect: Rect,
1957 dst_rect: Rect,
1958 alpha: f32,
1959 color_filter: Option<ColorFilter>,
1960 sampling: ImageSampling,
1961 ) {
1962 self.push_blended_primitive(
1963 DrawPrimitive::Image {
1964 rect: dst_rect,
1965 image,
1966 alpha: alpha.clamp(0.0, 1.0),
1967 color_filter,
1968 sampling,
1969 src_rect: Some(src_rect),
1970 },
1971 BlendMode::SrcOver,
1972 );
1973 }
1974
1975 fn draw_image_src_blend(
1976 &mut self,
1977 image: ImageBitmap,
1978 src_rect: Rect,
1979 dst_rect: Rect,
1980 alpha: f32,
1981 color_filter: Option<ColorFilter>,
1982 blend_mode: BlendMode,
1983 ) {
1984 self.push_blended_primitive(
1985 DrawPrimitive::Image {
1986 rect: dst_rect,
1987 image,
1988 alpha: alpha.clamp(0.0, 1.0),
1989 color_filter,
1990 sampling: ImageSampling::Nearest,
1991 src_rect: Some(src_rect),
1992 },
1993 blend_mode,
1994 );
1995 }
1996
1997 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1998 const SUPERSAMPLE: f32 = 2.0;
2003 const MAX_MASK_PIXELS: f32 = 4096.0;
2005
2006 if path.is_empty() {
2007 return;
2008 }
2009 let bounds = path.bounds();
2010 if bounds.width <= 0.0 || bounds.height <= 0.0 {
2011 return;
2012 }
2013
2014 let color = match &brush {
2015 Brush::Solid(color) => *color,
2016 Brush::LinearGradient { colors, .. }
2017 | Brush::RadialGradient { colors, .. }
2018 | Brush::SweepGradient { colors, .. } => match colors.first() {
2019 Some(color) => *color,
2020 None => return,
2021 },
2022 };
2023 if color.3 <= 0.0 {
2024 return;
2025 }
2026
2027 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
2030 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
2031 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
2032 let mask_width = (rect_width * SUPERSAMPLE)
2033 .ceil()
2034 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2035 let mask_height = (rect_height * SUPERSAMPLE)
2036 .ceil()
2037 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2038
2039 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
2040
2041 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2042 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2043 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2044 let alpha = color.3.clamp(0.0, 1.0);
2045
2046 let mut pixels = Vec::with_capacity(mask.len() * 4);
2047 for coverage in mask {
2048 pixels.extend_from_slice(&[red, green, blue, (alpha * coverage as f32 + 0.5) as u8]);
2049 }
2050
2051 let Ok(image) = ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
2052 else {
2053 return;
2054 };
2055
2056 self.push_other(DrawPrimitive::Image {
2057 rect: Rect {
2058 x: origin.x,
2059 y: origin.y,
2060 width: rect_width,
2061 height: rect_height,
2062 },
2063 image,
2064 alpha: 1.0,
2065 color_filter: None,
2066 sampling: ImageSampling::Linear,
2067 src_rect: None,
2068 });
2069 }
2070
2071 fn measure_text(&self, text: &str, style: &TextStyle) -> TextMeasurement {
2072 match &self.text_measurer {
2073 Some(measurer) => measurer.measure_text(text, style),
2074 None => estimate_text_measurement(text, style),
2075 }
2076 }
2077
2078 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &TextStyle) {
2079 if text.is_empty() {
2082 return;
2083 }
2084 let Some(color) = solid_fill_color(&brush) else {
2085 return;
2086 };
2087 if color.3 <= 0.0 {
2088 return;
2089 }
2090 let measurement = self.measure_text(text, style);
2091 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
2092 return;
2093 }
2094 let origin = align_text_block(rect, measurement, style);
2095 if !origin.x.is_finite() || !origin.y.is_finite() {
2096 return;
2097 }
2098 self.push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
2099 rect: Rect::from_origin_size(origin, measurement.size),
2100 text: shared_text_str(text),
2101 style: style.clone(),
2102 color,
2103 })));
2104 }
2105
2106 fn into_primitives(self) -> Vec<DrawPrimitive> {
2107 self.finish().primitives
2108 }
2109}
2110
2111fn solid_fill_color(brush: &Brush) -> Option<Color> {
2116 match brush {
2117 Brush::Solid(color) => Some(*color),
2118 Brush::LinearGradient { colors, .. }
2119 | Brush::RadialGradient { colors, .. }
2120 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
2121 }
2122}
2123
2124#[cfg(test)]
2125mod tests {
2126 use super::*;
2127 use crate::{Color, FontStyle, FontWeight, ImageBitmap, RenderEffect};
2128
2129 #[test]
2134 fn compact_recording_materializes_in_recorded_order() {
2135 let size = Size::new(100.0, 100.0);
2136 let solid = Brush::solid(Color::WHITE);
2137 let gradient = Brush::vertical_gradient(vec![Color::RED, Color::BLUE], 0.0, 100.0);
2138 let center = Point::new(50.0, 50.0);
2139 let stroke = Stroke::new(4.0);
2140 let rect = Rect {
2141 x: 10.0,
2142 y: 20.0,
2143 width: 30.0,
2144 height: 40.0,
2145 };
2146 let batch = vec![
2147 DrawPrimitive::Content,
2148 DrawPrimitive::Rect {
2149 rect,
2150 brush: solid.clone(),
2151 stroke: None,
2152 },
2153 ];
2154
2155 let record = |scope: &mut DrawScopeDefault| {
2157 scope.draw_rect_at(rect, solid.clone());
2158 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 1.5, stroke);
2159 scope.draw_rect_at(rect, gradient.clone());
2160 scope.draw_circle(solid.clone(), center, 12.0);
2161 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 0.0, stroke); scope.draw_rect_at_blend(rect, solid.clone(), BlendMode::Plus);
2163 scope.draw_content();
2164 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2165 scope.push_recorded(batch.clone());
2166 };
2167
2168 let mut compact = DrawScopeDefault::new(size);
2169 record(&mut compact);
2170 let finished = compact.finish();
2171
2172 let arc_via_ordinary = |brush: Brush, radius: f32, start: f32, sweep: f32| {
2176 let mut scope = DrawScopeDefault::new(size);
2177 scope.draw_arc(brush, center, radius, start, sweep, stroke);
2178 scope.into_primitives().remove(0)
2179 };
2180 let expected = vec![
2181 DrawPrimitive::Rect {
2182 rect,
2183 brush: solid.clone(),
2184 stroke: None,
2185 },
2186 arc_via_ordinary(solid.clone(), 30.0, 0.5, 1.5),
2187 DrawPrimitive::Rect {
2188 rect,
2189 brush: gradient.clone(),
2190 stroke: None,
2191 },
2192 DrawPrimitive::RoundRect {
2193 rect: Rect {
2194 x: center.x - 12.0,
2195 y: center.y - 12.0,
2196 width: 24.0,
2197 height: 24.0,
2198 },
2199 brush: solid.clone(),
2200 radii: CornerRadii::uniform(12.0),
2201 stroke: None,
2202 },
2203 DrawPrimitive::Blend {
2204 primitive: Box::new(DrawPrimitive::Rect {
2205 rect,
2206 brush: solid.clone(),
2207 stroke: None,
2208 }),
2209 blend_mode: BlendMode::Plus,
2210 },
2211 DrawPrimitive::Content,
2212 {
2213 let mut scope = DrawScopeDefault::new(size);
2214 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2215 scope.into_primitives().remove(0)
2216 },
2217 DrawPrimitive::Content,
2218 DrawPrimitive::Rect {
2219 rect,
2220 brush: solid.clone(),
2221 stroke: None,
2222 },
2223 ];
2224 assert_eq!(finished.primitives, expected);
2225 assert_eq!(finished.content_markers, 2);
2226 }
2227
2228 #[test]
2231 fn reused_recording_buffers_record_identically_to_fresh() {
2232 let size = Size::new(64.0, 64.0);
2233 let record = |scope: &mut DrawScopeDefault| {
2234 scope.draw_circle(Brush::solid(Color::RED), Point::new(32.0, 32.0), 10.0);
2235 scope.draw_arc(
2236 Brush::solid(Color::BLUE),
2237 Point::new(32.0, 32.0),
2238 20.0,
2239 0.0,
2240 3.0,
2241 Stroke::new(2.0),
2242 );
2243 };
2244
2245 let mut fresh = DrawScopeDefault::new(size);
2246 record(&mut fresh);
2247 let fresh = fresh.finish();
2248
2249 let mut dirty = DrawScopeDefault::new(size);
2251 dirty.draw_rect(Brush::solid(Color::BLACK));
2252 dirty.draw_content();
2253 dirty.draw_arc(
2254 Brush::solid(Color::WHITE),
2255 Point::new(1.0, 1.0),
2256 5.0,
2257 1.0,
2258 1.0,
2259 Stroke::new(1.0),
2260 );
2261 let dirty = dirty.finish();
2262
2263 let mut reused =
2264 DrawScopeDefault::with_recording(size, None, dirty.recording, dirty.primitives);
2265 record(&mut reused);
2266 let reused = reused.finish();
2267
2268 assert_eq!(fresh.primitives, reused.primitives);
2269 assert_eq!(fresh.content_markers, reused.content_markers);
2270 }
2271
2272 #[test]
2273 fn redrawing_the_same_text_shares_one_str_allocation() {
2274 let first = shared_text_str("BREAK THE RING");
2275 let second = shared_text_str("BREAK THE RING");
2276 assert!(Rc::ptr_eq(&first, &second));
2277 assert_eq!(&*second, "BREAK THE RING");
2278 }
2279
2280 #[test]
2281 fn different_text_gets_its_own_str() {
2282 let first = shared_text_str("340");
2283 let second = shared_text_str("350");
2284 assert!(!Rc::ptr_eq(&first, &second));
2285 assert_eq!(&*first, "340");
2286 assert_eq!(&*second, "350");
2287 }
2288
2289 #[test]
2290 fn the_text_pool_survives_overflowing_its_capacity() {
2291 for index in 0..600 {
2292 let text = format!("run-{index}");
2293 assert_eq!(&*shared_text_str(&text), text.as_str());
2294 }
2295 assert_eq!(&*shared_text_str("still correct"), "still correct");
2296 }
2297
2298 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
2299 match primitive {
2300 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
2301 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
2302 other => panic!("expected image primitive, got {other:?}"),
2303 }
2304 }
2305
2306 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
2307 match primitive {
2308 DrawPrimitive::Image { .. } => primitive,
2309 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
2310 other => panic!("expected image primitive, got {other:?}"),
2311 }
2312 }
2313
2314 #[test]
2315 fn draw_svg_path_emits_supersampled_image_over_path_bounds() {
2316 let mut scope = DrawScopeDefault::new(Size::new(32.0, 32.0));
2317 scope.draw_svg_path("M 4 4 H 20 V 20 H 4 Z", Brush::solid(Color::RED));
2318
2319 let primitives = scope.into_primitives();
2320 assert_eq!(primitives.len(), 1);
2321 let DrawPrimitive::Image { rect, image, .. } = &primitives[0] else {
2322 panic!("expected image primitive, got {:?}", primitives[0]);
2323 };
2324
2325 assert_eq!((rect.x, rect.y), (3.0, 3.0));
2327 assert_eq!((rect.width, rect.height), (18.0, 18.0));
2328 assert_eq!((image.width(), image.height()), (36, 36));
2330
2331 let pixels = image.pixels();
2334 let index = (18 * 36 + 18) * 4;
2335 assert_eq!(
2336 &pixels[index..index + 4],
2337 &[255, 0, 0, 255],
2338 "path interior must be opaque brush color"
2339 );
2340 assert_eq!(pixels[3], 0, "outside the path must stay transparent");
2342 }
2343
2344 #[test]
2345 fn draw_svg_path_ignores_invalid_data() {
2346 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2347 scope.draw_svg_path("definitely not a path", Brush::solid(Color::WHITE));
2348 assert!(scope.into_primitives().is_empty());
2349 }
2350
2351 #[test]
2352 fn draw_vector_path_applies_brush_alpha() {
2353 let path = crate::VectorPath::parse("M 0 0 H 8 V 8 H 0 Z").expect("valid path");
2354 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2355 scope.draw_vector_path(&path, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 0.5)));
2356
2357 let primitives = scope.into_primitives();
2358 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
2359 panic!("expected image primitive");
2360 };
2361 let pixels = image.pixels();
2362 let width = image.width() as usize;
2364 let index = ((image.height() as usize / 2) * width + width / 2) * 4;
2365 assert_eq!(&pixels[index..index + 3], &[0, 0, 255]);
2366 let alpha = pixels[index + 3];
2367 assert!(
2368 (alpha as i32 - 128).abs() <= 2,
2369 "interior alpha must honor the brush alpha, got {alpha}"
2370 );
2371 }
2372
2373 #[test]
2374 fn draw_content_inserts_content_marker() {
2375 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
2376 scope.draw_rect(Brush::solid(Color::WHITE));
2377 scope.draw_content();
2378 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
2379
2380 let primitives = scope.into_primitives();
2381 assert!(matches!(primitives[1], DrawPrimitive::Content));
2382 assert!(matches!(
2383 primitives[2],
2384 DrawPrimitive::Blend {
2385 blend_mode: BlendMode::DstOut,
2386 ..
2387 }
2388 ));
2389 }
2390
2391 #[test]
2392 fn draw_rect_blend_wraps_non_default_modes() {
2393 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2394 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
2395
2396 let primitives = scope.into_primitives();
2397 assert_eq!(primitives.len(), 1);
2398 match &primitives[0] {
2399 DrawPrimitive::Blend {
2400 primitive,
2401 blend_mode,
2402 } => {
2403 assert_eq!(*blend_mode, BlendMode::DstOut);
2404 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
2405 }
2406 other => panic!("expected blended primitive, got {other:?}"),
2407 }
2408 }
2409
2410 #[test]
2411 fn draw_circle_records_centered_round_rect() {
2412 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
2413 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
2414
2415 let primitives = scope.into_primitives();
2416 assert_eq!(primitives.len(), 1);
2417 match &primitives[0] {
2418 DrawPrimitive::RoundRect { rect, radii, .. } => {
2419 assert_eq!(
2420 *rect,
2421 Rect {
2422 x: 7.0,
2423 y: 11.0,
2424 width: 10.0,
2425 height: 10.0,
2426 }
2427 );
2428 assert_eq!(*radii, CornerRadii::uniform(5.0));
2429 }
2430 other => panic!("expected circular round rect, got {other:?}"),
2431 }
2432 }
2433
2434 #[test]
2435 fn draw_circle_blend_wraps_non_default_modes() {
2436 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2437 scope.draw_circle_blend(
2438 Brush::solid(Color::RED),
2439 Point::new(5.0, 5.0),
2440 3.0,
2441 BlendMode::Plus,
2442 );
2443
2444 let primitives = scope.into_primitives();
2445 assert_eq!(primitives.len(), 1);
2446 match &primitives[0] {
2447 DrawPrimitive::Blend {
2448 primitive,
2449 blend_mode,
2450 } => {
2451 assert_eq!(*blend_mode, BlendMode::Plus);
2452 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
2453 }
2454 other => panic!("expected blended circle primitive, got {other:?}"),
2455 }
2456 }
2457
2458 #[test]
2459 fn rect_union_encloses_both_inputs() {
2460 let lhs = Rect {
2461 x: 10.0,
2462 y: 5.0,
2463 width: 8.0,
2464 height: 4.0,
2465 };
2466 let rhs = Rect {
2467 x: 4.0,
2468 y: 7.0,
2469 width: 10.0,
2470 height: 6.0,
2471 };
2472
2473 assert_eq!(
2474 lhs.union(rhs),
2475 Rect {
2476 x: 4.0,
2477 y: 5.0,
2478 width: 14.0,
2479 height: 8.0,
2480 }
2481 );
2482 }
2483
2484 #[test]
2485 fn draw_image_uses_scope_size_as_default_rect() {
2486 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
2487 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
2488 scope.draw_image(image.clone());
2489 let primitives = scope.into_primitives();
2490 assert_eq!(primitives.len(), 1);
2491 match unwrap_image(&primitives[0]) {
2492 DrawPrimitive::Image {
2493 rect,
2494 image: actual,
2495 alpha,
2496 color_filter,
2497 sampling,
2498 src_rect,
2499 } => {
2500 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
2501 assert_eq!(*actual, image);
2502 assert_eq!(*alpha, 1.0);
2503 assert!(color_filter.is_none());
2504 assert_eq!(*sampling, ImageSampling::Nearest);
2505 assert!(src_rect.is_none());
2506 }
2507 other => panic!("expected image primitive, got {other:?}"),
2508 }
2509 }
2510
2511 #[test]
2512 fn draw_image_src_stores_src_rect() {
2513 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2514 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2515 let src = Rect {
2516 x: 10.0,
2517 y: 20.0,
2518 width: 30.0,
2519 height: 40.0,
2520 };
2521 let dst = Rect {
2522 x: 0.0,
2523 y: 0.0,
2524 width: 60.0,
2525 height: 80.0,
2526 };
2527 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
2528 let primitives = scope.into_primitives();
2529 assert_eq!(primitives.len(), 1);
2530 match unwrap_image(&primitives[0]) {
2531 DrawPrimitive::Image {
2532 rect,
2533 image: actual,
2534 alpha,
2535 sampling,
2536 src_rect,
2537 ..
2538 } => {
2539 assert_eq!(*rect, dst);
2540 assert_eq!(*actual, image);
2541 assert!((alpha - 0.8).abs() < 1e-5);
2542 assert_eq!(*sampling, ImageSampling::Nearest);
2543 assert_eq!(*src_rect, Some(src));
2544 }
2545 other => panic!("expected image primitive, got {other:?}"),
2546 }
2547 }
2548
2549 #[test]
2550 fn draw_image_at_sampled_records_requested_sampling() {
2551 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2552 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
2553 let dst = Rect {
2554 x: 2.0,
2555 y: 3.0,
2556 width: 40.0,
2557 height: 30.0,
2558 };
2559
2560 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
2561
2562 let primitives = scope.into_primitives();
2563 assert_eq!(primitives.len(), 1);
2564 match unwrap_image(&primitives[0]) {
2565 DrawPrimitive::Image {
2566 rect,
2567 image: actual,
2568 alpha,
2569 sampling,
2570 src_rect,
2571 ..
2572 } => {
2573 assert_eq!(*rect, dst);
2574 assert_eq!(*actual, image);
2575 assert!((alpha - 0.7).abs() < 1e-5);
2576 assert_eq!(*sampling, ImageSampling::Linear);
2577 assert!(src_rect.is_none());
2578 }
2579 other => panic!("expected image primitive, got {other:?}"),
2580 }
2581 }
2582
2583 #[test]
2584 fn draw_image_src_sampled_records_requested_sampling() {
2585 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2586 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2587 let src = Rect {
2588 x: 4.0,
2589 y: 6.0,
2590 width: 16.0,
2591 height: 20.0,
2592 };
2593 let dst = Rect {
2594 x: 8.0,
2595 y: 10.0,
2596 width: 32.0,
2597 height: 40.0,
2598 };
2599
2600 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
2601
2602 let primitives = scope.into_primitives();
2603 assert_eq!(primitives.len(), 1);
2604 match unwrap_image(&primitives[0]) {
2605 DrawPrimitive::Image {
2606 rect,
2607 image: actual,
2608 alpha,
2609 sampling,
2610 src_rect,
2611 ..
2612 } => {
2613 assert_eq!(*rect, dst);
2614 assert_eq!(*actual, image);
2615 assert!((alpha - 0.5).abs() < 1e-5);
2616 assert_eq!(*sampling, ImageSampling::Linear);
2617 assert_eq!(*src_rect, Some(src));
2618 }
2619 other => panic!("expected image primitive, got {other:?}"),
2620 }
2621 }
2622
2623 #[test]
2624 fn draw_image_at_clamps_alpha() {
2625 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2626 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
2627 scope.draw_image_at(
2628 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
2629 image,
2630 3.0,
2631 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
2632 );
2633 assert_image_alpha(&scope.into_primitives()[0], 1.0);
2634 }
2635
2636 #[test]
2637 fn graphics_layer_clone_with_render_effect() {
2638 let layer = GraphicsLayer {
2639 render_effect: Some(RenderEffect::blur(10.0)),
2640 backdrop_effect: Some(RenderEffect::blur(6.0)),
2641 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
2642 alpha: 0.5,
2643 rotation_z: 12.0,
2644 shadow_elevation: 4.0,
2645 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
2646 clip: true,
2647 compositing_strategy: CompositingStrategy::Offscreen,
2648 blend_mode: BlendMode::SrcOver,
2649 ..Default::default()
2650 };
2651 let cloned = layer.clone();
2652 assert_eq!(cloned.alpha, 0.5);
2653 assert!(cloned.render_effect.is_some());
2654 assert!(cloned.backdrop_effect.is_some());
2655 assert_eq!(layer.color_filter, cloned.color_filter);
2656 assert_eq!(layer.render_effect, cloned.render_effect);
2657 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
2658 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
2659 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
2660 assert_eq!(
2661 cloned.shape,
2662 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
2663 );
2664 assert!(cloned.clip);
2665 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
2666 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
2667 }
2668
2669 #[test]
2670 fn graphics_layer_default_has_no_effect() {
2671 let layer = GraphicsLayer::default();
2672 assert!(layer.color_filter.is_none());
2673 assert!(layer.render_effect.is_none());
2674 assert!(layer.backdrop_effect.is_none());
2675 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
2676 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
2677 assert_eq!(layer.alpha, 1.0);
2678 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
2679 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
2680 assert_eq!(layer.shape, LayerShape::Rectangle);
2681 assert!(!layer.clip);
2682 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
2683 assert_eq!(layer.spot_shadow_color, Color::BLACK);
2684 }
2685
2686 #[test]
2687 fn transform_origin_construction() {
2688 let origin = TransformOrigin::new(0.25, 0.75);
2689 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
2690 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
2691 }
2692
2693 #[test]
2694 fn layer_shape_default_is_rectangle() {
2695 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
2696 }
2697
2698 use crate::{StrokeCap, StrokeJoin};
2701 use std::f32::consts::{FRAC_PI_2, PI};
2702
2703 fn approx(a: f32, b: f32) -> bool {
2708 (a - b).abs() < 0.25
2709 }
2710
2711 fn scope(size: f32) -> DrawScopeDefault {
2712 DrawScopeDefault::new(Size::new(size, size))
2713 }
2714
2715 #[test]
2716 fn draw_rect_stroked_records_scope_rect_and_stroke() {
2717 let mut scope = scope(20.0);
2718 scope.draw_rect_stroked(
2719 Brush::solid(Color::RED),
2720 Stroke::new(3.0).with_join(StrokeJoin::Bevel),
2721 );
2722
2723 let primitives = scope.into_primitives();
2724 assert_eq!(primitives.len(), 1);
2725 match &primitives[0] {
2726 DrawPrimitive::Rect {
2727 rect,
2728 stroke: Some(stroke),
2729 ..
2730 } => {
2731 assert_eq!(*rect, Rect::from_size(Size::new(20.0, 20.0)));
2734 assert_eq!(stroke.width, 3.0);
2735 assert_eq!(stroke.join, StrokeJoin::Bevel);
2736 }
2737 other => panic!("expected stroked rect, got {other:?}"),
2738 }
2739 }
2740
2741 #[test]
2742 fn draw_rect_at_stroked_records_requested_rect() {
2743 let mut scope = scope(50.0);
2744 let rect = Rect {
2745 x: 4.0,
2746 y: 6.0,
2747 width: 12.0,
2748 height: 9.0,
2749 };
2750 scope.draw_rect_at_stroked(rect, Brush::solid(Color::BLUE), Stroke::new(2.0));
2751 match &scope.into_primitives()[0] {
2752 DrawPrimitive::Rect {
2753 rect: actual,
2754 stroke: Some(stroke),
2755 ..
2756 } => {
2757 assert_eq!(*actual, rect);
2758 assert_eq!(stroke.width, 2.0);
2759 }
2760 other => panic!("expected stroked rect, got {other:?}"),
2761 }
2762 }
2763
2764 #[test]
2765 fn draw_round_rect_stroked_keeps_radii_and_stroke() {
2766 let mut scope = scope(30.0);
2767 scope.draw_round_rect_stroked(
2768 Brush::solid(Color::GREEN),
2769 CornerRadii::uniform(5.0),
2770 Stroke::new(4.0).with_join(StrokeJoin::Round),
2771 );
2772 match &scope.into_primitives()[0] {
2773 DrawPrimitive::RoundRect {
2774 rect,
2775 radii,
2776 stroke: Some(stroke),
2777 ..
2778 } => {
2779 assert_eq!(*rect, Rect::from_size(Size::new(30.0, 30.0)));
2780 assert_eq!(*radii, CornerRadii::uniform(5.0));
2781 assert_eq!(stroke.width, 4.0);
2782 assert_eq!(stroke.join, StrokeJoin::Round);
2783 }
2784 other => panic!("expected stroked round rect, got {other:?}"),
2785 }
2786 }
2787
2788 #[test]
2789 fn draw_round_rect_at_stroked_records_requested_rect() {
2790 let mut scope = scope(60.0);
2791 let rect = Rect {
2792 x: 1.0,
2793 y: 2.0,
2794 width: 20.0,
2795 height: 10.0,
2796 };
2797 scope.draw_round_rect_at_stroked(
2798 rect,
2799 Brush::solid(Color::WHITE),
2800 CornerRadii::uniform(3.0),
2801 Stroke::new(1.5),
2802 );
2803 match &scope.into_primitives()[0] {
2804 DrawPrimitive::RoundRect {
2805 rect: actual,
2806 radii,
2807 stroke: Some(stroke),
2808 ..
2809 } => {
2810 assert_eq!(*actual, rect);
2811 assert_eq!(*radii, CornerRadii::uniform(3.0));
2812 assert_eq!(stroke.width, 1.5);
2813 }
2814 other => panic!("expected stroked round rect, got {other:?}"),
2815 }
2816 }
2817
2818 #[test]
2819 fn draw_circle_stroked_lowers_to_stroked_round_rect() {
2820 let mut scope = scope(40.0);
2823 scope.draw_circle_stroked(
2824 Brush::solid(Color::BLUE),
2825 Point::new(12.0, 16.0),
2826 5.0,
2827 Stroke::new(2.0),
2828 );
2829 match &scope.into_primitives()[0] {
2830 DrawPrimitive::RoundRect {
2831 rect,
2832 radii,
2833 stroke: Some(stroke),
2834 ..
2835 } => {
2836 assert_eq!(
2837 *rect,
2838 Rect {
2839 x: 7.0,
2840 y: 11.0,
2841 width: 10.0,
2842 height: 10.0,
2843 }
2844 );
2845 assert_eq!(*radii, CornerRadii::uniform(5.0));
2846 assert_eq!(stroke.width, 2.0);
2847 }
2848 other => panic!("expected stroked circular round rect, got {other:?}"),
2849 }
2850 }
2851
2852 #[test]
2853 fn draw_arc_records_arc_primitive_with_tight_bounds() {
2854 let mut scope = scope(200.0);
2855 scope.draw_arc(
2856 Brush::solid(Color::RED),
2857 Point::new(100.0, 100.0),
2858 50.0,
2859 0.0,
2860 FRAC_PI_2,
2861 Stroke::new(10.0),
2862 );
2863 let primitives = scope.into_primitives();
2864 assert_eq!(primitives.len(), 1);
2865 match &primitives[0] {
2866 DrawPrimitive::Arc {
2867 rect,
2868 center,
2869 radius,
2870 start_angle,
2871 sweep_angle,
2872 stroke: Some(stroke),
2873 inner_radius,
2874 ..
2875 } => {
2876 assert_eq!(*center, Point::new(100.0, 100.0));
2877 assert_eq!(*radius, 50.0);
2878 assert_eq!(*start_angle, 0.0);
2879 assert!(approx(*sweep_angle, FRAC_PI_2));
2880 assert_eq!(stroke.width, 10.0);
2881 assert_eq!(*inner_radius, 0.0);
2882 assert!(approx(rect.x, 100.0), "{rect:?}");
2885 assert!(approx(rect.y, 100.0), "{rect:?}");
2886 assert!(approx(rect.width, 55.0), "{rect:?}");
2887 assert!(approx(rect.height, 55.0), "{rect:?}");
2888 }
2889 other => panic!("expected arc primitive, got {other:?}"),
2890 }
2891 }
2892
2893 #[test]
2894 fn draw_arc_bounds_cover_a_quadrant_spanning_sweep() {
2895 let mut scope = scope(200.0);
2896 scope.draw_arc(
2899 Brush::solid(Color::RED),
2900 Point::new(100.0, 100.0),
2901 50.0,
2902 0.0,
2903 3.0 * FRAC_PI_2,
2904 Stroke::new(4.0),
2905 );
2906 let DrawPrimitive::Arc { rect, .. } = &scope.into_primitives()[0] else {
2907 panic!("expected arc primitive");
2908 };
2909 assert!(approx(rect.x, 48.0), "{rect:?}");
2910 assert!(approx(rect.y, 48.0), "{rect:?}");
2911 assert!(approx(rect.width, 104.0), "{rect:?}");
2912 assert!(approx(rect.height, 104.0), "{rect:?}");
2913 }
2914
2915 #[test]
2916 fn draw_annular_sector_records_inner_radius_and_no_stroke() {
2917 let mut scope = scope(200.0);
2918 scope.draw_annular_sector(
2919 Brush::solid(Color::WHITE),
2920 Point::new(100.0, 100.0),
2921 30.0,
2922 50.0,
2923 0.0,
2924 PI,
2925 );
2926 match &scope.into_primitives()[0] {
2927 DrawPrimitive::Arc {
2928 rect,
2929 center,
2930 radius,
2931 inner_radius,
2932 stroke,
2933 sweep_angle,
2934 ..
2935 } => {
2936 assert!(stroke.is_none(), "annular sectors are filled, not stroked");
2937 assert_eq!(*center, Point::new(100.0, 100.0));
2938 assert_eq!(*radius, 50.0);
2939 assert_eq!(*inner_radius, 30.0);
2940 assert!(approx(*sweep_angle, PI));
2941 assert!(approx(rect.x, 50.0), "{rect:?}");
2943 assert!(approx(rect.y, 100.0), "{rect:?}");
2944 assert!(approx(rect.width, 100.0), "{rect:?}");
2945 assert!(approx(rect.height, 50.0), "{rect:?}");
2946 }
2947 other => panic!("expected arc primitive, got {other:?}"),
2948 }
2949 }
2950
2951 #[test]
2952 fn draw_arc_blend_wraps_non_default_modes() {
2953 let mut scope = scope(100.0);
2954 scope.draw_arc_blend(
2955 Brush::solid(Color::RED),
2956 Point::new(50.0, 50.0),
2957 20.0,
2958 0.0,
2959 1.0,
2960 Stroke::new(2.0),
2961 BlendMode::DstOut,
2962 );
2963 match &scope.into_primitives()[0] {
2964 DrawPrimitive::Blend {
2965 primitive,
2966 blend_mode,
2967 } => {
2968 assert_eq!(*blend_mode, BlendMode::DstOut);
2969 assert!(matches!(**primitive, DrawPrimitive::Arc { .. }));
2970 }
2971 other => panic!("expected blended arc, got {other:?}"),
2972 }
2973 }
2974
2975 #[test]
2976 fn draw_annular_sector_blend_wraps_non_default_modes() {
2977 let mut scope = scope(100.0);
2978 scope.draw_annular_sector_blend(
2979 Brush::solid(Color::RED),
2980 Point::new(50.0, 50.0),
2981 5.0,
2982 20.0,
2983 0.0,
2984 1.0,
2985 BlendMode::Plus,
2986 );
2987 assert!(matches!(
2988 &scope.into_primitives()[0],
2989 DrawPrimitive::Blend {
2990 blend_mode: BlendMode::Plus,
2991 ..
2992 }
2993 ));
2994 }
2995
2996 #[test]
2997 fn stroked_blend_variants_wrap_non_default_modes() {
2998 let mut scope = scope(20.0);
2999 scope.draw_rect_stroked_blend(
3000 Brush::solid(Color::RED),
3001 Stroke::new(2.0),
3002 BlendMode::DstOut,
3003 );
3004 scope.draw_round_rect_stroked_blend(
3005 Brush::solid(Color::RED),
3006 CornerRadii::uniform(2.0),
3007 Stroke::new(2.0),
3008 BlendMode::DstOut,
3009 );
3010 scope.draw_circle_stroked_blend(
3011 Brush::solid(Color::RED),
3012 Point::new(10.0, 10.0),
3013 5.0,
3014 Stroke::new(2.0),
3015 BlendMode::DstOut,
3016 );
3017 let primitives = scope.into_primitives();
3018 assert_eq!(primitives.len(), 3);
3019 for primitive in &primitives {
3020 assert!(
3021 matches!(
3022 primitive,
3023 DrawPrimitive::Blend {
3024 blend_mode: BlendMode::DstOut,
3025 ..
3026 }
3027 ),
3028 "expected blended primitive, got {primitive:?}"
3029 );
3030 }
3031 }
3032
3033 #[test]
3034 fn negative_sweeps_and_overlong_sweeps_produce_finite_bounds() {
3035 let mut scope = scope(200.0);
3036 scope.draw_arc(
3037 Brush::solid(Color::RED),
3038 Point::new(100.0, 100.0),
3039 40.0,
3040 FRAC_PI_2,
3041 -FRAC_PI_2,
3042 Stroke::new(4.0),
3043 );
3044 scope.draw_arc(
3045 Brush::solid(Color::RED),
3046 Point::new(100.0, 100.0),
3047 40.0,
3048 0.3,
3049 crate::stroke::TAU * 4.0,
3050 Stroke::new(4.0),
3051 );
3052 let primitives = scope.into_primitives();
3053 assert_eq!(primitives.len(), 2);
3054
3055 let DrawPrimitive::Arc { rect: negative, .. } = &primitives[0] else {
3056 panic!("expected arc");
3057 };
3058 assert!(approx(negative.x, 100.0), "{negative:?}");
3060 assert!(approx(negative.y, 100.0), "{negative:?}");
3061 assert!(approx(negative.width, 42.0), "{negative:?}");
3062
3063 let DrawPrimitive::Arc { rect: full, .. } = &primitives[1] else {
3064 panic!("expected arc");
3065 };
3066 assert!(approx(full.x, 58.0), "{full:?}");
3068 assert!(approx(full.width, 84.0), "{full:?}");
3069 assert!(approx(full.height, 84.0), "{full:?}");
3070 }
3071
3072 #[test]
3073 fn degenerate_stroke_and_arc_inputs_emit_nothing_and_never_panic() {
3074 let mut scope = scope(50.0);
3075 let brush = Brush::solid(Color::RED);
3076 let center = Point::new(25.0, 25.0);
3077
3078 scope.draw_rect_stroked(brush.clone(), Stroke::new(0.0));
3080 scope.draw_rect_stroked(brush.clone(), Stroke::new(-4.0));
3081 scope.draw_rect_stroked(brush.clone(), Stroke::new(f32::NAN));
3082 scope.draw_round_rect_stroked(brush.clone(), CornerRadii::uniform(2.0), Stroke::new(0.0));
3083 scope.draw_circle_stroked(brush.clone(), center, 10.0, Stroke::new(0.0));
3084 scope.draw_circle_stroked(brush.clone(), center, f32::NAN, Stroke::new(2.0));
3085 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 0.0, Stroke::new(2.0));
3087 scope.draw_arc(brush.clone(), center, 10.0, 0.0, f32::NAN, Stroke::new(2.0));
3088 scope.draw_arc(
3089 brush.clone(),
3090 center,
3091 f32::INFINITY,
3092 0.0,
3093 1.0,
3094 Stroke::new(2.0),
3095 );
3096 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 1.0, Stroke::new(0.0));
3098 scope.draw_arc(brush.clone(), center, 0.0, 0.0, 1.0, Stroke::new(0.0));
3099 scope.draw_annular_sector(brush.clone(), center, 10.0, 10.0, 0.0, 1.0);
3101 scope.draw_annular_sector(brush.clone(), center, 20.0, 10.0, 0.0, 1.0);
3102 scope.draw_annular_sector(brush.clone(), center, 0.0, 0.0, 0.0, 1.0);
3103 scope.draw_annular_sector(brush.clone(), center, 0.0, 10.0, 0.0, 0.0);
3104 scope.draw_annular_sector(brush, center, f32::NAN, 10.0, 0.0, 1.0);
3105
3106 assert!(
3107 scope.into_primitives().is_empty(),
3108 "degenerate stroke/arc requests must not reach the renderer"
3109 );
3110 }
3111
3112 #[test]
3113 fn zero_radius_arc_with_positive_width_stays_finite() {
3114 let mut scope = scope(50.0);
3117 scope.draw_arc(
3118 Brush::solid(Color::RED),
3119 Point::new(25.0, 25.0),
3120 0.0,
3121 0.0,
3122 FRAC_PI_2,
3123 Stroke::new(6.0).with_cap(StrokeCap::Round),
3124 );
3125 let primitives = scope.into_primitives();
3126 assert_eq!(primitives.len(), 1);
3127 let DrawPrimitive::Arc { rect, .. } = &primitives[0] else {
3128 panic!("expected arc");
3129 };
3130 for value in [rect.x, rect.y, rect.width, rect.height] {
3131 assert!(value.is_finite(), "{rect:?}");
3132 }
3133 assert!(rect.width > 0.0 && rect.height > 0.0, "{rect:?}");
3134 }
3135
3136 struct FixedAdvanceTextMeasurer {
3141 advance: f32,
3142 line_height: f32,
3143 calls: std::cell::Cell<usize>,
3144 }
3145
3146 impl FixedAdvanceTextMeasurer {
3147 fn shared(advance: f32, line_height: f32) -> Rc<Self> {
3148 Rc::new(Self {
3149 advance,
3150 line_height,
3151 calls: std::cell::Cell::new(0),
3152 })
3153 }
3154 }
3155
3156 impl DrawTextMeasurer for FixedAdvanceTextMeasurer {
3157 fn measure_text(&self, text: &str, _style: &TextStyle) -> TextMeasurement {
3158 self.calls.set(self.calls.get() + 1);
3159 let lines: Vec<&str> = text.split('\n').collect();
3160 let width = lines
3161 .iter()
3162 .map(|line| line.chars().count() as f32 * self.advance)
3163 .fold(0.0_f32, f32::max);
3164 TextMeasurement {
3165 size: Size::new(width, lines.len() as f32 * self.line_height),
3166 line_height: self.line_height,
3167 first_baseline: self.line_height * 0.75,
3168 line_count: lines.len(),
3169 }
3170 }
3171 }
3172
3173 fn text_scope(size: Size) -> (DrawScopeDefault, Rc<FixedAdvanceTextMeasurer>) {
3174 let measurer = FixedAdvanceTextMeasurer::shared(10.0, 20.0);
3175 (
3176 DrawScopeDefault::with_text_measurer(size, measurer.clone()),
3177 measurer,
3178 )
3179 }
3180
3181 fn unwrap_text(primitive: &DrawPrimitive) -> &TextPrimitive {
3182 match primitive {
3183 DrawPrimitive::Text(text) => text,
3184 other => panic!("expected text primitive, got {other:?}"),
3185 }
3186 }
3187
3188 #[test]
3189 fn drawn_text_occupies_exactly_the_box_measure_text_reported() {
3190 let (mut scope, _) = text_scope(Size::new(200.0, 100.0));
3191 let style = TextStyle::new(16.0);
3192 let measured = scope.measure_text("ABCD", &style);
3193
3194 scope.draw_text_from(
3195 Point::new(7.0, 11.0),
3196 Brush::solid(Color::WHITE),
3197 "ABCD",
3198 &style,
3199 );
3200
3201 let primitives = scope.into_primitives();
3202 assert_eq!(primitives.len(), 1);
3203 let text = unwrap_text(&primitives[0]);
3204 assert_eq!(
3205 text.rect,
3206 Rect {
3207 x: 7.0,
3208 y: 11.0,
3209 width: measured.size.width,
3210 height: measured.size.height,
3211 },
3212 "the drawn block must be the measured block, or callers cannot center text"
3213 );
3214 assert_eq!(&*text.text, "ABCD");
3215 assert_eq!(text.color, Color::WHITE);
3216 }
3217
3218 #[test]
3219 fn text_alignment_positions_the_measured_block_inside_the_box() {
3220 let box_rect = Rect {
3221 x: 100.0,
3222 y: 50.0,
3223 width: 200.0,
3224 height: 80.0,
3225 };
3226 let cases = [
3228 (TextAlign::Left, TextVerticalAlign::Top, 100.0, 50.0),
3229 (TextAlign::Center, TextVerticalAlign::Center, 190.0, 80.0),
3230 (TextAlign::Right, TextVerticalAlign::Bottom, 280.0, 110.0),
3231 ];
3232 for (align, vertical_align, expected_x, expected_y) in cases {
3233 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3234 let style = TextStyle::new(16.0)
3235 .with_align(align)
3236 .with_vertical_align(vertical_align);
3237 scope.draw_text_at(box_rect, Brush::solid(Color::WHITE), "AB", &style);
3238 let primitives = scope.into_primitives();
3239 let text = unwrap_text(&primitives[0]);
3240 assert!(
3241 approx(text.rect.x, expected_x) && approx(text.rect.y, expected_y),
3242 "{align:?}/{vertical_align:?} placed the block at {:?}",
3243 text.rect
3244 );
3245 assert!(approx(text.rect.width, 20.0) && approx(text.rect.height, 20.0));
3246 }
3247 }
3248
3249 #[test]
3250 fn baseline_aligned_text_hangs_above_the_box_edge() {
3251 let (mut scope, _) = text_scope(Size::new(200.0, 200.0));
3252 let style = TextStyle::new(16.0).with_vertical_align(TextVerticalAlign::Baseline);
3253 let measured = scope.measure_text("Ag", &style);
3254 scope.draw_text_at(
3255 Rect {
3256 x: 0.0,
3257 y: 100.0,
3258 width: 200.0,
3259 height: 0.0,
3260 },
3261 Brush::solid(Color::WHITE),
3262 "Ag",
3263 &style,
3264 );
3265 let primitives = scope.into_primitives();
3266 let text = unwrap_text(&primitives[0]);
3267 assert!(
3269 approx(text.rect.y, 100.0 - measured.first_baseline),
3270 "{:?}",
3271 text.rect
3272 );
3273 }
3274
3275 #[test]
3276 fn draw_text_fills_the_whole_scope_rect() {
3277 let (mut scope, _) = text_scope(Size::new(120.0, 60.0));
3278 let style = TextStyle::new(16.0)
3279 .with_align(TextAlign::Right)
3280 .with_vertical_align(TextVerticalAlign::Bottom);
3281 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3282 let primitives = scope.into_primitives();
3283 let text = unwrap_text(&primitives[0]);
3284 assert!(
3285 approx(text.rect.x, 100.0) && approx(text.rect.y, 40.0),
3286 "{:?}",
3287 text.rect
3288 );
3289 }
3290
3291 #[test]
3292 fn draw_text_from_ignores_alignment_and_anchors_the_top_left() {
3293 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3294 let style = TextStyle::new(16.0)
3296 .with_align(TextAlign::Center)
3297 .with_vertical_align(TextVerticalAlign::Bottom);
3298 scope.draw_text_from(
3299 Point::new(30.0, 40.0),
3300 Brush::solid(Color::WHITE),
3301 "AB",
3302 &style,
3303 );
3304 let primitives = scope.into_primitives();
3305 let text = unwrap_text(&primitives[0]);
3306 assert!(
3307 approx(text.rect.x, 30.0) && approx(text.rect.y, 40.0),
3308 "{:?}",
3309 text.rect
3310 );
3311 }
3312
3313 #[test]
3314 fn multiline_text_measures_the_widest_line_and_stacks_the_lines() {
3315 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3316 let style = TextStyle::new(16.0);
3317 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "AB\nABCDE", &style);
3318 let primitives = scope.into_primitives();
3319 let text = unwrap_text(&primitives[0]);
3320 assert!(approx(text.rect.width, 50.0), "{:?}", text.rect);
3321 assert!(approx(text.rect.height, 40.0), "{:?}", text.rect);
3322 }
3323
3324 #[test]
3325 fn empty_text_draws_nothing_and_never_measures() {
3326 let (mut scope, measurer) = text_scope(Size::new(100.0, 100.0));
3327 scope.draw_text(Brush::solid(Color::WHITE), "", &TextStyle::new(16.0));
3328 scope.draw_text_at(
3329 Rect::from_size(Size::new(10.0, 10.0)),
3330 Brush::solid(Color::WHITE),
3331 "",
3332 &TextStyle::new(16.0),
3333 );
3334 scope.draw_text_from(
3335 Point::ZERO,
3336 Brush::solid(Color::WHITE),
3337 "",
3338 &TextStyle::new(16.0),
3339 );
3340 assert!(scope.into_primitives().is_empty());
3341 assert_eq!(
3342 measurer.calls.get(),
3343 0,
3344 "an empty string must not cost a measurement"
3345 );
3346 }
3347
3348 #[test]
3349 fn invisible_text_draws_nothing() {
3350 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3351 let style = TextStyle::new(16.0);
3352 scope.draw_text(Brush::solid(Color(1.0, 1.0, 1.0, 0.0)), "AB", &style);
3353 scope.draw_text(
3354 Brush::LinearGradient {
3355 colors: Vec::new(),
3356 stops: None,
3357 start: Point::ZERO,
3358 end: Point::new(1.0, 1.0),
3359 tile_mode: crate::render_effect::TileMode::Clamp,
3360 },
3361 "AB",
3362 &style,
3363 );
3364 assert!(scope.into_primitives().is_empty());
3365 }
3366
3367 #[test]
3368 fn gradient_text_brushes_fall_back_to_their_first_stop() {
3369 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3370 scope.draw_text(
3371 Brush::linear_gradient(vec![Color::RED, Color::BLUE]),
3372 "AB",
3373 &TextStyle::new(16.0),
3374 );
3375 let primitives = scope.into_primitives();
3376 assert_eq!(unwrap_text(&primitives[0]).color, Color::RED);
3377 }
3378
3379 #[test]
3380 fn a_scope_without_a_measurer_falls_back_to_the_font_free_estimate() {
3381 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
3382 let style = TextStyle::new(16.0);
3383 assert_eq!(
3384 scope.measure_text("ABC", &style),
3385 crate::estimate_text_measurement("ABC", &style)
3386 );
3387 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "ABC", &style);
3388 let primitives = scope.into_primitives();
3389 let text = unwrap_text(&primitives[0]);
3390 assert!(text.rect.width > 0.0 && text.rect.height > 0.0);
3391 }
3392
3393 #[test]
3394 fn degenerate_text_geometry_emits_nothing_and_never_panics() {
3395 struct DegenerateTextMeasurer;
3396 impl DrawTextMeasurer for DegenerateTextMeasurer {
3397 fn measure_text(&self, _text: &str, _style: &TextStyle) -> TextMeasurement {
3398 TextMeasurement {
3399 size: Size::new(f32::NAN, 0.0),
3400 line_height: f32::NAN,
3401 first_baseline: f32::NAN,
3402 line_count: 1,
3403 }
3404 }
3405 }
3406
3407 let mut scope = DrawScopeDefault::with_text_measurer(
3408 Size::new(50.0, 50.0),
3409 Rc::new(DegenerateTextMeasurer),
3410 );
3411 scope.draw_text(Brush::solid(Color::WHITE), "AB", &TextStyle::new(16.0));
3412 scope.draw_text_at(
3413 Rect {
3414 x: f32::NAN,
3415 y: 0.0,
3416 width: 10.0,
3417 height: 10.0,
3418 },
3419 Brush::solid(Color::WHITE),
3420 "AB",
3421 &TextStyle::new(16.0),
3422 );
3423 assert!(
3424 scope.into_primitives().is_empty(),
3425 "unmeasurable text must not reach the renderer"
3426 );
3427 }
3428
3429 #[test]
3430 fn text_style_survives_lowering_into_the_primitive() {
3431 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3432 let style = TextStyle::new(21.0)
3433 .with_font_family("Fira Sans")
3434 .with_weight(FontWeight::BOLD)
3435 .with_style(FontStyle::Italic)
3436 .with_letter_spacing(2.0)
3437 .with_line_height(26.0);
3438 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3439 let primitives = scope.into_primitives();
3440 assert_eq!(unwrap_text(&primitives[0]).style, style);
3441 }
3442
3443 #[test]
3444 fn a_layers_composite_alpha_is_a_truncated_byte() {
3445 for byte in 0..=255u32 {
3446 let exact = byte as f32 / 255.0;
3447 assert!(
3448 (GraphicsLayer::composite_alpha_8bit(exact) - exact).abs() < 1e-6,
3449 "byte {byte} moved"
3450 );
3451 if byte < 255 {
3452 let nearly_next = (byte as f32 + 0.999) / 255.0;
3457 assert!(
3458 (GraphicsLayer::composite_alpha_8bit(nearly_next) - exact).abs() < 1e-6,
3459 "byte {byte} + 0.999 did not truncate"
3460 );
3461 }
3462 }
3463 assert_eq!(GraphicsLayer::composite_alpha_8bit(1.0), 1.0);
3464 assert_eq!(GraphicsLayer::composite_alpha_8bit(0.0), 0.0);
3465 assert_eq!(GraphicsLayer::composite_alpha_8bit(-3.0), 0.0);
3466 assert_eq!(GraphicsLayer::composite_alpha_8bit(7.0), 1.0);
3467 }
3468}