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 pub(crate) fn raw(self) -> u32 {
910 self.0
911 }
912}
913
914#[derive(Clone, Copy, Debug, PartialEq)]
916pub struct SolidRectRecord {
917 pub rect: Rect,
918 pub color: Color,
919 pub stroke: Option<Stroke>,
920}
921
922#[derive(Clone, Copy, Debug, PartialEq)]
925pub struct SolidRoundRectRecord {
926 pub rect: Rect,
927 pub radii: CornerRadii,
928 pub color: Color,
929 pub stroke: Option<Stroke>,
930}
931
932#[derive(Clone, Copy, Debug, PartialEq)]
937pub struct SolidArcRecord {
938 pub center: Point,
939 pub radius: f32,
940 pub start_angle: f32,
941 pub sweep_angle: f32,
942 pub inner_radius: f32,
943 pub color: Color,
944 pub stroke: Option<Stroke>,
945}
946
947#[derive(Clone, Debug, Default)]
955pub struct CommandRecording {
956 pub(crate) tape: Vec<TapeRef>,
961 pub(crate) rects: Vec<SolidRectRecord>,
962 pub(crate) round_rects: Vec<SolidRoundRectRecord>,
963 pub(crate) arcs: Vec<SolidArcRecord>,
964 pub(crate) others: Vec<DrawPrimitive>,
965}
966
967impl CommandRecording {
968 pub fn len(&self) -> usize {
970 self.tape.len()
971 }
972
973 pub fn materialize_range(
978 &self,
979 tape_start: usize,
980 tape_end: usize,
981 ) -> Option<Vec<DrawPrimitive>> {
982 if tape_start > tape_end || tape_end > self.tape.len() {
983 return None;
984 }
985 let mut out = Vec::with_capacity(tape_end - tape_start);
986 for entry in &self.tape[tape_start..tape_end] {
987 match entry.kind() {
988 RecordKind::SolidRect => {
989 let record = self.rects.get(entry.index())?;
990 out.push(DrawPrimitive::Rect {
991 rect: record.rect,
992 brush: Brush::Solid(record.color),
993 stroke: record.stroke,
994 });
995 }
996 RecordKind::SolidRoundRect => {
997 let record = self.round_rects.get(entry.index())?;
998 out.push(DrawPrimitive::RoundRect {
999 rect: record.rect,
1000 brush: Brush::Solid(record.color),
1001 radii: record.radii,
1002 stroke: record.stroke,
1003 });
1004 }
1005 RecordKind::SolidArc => {
1006 let record = self.arcs.get(entry.index())?;
1007 if let Some(primitive) = materialize_solid_arc(record) {
1008 out.push(primitive);
1009 }
1010 }
1011 RecordKind::Other => {
1012 out.push(self.others.get(entry.index())?.clone());
1013 }
1014 }
1015 }
1016 Some(out)
1017 }
1018
1019 pub fn is_empty(&self) -> bool {
1020 self.tape.is_empty()
1021 }
1022
1023 #[doc(hidden)]
1027 pub fn tape_ptr(&self) -> *const u8 {
1028 self.tape.as_ptr() as *const u8
1029 }
1030
1031 fn clear(&mut self) {
1032 self.tape.clear();
1033 self.rects.clear();
1034 self.round_rects.clear();
1035 self.arcs.clear();
1036 self.others.clear();
1037 }
1038
1039 pub(crate) fn clone_records_from(&mut self, source: &Self) {
1045 self.tape.clone_from(&source.tape);
1046 self.rects.clone_from(&source.rects);
1047 self.round_rects.clone_from(&source.round_rects);
1048 self.arcs.clone_from(&source.arcs);
1049 self.others.clone_from(&source.others);
1050 }
1051}
1052
1053pub struct FinishedRecording {
1057 pub primitives: Vec<DrawPrimitive>,
1058 pub content_markers: u32,
1059 pub recording: CommandRecording,
1060 pub dropped: Vec<u32>,
1065}
1066
1067#[derive(Default)]
1068pub struct DrawScopeDefault {
1069 size: Size,
1070 rec: CommandRecording,
1073 out: Vec<DrawPrimitive>,
1076 content_markers: u32,
1080 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1084}
1085
1086const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1093
1094thread_local! {
1095 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
1096 std::cell::RefCell::new(std::collections::HashMap::new());
1097}
1098
1099fn recorded_primitive_capacity(size: Size) -> usize {
1100 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1101 counts
1102 .borrow()
1103 .get(&(size.width.to_bits(), size.height.to_bits()))
1104 .copied()
1105 .unwrap_or(0)
1106 })
1107}
1108
1109fn note_recorded_primitive_count(size: Size, count: usize) {
1110 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1111 let mut counts = counts.borrow_mut();
1112 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1113 counts.clear();
1114 }
1115 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1116 });
1117}
1118
1119impl DrawScopeDefault {
1120 pub fn new(size: Size) -> Self {
1121 Self::with_recording(size, None, CommandRecording::default(), Vec::new())
1122 }
1123
1124 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1129 Self::with_recording(
1130 size,
1131 Some(text_measurer),
1132 CommandRecording::default(),
1133 Vec::new(),
1134 )
1135 }
1136
1137 pub fn with_text_measurer_reusing(
1143 size: Size,
1144 text_measurer: Rc<dyn DrawTextMeasurer>,
1145 storage: Vec<DrawPrimitive>,
1146 ) -> Self {
1147 Self::with_recording(
1148 size,
1149 Some(text_measurer),
1150 CommandRecording::default(),
1151 storage,
1152 )
1153 }
1154
1155 pub fn with_recording(
1159 size: Size,
1160 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1161 mut recording: CommandRecording,
1162 out: Vec<DrawPrimitive>,
1163 ) -> Self {
1164 recording.clear();
1165 recording.tape.reserve(recorded_primitive_capacity(size));
1166 Self {
1167 size,
1168 rec: recording,
1169 out,
1170 content_markers: 0,
1171 text_measurer,
1172 }
1173 }
1174
1175 pub fn content_marker_count(&self) -> u32 {
1179 self.content_markers
1180 }
1181
1182 pub fn recorded(&self) -> &CommandRecording {
1185 &self.rec
1186 }
1187
1188 pub fn push_recorded(&mut self, primitives: Vec<DrawPrimitive>) {
1193 self.content_markers += primitives
1194 .iter()
1195 .filter(|primitive| matches!(primitive, DrawPrimitive::Content))
1196 .count() as u32;
1197 let base = self.rec.others.len();
1198 self.rec
1199 .tape
1200 .extend((0..primitives.len()).map(|i| TapeRef::new(RecordKind::Other, base + i)));
1201 self.rec.others.extend(primitives);
1202 }
1203
1204 pub fn finish(mut self) -> FinishedRecording {
1210 if std::env::var_os("CRANPOSE_RECORD_MIX_DIAG").is_some() && self.rec.tape.len() > 400 {
1213 eprintln!(
1214 "[record-mix] tape={} rects={} round_rects={} arcs={} others={}",
1215 self.rec.tape.len(),
1216 self.rec.rects.len(),
1217 self.rec.round_rects.len(),
1218 self.rec.arcs.len(),
1219 self.rec.others.len(),
1220 );
1221 }
1222 let mut out = std::mem::take(&mut self.out);
1223 out.clear();
1224 out.reserve(self.rec.tape.len());
1225 let mut dropped: Vec<u32> = Vec::new();
1226 {
1227 let mut others = self.rec.others.drain(..);
1231 for (tape_index, entry) in self.rec.tape.iter().enumerate() {
1232 match entry.kind() {
1233 RecordKind::SolidRect => {
1234 let record = &self.rec.rects[entry.index()];
1235 out.push(DrawPrimitive::Rect {
1236 rect: record.rect,
1237 brush: Brush::Solid(record.color),
1238 stroke: record.stroke,
1239 });
1240 }
1241 RecordKind::SolidRoundRect => {
1242 let record = &self.rec.round_rects[entry.index()];
1243 out.push(DrawPrimitive::RoundRect {
1244 rect: record.rect,
1245 brush: Brush::Solid(record.color),
1246 radii: record.radii,
1247 stroke: record.stroke,
1248 });
1249 }
1250 RecordKind::SolidArc => {
1251 let record = &self.rec.arcs[entry.index()];
1252 if let Some(primitive) = materialize_solid_arc(record) {
1253 out.push(primitive);
1254 } else {
1255 dropped.push(tape_index as u32);
1256 }
1257 }
1258 RecordKind::Other => {
1259 out.push(others.next().expect("tape/others in sync"));
1260 }
1261 }
1262 }
1263 }
1264 self.rec.clear();
1265 note_recorded_primitive_count(self.size, out.len());
1266 FinishedRecording {
1267 primitives: out,
1268 content_markers: self.content_markers,
1269 recording: self.rec,
1270 dropped,
1271 }
1272 }
1273
1274 pub fn finish_recording_only(mut self) -> FinishedRecording {
1283 let mut out = std::mem::take(&mut self.out);
1284 out.clear();
1285 note_recorded_primitive_count(self.size, self.rec.tape.len());
1286 self.rec.clear();
1287 FinishedRecording {
1288 primitives: out,
1289 content_markers: self.content_markers,
1290 recording: self.rec,
1291 dropped: Vec::new(),
1292 }
1293 }
1294
1295 pub fn finish_replay(
1303 mut self,
1304 center: Point,
1305 outcome: crate::record_replay::ReplayOutcome,
1306 bypass: &mut dyn FnMut(u32) -> bool,
1307 ) -> (
1308 FinishedRecording,
1309 Option<crate::record_replay::CommandReplayFrame>,
1310 ) {
1311 use crate::record_replay::{CommandReplayFrame, FrameSpan, ReplayOutcome, ReplaySpan};
1312 let ReplayOutcome::Spans(replay_spans) = outcome else {
1313 return (self.finish(), None);
1314 };
1315 let tape_len = self.rec.tape.len();
1316 let mut out = std::mem::take(&mut self.out);
1317 out.clear();
1318 let mut dropped: Vec<u32> = Vec::new();
1319 let mut spans: Vec<FrameSpan> = Vec::with_capacity(replay_spans.len());
1320 let mut any_retained = false;
1321 {
1322 let mut others = self.rec.others.drain(..);
1327 let tape = &self.rec.tape;
1328 let rects = &self.rec.rects;
1329 let round_rects = &self.rec.round_rects;
1330 let arcs = &self.rec.arcs;
1331 macro_rules! materialize_range {
1335 ($start:expr, $end:expr) => {{
1336 let prim_start = out.len() as u32;
1337 for tape_index in $start..$end {
1338 let entry = tape[tape_index];
1339 match entry.kind() {
1340 RecordKind::SolidRect => {
1341 let record = &rects[entry.index()];
1342 out.push(DrawPrimitive::Rect {
1343 rect: record.rect,
1344 brush: Brush::Solid(record.color),
1345 stroke: record.stroke,
1346 });
1347 }
1348 RecordKind::SolidRoundRect => {
1349 let record = &round_rects[entry.index()];
1350 out.push(DrawPrimitive::RoundRect {
1351 rect: record.rect,
1352 brush: Brush::Solid(record.color),
1353 radii: record.radii,
1354 stroke: record.stroke,
1355 });
1356 }
1357 RecordKind::SolidArc => {
1358 let record = &arcs[entry.index()];
1359 if let Some(primitive) = materialize_solid_arc(record) {
1360 out.push(primitive);
1361 } else {
1362 dropped.push(tape_index as u32);
1363 }
1364 }
1365 RecordKind::Other => {
1366 out.push(others.next().expect("tape/others in sync"));
1367 }
1368 }
1369 }
1370 (prim_start, out.len() as u32)
1371 }};
1372 }
1373 for span in replay_spans {
1374 match span {
1375 ReplaySpan::Dynamic {
1376 tape_start,
1377 tape_end,
1378 } => {
1379 let range = materialize_range!(tape_start, tape_end);
1380 if range.1 > range.0 {
1381 spans.push(FrameSpan::Dynamic { range });
1382 }
1383 }
1384 ReplaySpan::Retained {
1385 slot,
1386 capture,
1387 slot_offset,
1388 tape_start,
1389 tape_end,
1390 transform,
1391 recolors,
1392 bounds,
1393 } => {
1394 let compact = tape[tape_start..tape_end]
1398 .iter()
1399 .all(|entry| entry.kind() != RecordKind::Other);
1400 if compact && !capture && bypass(slot) {
1401 any_retained = true;
1407 let position = out.len() as u32;
1408 spans.push(FrameSpan::Retained {
1409 slot,
1410 capture: false,
1411 slot_offset: slot_offset as u32,
1412 range: (position, position),
1413 tape_range: (tape_start as u32, tape_end as u32),
1414 transform,
1415 recolors,
1416 bounds,
1417 });
1418 continue;
1419 }
1420 let drops_before = dropped.len();
1421 let range = materialize_range!(tape_start, tape_end);
1422 if compact && dropped.len() == drops_before {
1423 any_retained = true;
1424 spans.push(FrameSpan::Retained {
1425 slot,
1426 capture,
1427 slot_offset: slot_offset as u32,
1428 range,
1429 tape_range: (tape_start as u32, tape_end as u32),
1430 transform,
1431 recolors,
1432 bounds,
1433 });
1434 } else if range.1 > range.0 {
1435 spans.push(FrameSpan::Dynamic { range });
1436 }
1437 }
1438 }
1439 }
1440 }
1441 note_recorded_primitive_count(self.size, tape_len);
1448 let frame = any_retained.then_some(CommandReplayFrame {
1452 center,
1453 spans,
1454 fallback: None,
1455 });
1456 (
1457 FinishedRecording {
1458 primitives: out,
1459 content_markers: self.content_markers,
1460 recording: self.rec,
1461 dropped,
1462 },
1463 frame,
1464 )
1465 }
1466
1467 fn push_other(&mut self, primitive: DrawPrimitive) {
1468 let idx = self.rec.others.len();
1469 self.rec.tape.push(TapeRef::new(RecordKind::Other, idx));
1470 self.rec.others.push(primitive);
1471 }
1472
1473 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1474 if blend_mode != BlendMode::SrcOver {
1475 self.push_other(DrawPrimitive::Blend {
1476 primitive: Box::new(primitive),
1477 blend_mode,
1478 });
1479 return;
1480 }
1481 match primitive {
1482 DrawPrimitive::Rect {
1483 rect,
1484 brush: Brush::Solid(color),
1485 stroke,
1486 } => {
1487 let idx = self.rec.rects.len();
1488 self.rec.tape.push(TapeRef::new(RecordKind::SolidRect, idx));
1489 self.rec.rects.push(SolidRectRecord {
1490 rect,
1491 color,
1492 stroke,
1493 });
1494 }
1495 DrawPrimitive::RoundRect {
1496 rect,
1497 brush: Brush::Solid(color),
1498 radii,
1499 stroke,
1500 } => {
1501 let idx = self.rec.round_rects.len();
1502 self.rec
1503 .tape
1504 .push(TapeRef::new(RecordKind::SolidRoundRect, idx));
1505 self.rec.round_rects.push(SolidRoundRectRecord {
1506 rect,
1507 radii,
1508 color,
1509 stroke,
1510 });
1511 }
1512 other => self.push_other(other),
1513 }
1514 }
1515
1516 #[allow(clippy::too_many_arguments)]
1523 fn push_arc(
1524 &mut self,
1525 brush: Brush,
1526 center: Point,
1527 radius: f32,
1528 start_angle: f32,
1529 sweep_angle: f32,
1530 stroke: Option<Stroke>,
1531 inner_radius: f32,
1532 blend_mode: BlendMode,
1533 ) {
1534 if blend_mode == BlendMode::SrcOver {
1538 if let Brush::Solid(color) = brush {
1539 let idx = self.rec.arcs.len();
1540 self.rec.tape.push(TapeRef::new(RecordKind::SolidArc, idx));
1541 self.rec.arcs.push(SolidArcRecord {
1542 center,
1543 radius,
1544 start_angle,
1545 sweep_angle,
1546 inner_radius,
1547 color,
1548 stroke,
1549 });
1550 return;
1551 }
1552 }
1553 let (band_inner, band_outer, cap) = arc_band(radius, inner_radius, stroke);
1554 let geometry = ArcGeometry::new(
1555 center,
1556 band_inner,
1557 band_outer,
1558 start_angle,
1559 sweep_angle,
1560 cap,
1561 );
1562 if geometry.is_degenerate() {
1563 return;
1564 }
1565 self.push_blended_primitive(
1566 DrawPrimitive::Arc {
1567 rect: geometry.bounds(),
1568 brush,
1569 center,
1570 radius,
1571 start_angle,
1572 sweep_angle,
1573 stroke,
1574 inner_radius,
1575 },
1576 blend_mode,
1577 );
1578 }
1579}
1580
1581fn materialize_solid_arc(record: &SolidArcRecord) -> Option<DrawPrimitive> {
1586 let (band_inner, band_outer, cap) = arc_band(record.radius, record.inner_radius, record.stroke);
1587 let geometry = ArcGeometry::new(
1588 record.center,
1589 band_inner,
1590 band_outer,
1591 record.start_angle,
1592 record.sweep_angle,
1593 cap,
1594 );
1595 if geometry.is_degenerate() {
1596 return None;
1597 }
1598 Some(DrawPrimitive::Arc {
1599 rect: geometry.bounds(),
1600 brush: Brush::Solid(record.color),
1601 center: record.center,
1602 radius: record.radius,
1603 start_angle: record.start_angle,
1604 sweep_angle: record.sweep_angle,
1605 stroke: record.stroke,
1606 inner_radius: record.inner_radius,
1607 })
1608}
1609
1610impl DrawScope for DrawScopeDefault {
1611 fn size(&self) -> Size {
1612 self.size
1613 }
1614
1615 fn draw_content(&mut self) {
1616 self.content_markers += 1;
1617 self.push_other(DrawPrimitive::Content);
1618 }
1619
1620 fn draw_rect(&mut self, brush: Brush) {
1621 self.draw_rect_blend(brush, BlendMode::SrcOver);
1622 }
1623
1624 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1625 self.push_blended_primitive(
1626 DrawPrimitive::Rect {
1627 rect: Rect::from_size(self.size),
1628 brush,
1629 stroke: None,
1630 },
1631 blend_mode,
1632 );
1633 }
1634
1635 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1636 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1637 }
1638
1639 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1640 self.push_blended_primitive(
1641 DrawPrimitive::Rect {
1642 rect,
1643 brush,
1644 stroke: None,
1645 },
1646 blend_mode,
1647 );
1648 }
1649
1650 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1651 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1652 }
1653
1654 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1655 self.push_blended_primitive(
1656 DrawPrimitive::RoundRect {
1657 rect: Rect::from_size(self.size),
1658 brush,
1659 radii,
1660 stroke: None,
1661 },
1662 blend_mode,
1663 );
1664 }
1665
1666 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1667 self.push_blended_primitive(
1668 DrawPrimitive::RoundRect {
1669 rect,
1670 brush,
1671 radii,
1672 stroke: None,
1673 },
1674 BlendMode::SrcOver,
1675 );
1676 }
1677
1678 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1679 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1680 }
1681
1682 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1683 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1684 }
1685
1686 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1687 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1688 }
1689
1690 fn draw_rect_at_stroked_blend(
1691 &mut self,
1692 rect: Rect,
1693 brush: Brush,
1694 stroke: Stroke,
1695 blend_mode: BlendMode,
1696 ) {
1697 if !stroke.is_visible() {
1698 return;
1699 }
1700 self.push_blended_primitive(
1701 DrawPrimitive::Rect {
1702 rect,
1703 brush,
1704 stroke: Some(stroke),
1705 },
1706 blend_mode,
1707 );
1708 }
1709
1710 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1711 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1712 }
1713
1714 fn draw_round_rect_stroked_blend(
1715 &mut self,
1716 brush: Brush,
1717 radii: CornerRadii,
1718 stroke: Stroke,
1719 blend_mode: BlendMode,
1720 ) {
1721 self.draw_round_rect_at_stroked_blend(
1722 Rect::from_size(self.size),
1723 brush,
1724 radii,
1725 stroke,
1726 blend_mode,
1727 );
1728 }
1729
1730 fn draw_round_rect_at_stroked(
1731 &mut self,
1732 rect: Rect,
1733 brush: Brush,
1734 radii: CornerRadii,
1735 stroke: Stroke,
1736 ) {
1737 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1738 }
1739
1740 fn draw_round_rect_at_stroked_blend(
1741 &mut self,
1742 rect: Rect,
1743 brush: Brush,
1744 radii: CornerRadii,
1745 stroke: Stroke,
1746 blend_mode: BlendMode,
1747 ) {
1748 if !stroke.is_visible() {
1749 return;
1750 }
1751 self.push_blended_primitive(
1752 DrawPrimitive::RoundRect {
1753 rect,
1754 brush,
1755 radii,
1756 stroke: Some(stroke),
1757 },
1758 blend_mode,
1759 );
1760 }
1761
1762 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1763 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1764 }
1765
1766 fn draw_circle_stroked_blend(
1767 &mut self,
1768 brush: Brush,
1769 center: Point,
1770 radius: f32,
1771 stroke: Stroke,
1772 blend_mode: BlendMode,
1773 ) {
1774 if !stroke.is_visible() || !radius.is_finite() {
1775 return;
1776 }
1777 let radius = radius.max(0.0);
1778 let diameter = radius * 2.0;
1779 self.draw_round_rect_at_stroked_blend(
1780 Rect {
1781 x: center.x - radius,
1782 y: center.y - radius,
1783 width: diameter,
1784 height: diameter,
1785 },
1786 brush,
1787 CornerRadii::uniform(radius),
1788 stroke,
1789 blend_mode,
1790 );
1791 }
1792
1793 fn draw_arc(
1794 &mut self,
1795 brush: Brush,
1796 center: Point,
1797 radius: f32,
1798 start_angle: f32,
1799 sweep_angle: f32,
1800 stroke: Stroke,
1801 ) {
1802 self.draw_arc_blend(
1803 brush,
1804 center,
1805 radius,
1806 start_angle,
1807 sweep_angle,
1808 stroke,
1809 BlendMode::SrcOver,
1810 );
1811 }
1812
1813 fn draw_arc_blend(
1814 &mut self,
1815 brush: Brush,
1816 center: Point,
1817 radius: f32,
1818 start_angle: f32,
1819 sweep_angle: f32,
1820 stroke: Stroke,
1821 blend_mode: BlendMode,
1822 ) {
1823 if !stroke.is_visible() {
1824 return;
1825 }
1826 self.push_arc(
1827 brush,
1828 center,
1829 radius,
1830 start_angle,
1831 sweep_angle,
1832 Some(stroke),
1833 0.0,
1834 blend_mode,
1835 );
1836 }
1837
1838 fn draw_annular_sector(
1839 &mut self,
1840 brush: Brush,
1841 center: Point,
1842 inner_radius: f32,
1843 outer_radius: f32,
1844 start_angle: f32,
1845 sweep_angle: f32,
1846 ) {
1847 self.draw_annular_sector_blend(
1848 brush,
1849 center,
1850 inner_radius,
1851 outer_radius,
1852 start_angle,
1853 sweep_angle,
1854 BlendMode::SrcOver,
1855 );
1856 }
1857
1858 fn draw_annular_sector_blend(
1859 &mut self,
1860 brush: Brush,
1861 center: Point,
1862 inner_radius: f32,
1863 outer_radius: f32,
1864 start_angle: f32,
1865 sweep_angle: f32,
1866 blend_mode: BlendMode,
1867 ) {
1868 self.push_arc(
1869 brush,
1870 center,
1871 outer_radius,
1872 start_angle,
1873 sweep_angle,
1874 None,
1875 inner_radius,
1876 blend_mode,
1877 );
1878 }
1879
1880 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1881 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1882 }
1883
1884 fn draw_circle_blend(
1885 &mut self,
1886 brush: Brush,
1887 center: Point,
1888 radius: f32,
1889 blend_mode: BlendMode,
1890 ) {
1891 let radius = radius.max(0.0);
1892 let diameter = radius * 2.0;
1893 self.push_blended_primitive(
1894 DrawPrimitive::RoundRect {
1895 rect: Rect {
1896 x: center.x - radius,
1897 y: center.y - radius,
1898 width: diameter,
1899 height: diameter,
1900 },
1901 brush,
1902 radii: CornerRadii::uniform(radius),
1903 stroke: None,
1904 },
1905 blend_mode,
1906 );
1907 }
1908
1909 fn draw_image(&mut self, image: ImageBitmap) {
1910 self.draw_image_blend(image, BlendMode::SrcOver);
1911 }
1912
1913 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1914 self.push_blended_primitive(
1915 DrawPrimitive::Image {
1916 rect: Rect::from_size(self.size),
1917 image,
1918 alpha: 1.0,
1919 color_filter: None,
1920 sampling: ImageSampling::Nearest,
1921 src_rect: None,
1922 },
1923 blend_mode,
1924 );
1925 }
1926
1927 fn draw_image_at(
1928 &mut self,
1929 rect: Rect,
1930 image: ImageBitmap,
1931 alpha: f32,
1932 color_filter: Option<ColorFilter>,
1933 ) {
1934 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1935 }
1936
1937 fn draw_image_at_sampled(
1938 &mut self,
1939 rect: Rect,
1940 image: ImageBitmap,
1941 alpha: f32,
1942 color_filter: Option<ColorFilter>,
1943 sampling: ImageSampling,
1944 ) {
1945 self.push_blended_primitive(
1946 DrawPrimitive::Image {
1947 rect,
1948 image,
1949 alpha: alpha.clamp(0.0, 1.0),
1950 color_filter,
1951 sampling,
1952 src_rect: None,
1953 },
1954 BlendMode::SrcOver,
1955 );
1956 }
1957
1958 fn draw_image_at_blend(
1959 &mut self,
1960 rect: Rect,
1961 image: ImageBitmap,
1962 alpha: f32,
1963 color_filter: Option<ColorFilter>,
1964 blend_mode: BlendMode,
1965 ) {
1966 self.push_blended_primitive(
1967 DrawPrimitive::Image {
1968 rect,
1969 image,
1970 alpha: alpha.clamp(0.0, 1.0),
1971 color_filter,
1972 sampling: ImageSampling::Nearest,
1973 src_rect: None,
1974 },
1975 blend_mode,
1976 );
1977 }
1978
1979 fn draw_image_src(
1980 &mut self,
1981 image: ImageBitmap,
1982 src_rect: Rect,
1983 dst_rect: Rect,
1984 alpha: f32,
1985 color_filter: Option<ColorFilter>,
1986 ) {
1987 self.draw_image_src_blend(
1988 image,
1989 src_rect,
1990 dst_rect,
1991 alpha,
1992 color_filter,
1993 BlendMode::SrcOver,
1994 );
1995 }
1996
1997 fn draw_image_src_sampled(
1998 &mut self,
1999 image: ImageBitmap,
2000 src_rect: Rect,
2001 dst_rect: Rect,
2002 alpha: f32,
2003 color_filter: Option<ColorFilter>,
2004 sampling: ImageSampling,
2005 ) {
2006 self.push_blended_primitive(
2007 DrawPrimitive::Image {
2008 rect: dst_rect,
2009 image,
2010 alpha: alpha.clamp(0.0, 1.0),
2011 color_filter,
2012 sampling,
2013 src_rect: Some(src_rect),
2014 },
2015 BlendMode::SrcOver,
2016 );
2017 }
2018
2019 fn draw_image_src_blend(
2020 &mut self,
2021 image: ImageBitmap,
2022 src_rect: Rect,
2023 dst_rect: Rect,
2024 alpha: f32,
2025 color_filter: Option<ColorFilter>,
2026 blend_mode: BlendMode,
2027 ) {
2028 self.push_blended_primitive(
2029 DrawPrimitive::Image {
2030 rect: dst_rect,
2031 image,
2032 alpha: alpha.clamp(0.0, 1.0),
2033 color_filter,
2034 sampling: ImageSampling::Nearest,
2035 src_rect: Some(src_rect),
2036 },
2037 blend_mode,
2038 );
2039 }
2040
2041 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
2042 const SUPERSAMPLE: f32 = 2.0;
2047 const MAX_MASK_PIXELS: f32 = 4096.0;
2049
2050 if path.is_empty() {
2051 return;
2052 }
2053 let bounds = path.bounds();
2054 if bounds.width <= 0.0 || bounds.height <= 0.0 {
2055 return;
2056 }
2057
2058 let color = match &brush {
2059 Brush::Solid(color) => *color,
2060 Brush::LinearGradient { colors, .. }
2061 | Brush::RadialGradient { colors, .. }
2062 | Brush::SweepGradient { colors, .. } => match colors.first() {
2063 Some(color) => *color,
2064 None => return,
2065 },
2066 };
2067 if color.3 <= 0.0 {
2068 return;
2069 }
2070
2071 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
2074 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
2075 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
2076 let mask_width = (rect_width * SUPERSAMPLE)
2077 .ceil()
2078 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2079 let mask_height = (rect_height * SUPERSAMPLE)
2080 .ceil()
2081 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2082
2083 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
2084
2085 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2086 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2087 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2088 let alpha = color.3.clamp(0.0, 1.0);
2089
2090 let mut pixels = Vec::with_capacity(mask.len() * 4);
2091 for coverage in mask {
2092 pixels.extend_from_slice(&[red, green, blue, (alpha * coverage as f32 + 0.5) as u8]);
2093 }
2094
2095 let Ok(image) = ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
2096 else {
2097 return;
2098 };
2099
2100 self.push_other(DrawPrimitive::Image {
2101 rect: Rect {
2102 x: origin.x,
2103 y: origin.y,
2104 width: rect_width,
2105 height: rect_height,
2106 },
2107 image,
2108 alpha: 1.0,
2109 color_filter: None,
2110 sampling: ImageSampling::Linear,
2111 src_rect: None,
2112 });
2113 }
2114
2115 fn measure_text(&self, text: &str, style: &TextStyle) -> TextMeasurement {
2116 match &self.text_measurer {
2117 Some(measurer) => measurer.measure_text(text, style),
2118 None => estimate_text_measurement(text, style),
2119 }
2120 }
2121
2122 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &TextStyle) {
2123 if text.is_empty() {
2126 return;
2127 }
2128 let Some(color) = solid_fill_color(&brush) else {
2129 return;
2130 };
2131 if color.3 <= 0.0 {
2132 return;
2133 }
2134 let measurement = self.measure_text(text, style);
2135 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
2136 return;
2137 }
2138 let origin = align_text_block(rect, measurement, style);
2139 if !origin.x.is_finite() || !origin.y.is_finite() {
2140 return;
2141 }
2142 self.push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
2143 rect: Rect::from_origin_size(origin, measurement.size),
2144 text: shared_text_str(text),
2145 style: style.clone(),
2146 color,
2147 })));
2148 }
2149
2150 fn into_primitives(self) -> Vec<DrawPrimitive> {
2151 self.finish().primitives
2152 }
2153}
2154
2155fn solid_fill_color(brush: &Brush) -> Option<Color> {
2160 match brush {
2161 Brush::Solid(color) => Some(*color),
2162 Brush::LinearGradient { colors, .. }
2163 | Brush::RadialGradient { colors, .. }
2164 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
2165 }
2166}
2167
2168#[cfg(test)]
2169mod tests {
2170 use super::*;
2171 use crate::{Color, FontStyle, FontWeight, ImageBitmap, RenderEffect};
2172
2173 #[test]
2178 fn compact_recording_materializes_in_recorded_order() {
2179 let size = Size::new(100.0, 100.0);
2180 let solid = Brush::solid(Color::WHITE);
2181 let gradient = Brush::vertical_gradient(vec![Color::RED, Color::BLUE], 0.0, 100.0);
2182 let center = Point::new(50.0, 50.0);
2183 let stroke = Stroke::new(4.0);
2184 let rect = Rect {
2185 x: 10.0,
2186 y: 20.0,
2187 width: 30.0,
2188 height: 40.0,
2189 };
2190 let batch = vec![
2191 DrawPrimitive::Content,
2192 DrawPrimitive::Rect {
2193 rect,
2194 brush: solid.clone(),
2195 stroke: None,
2196 },
2197 ];
2198
2199 let record = |scope: &mut DrawScopeDefault| {
2201 scope.draw_rect_at(rect, solid.clone());
2202 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 1.5, stroke);
2203 scope.draw_rect_at(rect, gradient.clone());
2204 scope.draw_circle(solid.clone(), center, 12.0);
2205 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 0.0, stroke); scope.draw_rect_at_blend(rect, solid.clone(), BlendMode::Plus);
2207 scope.draw_content();
2208 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2209 scope.push_recorded(batch.clone());
2210 };
2211
2212 let mut compact = DrawScopeDefault::new(size);
2213 record(&mut compact);
2214 let finished = compact.finish();
2215
2216 let arc_via_ordinary = |brush: Brush, radius: f32, start: f32, sweep: f32| {
2220 let mut scope = DrawScopeDefault::new(size);
2221 scope.draw_arc(brush, center, radius, start, sweep, stroke);
2222 scope.into_primitives().remove(0)
2223 };
2224 let expected = vec![
2225 DrawPrimitive::Rect {
2226 rect,
2227 brush: solid.clone(),
2228 stroke: None,
2229 },
2230 arc_via_ordinary(solid.clone(), 30.0, 0.5, 1.5),
2231 DrawPrimitive::Rect {
2232 rect,
2233 brush: gradient.clone(),
2234 stroke: None,
2235 },
2236 DrawPrimitive::RoundRect {
2237 rect: Rect {
2238 x: center.x - 12.0,
2239 y: center.y - 12.0,
2240 width: 24.0,
2241 height: 24.0,
2242 },
2243 brush: solid.clone(),
2244 radii: CornerRadii::uniform(12.0),
2245 stroke: None,
2246 },
2247 DrawPrimitive::Blend {
2248 primitive: Box::new(DrawPrimitive::Rect {
2249 rect,
2250 brush: solid.clone(),
2251 stroke: None,
2252 }),
2253 blend_mode: BlendMode::Plus,
2254 },
2255 DrawPrimitive::Content,
2256 {
2257 let mut scope = DrawScopeDefault::new(size);
2258 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2259 scope.into_primitives().remove(0)
2260 },
2261 DrawPrimitive::Content,
2262 DrawPrimitive::Rect {
2263 rect,
2264 brush: solid.clone(),
2265 stroke: None,
2266 },
2267 ];
2268 assert_eq!(finished.primitives, expected);
2269 assert_eq!(finished.content_markers, 2);
2270 }
2271
2272 #[test]
2275 fn reused_recording_buffers_record_identically_to_fresh() {
2276 let size = Size::new(64.0, 64.0);
2277 let record = |scope: &mut DrawScopeDefault| {
2278 scope.draw_circle(Brush::solid(Color::RED), Point::new(32.0, 32.0), 10.0);
2279 scope.draw_arc(
2280 Brush::solid(Color::BLUE),
2281 Point::new(32.0, 32.0),
2282 20.0,
2283 0.0,
2284 3.0,
2285 Stroke::new(2.0),
2286 );
2287 };
2288
2289 let mut fresh = DrawScopeDefault::new(size);
2290 record(&mut fresh);
2291 let fresh = fresh.finish();
2292
2293 let mut dirty = DrawScopeDefault::new(size);
2295 dirty.draw_rect(Brush::solid(Color::BLACK));
2296 dirty.draw_content();
2297 dirty.draw_arc(
2298 Brush::solid(Color::WHITE),
2299 Point::new(1.0, 1.0),
2300 5.0,
2301 1.0,
2302 1.0,
2303 Stroke::new(1.0),
2304 );
2305 let dirty = dirty.finish();
2306
2307 let mut reused =
2308 DrawScopeDefault::with_recording(size, None, dirty.recording, dirty.primitives);
2309 record(&mut reused);
2310 let reused = reused.finish();
2311
2312 assert_eq!(fresh.primitives, reused.primitives);
2313 assert_eq!(fresh.content_markers, reused.content_markers);
2314 }
2315
2316 #[test]
2317 fn redrawing_the_same_text_shares_one_str_allocation() {
2318 let first = shared_text_str("BREAK THE RING");
2319 let second = shared_text_str("BREAK THE RING");
2320 assert!(Rc::ptr_eq(&first, &second));
2321 assert_eq!(&*second, "BREAK THE RING");
2322 }
2323
2324 #[test]
2325 fn different_text_gets_its_own_str() {
2326 let first = shared_text_str("340");
2327 let second = shared_text_str("350");
2328 assert!(!Rc::ptr_eq(&first, &second));
2329 assert_eq!(&*first, "340");
2330 assert_eq!(&*second, "350");
2331 }
2332
2333 #[test]
2334 fn the_text_pool_survives_overflowing_its_capacity() {
2335 for index in 0..600 {
2336 let text = format!("run-{index}");
2337 assert_eq!(&*shared_text_str(&text), text.as_str());
2338 }
2339 assert_eq!(&*shared_text_str("still correct"), "still correct");
2340 }
2341
2342 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
2343 match primitive {
2344 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
2345 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
2346 other => panic!("expected image primitive, got {other:?}"),
2347 }
2348 }
2349
2350 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
2351 match primitive {
2352 DrawPrimitive::Image { .. } => primitive,
2353 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
2354 other => panic!("expected image primitive, got {other:?}"),
2355 }
2356 }
2357
2358 #[test]
2359 fn draw_svg_path_emits_supersampled_image_over_path_bounds() {
2360 let mut scope = DrawScopeDefault::new(Size::new(32.0, 32.0));
2361 scope.draw_svg_path("M 4 4 H 20 V 20 H 4 Z", Brush::solid(Color::RED));
2362
2363 let primitives = scope.into_primitives();
2364 assert_eq!(primitives.len(), 1);
2365 let DrawPrimitive::Image { rect, image, .. } = &primitives[0] else {
2366 panic!("expected image primitive, got {:?}", primitives[0]);
2367 };
2368
2369 assert_eq!((rect.x, rect.y), (3.0, 3.0));
2371 assert_eq!((rect.width, rect.height), (18.0, 18.0));
2372 assert_eq!((image.width(), image.height()), (36, 36));
2374
2375 let pixels = image.pixels();
2378 let index = (18 * 36 + 18) * 4;
2379 assert_eq!(
2380 &pixels[index..index + 4],
2381 &[255, 0, 0, 255],
2382 "path interior must be opaque brush color"
2383 );
2384 assert_eq!(pixels[3], 0, "outside the path must stay transparent");
2386 }
2387
2388 #[test]
2389 fn draw_svg_path_ignores_invalid_data() {
2390 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2391 scope.draw_svg_path("definitely not a path", Brush::solid(Color::WHITE));
2392 assert!(scope.into_primitives().is_empty());
2393 }
2394
2395 #[test]
2396 fn draw_vector_path_applies_brush_alpha() {
2397 let path = crate::VectorPath::parse("M 0 0 H 8 V 8 H 0 Z").expect("valid path");
2398 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2399 scope.draw_vector_path(&path, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 0.5)));
2400
2401 let primitives = scope.into_primitives();
2402 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
2403 panic!("expected image primitive");
2404 };
2405 let pixels = image.pixels();
2406 let width = image.width() as usize;
2408 let index = ((image.height() as usize / 2) * width + width / 2) * 4;
2409 assert_eq!(&pixels[index..index + 3], &[0, 0, 255]);
2410 let alpha = pixels[index + 3];
2411 assert!(
2412 (alpha as i32 - 128).abs() <= 2,
2413 "interior alpha must honor the brush alpha, got {alpha}"
2414 );
2415 }
2416
2417 #[test]
2418 fn draw_content_inserts_content_marker() {
2419 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
2420 scope.draw_rect(Brush::solid(Color::WHITE));
2421 scope.draw_content();
2422 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
2423
2424 let primitives = scope.into_primitives();
2425 assert!(matches!(primitives[1], DrawPrimitive::Content));
2426 assert!(matches!(
2427 primitives[2],
2428 DrawPrimitive::Blend {
2429 blend_mode: BlendMode::DstOut,
2430 ..
2431 }
2432 ));
2433 }
2434
2435 #[test]
2436 fn draw_rect_blend_wraps_non_default_modes() {
2437 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2438 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
2439
2440 let primitives = scope.into_primitives();
2441 assert_eq!(primitives.len(), 1);
2442 match &primitives[0] {
2443 DrawPrimitive::Blend {
2444 primitive,
2445 blend_mode,
2446 } => {
2447 assert_eq!(*blend_mode, BlendMode::DstOut);
2448 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
2449 }
2450 other => panic!("expected blended primitive, got {other:?}"),
2451 }
2452 }
2453
2454 #[test]
2455 fn draw_circle_records_centered_round_rect() {
2456 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
2457 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
2458
2459 let primitives = scope.into_primitives();
2460 assert_eq!(primitives.len(), 1);
2461 match &primitives[0] {
2462 DrawPrimitive::RoundRect { rect, radii, .. } => {
2463 assert_eq!(
2464 *rect,
2465 Rect {
2466 x: 7.0,
2467 y: 11.0,
2468 width: 10.0,
2469 height: 10.0,
2470 }
2471 );
2472 assert_eq!(*radii, CornerRadii::uniform(5.0));
2473 }
2474 other => panic!("expected circular round rect, got {other:?}"),
2475 }
2476 }
2477
2478 #[test]
2479 fn draw_circle_blend_wraps_non_default_modes() {
2480 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2481 scope.draw_circle_blend(
2482 Brush::solid(Color::RED),
2483 Point::new(5.0, 5.0),
2484 3.0,
2485 BlendMode::Plus,
2486 );
2487
2488 let primitives = scope.into_primitives();
2489 assert_eq!(primitives.len(), 1);
2490 match &primitives[0] {
2491 DrawPrimitive::Blend {
2492 primitive,
2493 blend_mode,
2494 } => {
2495 assert_eq!(*blend_mode, BlendMode::Plus);
2496 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
2497 }
2498 other => panic!("expected blended circle primitive, got {other:?}"),
2499 }
2500 }
2501
2502 #[test]
2503 fn rect_union_encloses_both_inputs() {
2504 let lhs = Rect {
2505 x: 10.0,
2506 y: 5.0,
2507 width: 8.0,
2508 height: 4.0,
2509 };
2510 let rhs = Rect {
2511 x: 4.0,
2512 y: 7.0,
2513 width: 10.0,
2514 height: 6.0,
2515 };
2516
2517 assert_eq!(
2518 lhs.union(rhs),
2519 Rect {
2520 x: 4.0,
2521 y: 5.0,
2522 width: 14.0,
2523 height: 8.0,
2524 }
2525 );
2526 }
2527
2528 #[test]
2529 fn draw_image_uses_scope_size_as_default_rect() {
2530 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
2531 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
2532 scope.draw_image(image.clone());
2533 let primitives = scope.into_primitives();
2534 assert_eq!(primitives.len(), 1);
2535 match unwrap_image(&primitives[0]) {
2536 DrawPrimitive::Image {
2537 rect,
2538 image: actual,
2539 alpha,
2540 color_filter,
2541 sampling,
2542 src_rect,
2543 } => {
2544 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
2545 assert_eq!(*actual, image);
2546 assert_eq!(*alpha, 1.0);
2547 assert!(color_filter.is_none());
2548 assert_eq!(*sampling, ImageSampling::Nearest);
2549 assert!(src_rect.is_none());
2550 }
2551 other => panic!("expected image primitive, got {other:?}"),
2552 }
2553 }
2554
2555 #[test]
2556 fn draw_image_src_stores_src_rect() {
2557 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2558 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2559 let src = Rect {
2560 x: 10.0,
2561 y: 20.0,
2562 width: 30.0,
2563 height: 40.0,
2564 };
2565 let dst = Rect {
2566 x: 0.0,
2567 y: 0.0,
2568 width: 60.0,
2569 height: 80.0,
2570 };
2571 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
2572 let primitives = scope.into_primitives();
2573 assert_eq!(primitives.len(), 1);
2574 match unwrap_image(&primitives[0]) {
2575 DrawPrimitive::Image {
2576 rect,
2577 image: actual,
2578 alpha,
2579 sampling,
2580 src_rect,
2581 ..
2582 } => {
2583 assert_eq!(*rect, dst);
2584 assert_eq!(*actual, image);
2585 assert!((alpha - 0.8).abs() < 1e-5);
2586 assert_eq!(*sampling, ImageSampling::Nearest);
2587 assert_eq!(*src_rect, Some(src));
2588 }
2589 other => panic!("expected image primitive, got {other:?}"),
2590 }
2591 }
2592
2593 #[test]
2594 fn draw_image_at_sampled_records_requested_sampling() {
2595 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2596 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
2597 let dst = Rect {
2598 x: 2.0,
2599 y: 3.0,
2600 width: 40.0,
2601 height: 30.0,
2602 };
2603
2604 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
2605
2606 let primitives = scope.into_primitives();
2607 assert_eq!(primitives.len(), 1);
2608 match unwrap_image(&primitives[0]) {
2609 DrawPrimitive::Image {
2610 rect,
2611 image: actual,
2612 alpha,
2613 sampling,
2614 src_rect,
2615 ..
2616 } => {
2617 assert_eq!(*rect, dst);
2618 assert_eq!(*actual, image);
2619 assert!((alpha - 0.7).abs() < 1e-5);
2620 assert_eq!(*sampling, ImageSampling::Linear);
2621 assert!(src_rect.is_none());
2622 }
2623 other => panic!("expected image primitive, got {other:?}"),
2624 }
2625 }
2626
2627 #[test]
2628 fn draw_image_src_sampled_records_requested_sampling() {
2629 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2630 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2631 let src = Rect {
2632 x: 4.0,
2633 y: 6.0,
2634 width: 16.0,
2635 height: 20.0,
2636 };
2637 let dst = Rect {
2638 x: 8.0,
2639 y: 10.0,
2640 width: 32.0,
2641 height: 40.0,
2642 };
2643
2644 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
2645
2646 let primitives = scope.into_primitives();
2647 assert_eq!(primitives.len(), 1);
2648 match unwrap_image(&primitives[0]) {
2649 DrawPrimitive::Image {
2650 rect,
2651 image: actual,
2652 alpha,
2653 sampling,
2654 src_rect,
2655 ..
2656 } => {
2657 assert_eq!(*rect, dst);
2658 assert_eq!(*actual, image);
2659 assert!((alpha - 0.5).abs() < 1e-5);
2660 assert_eq!(*sampling, ImageSampling::Linear);
2661 assert_eq!(*src_rect, Some(src));
2662 }
2663 other => panic!("expected image primitive, got {other:?}"),
2664 }
2665 }
2666
2667 #[test]
2668 fn draw_image_at_clamps_alpha() {
2669 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2670 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
2671 scope.draw_image_at(
2672 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
2673 image,
2674 3.0,
2675 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
2676 );
2677 assert_image_alpha(&scope.into_primitives()[0], 1.0);
2678 }
2679
2680 #[test]
2681 fn graphics_layer_clone_with_render_effect() {
2682 let layer = GraphicsLayer {
2683 render_effect: Some(RenderEffect::blur(10.0)),
2684 backdrop_effect: Some(RenderEffect::blur(6.0)),
2685 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
2686 alpha: 0.5,
2687 rotation_z: 12.0,
2688 shadow_elevation: 4.0,
2689 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
2690 clip: true,
2691 compositing_strategy: CompositingStrategy::Offscreen,
2692 blend_mode: BlendMode::SrcOver,
2693 ..Default::default()
2694 };
2695 let cloned = layer.clone();
2696 assert_eq!(cloned.alpha, 0.5);
2697 assert!(cloned.render_effect.is_some());
2698 assert!(cloned.backdrop_effect.is_some());
2699 assert_eq!(layer.color_filter, cloned.color_filter);
2700 assert_eq!(layer.render_effect, cloned.render_effect);
2701 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
2702 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
2703 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
2704 assert_eq!(
2705 cloned.shape,
2706 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
2707 );
2708 assert!(cloned.clip);
2709 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
2710 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
2711 }
2712
2713 #[test]
2714 fn graphics_layer_default_has_no_effect() {
2715 let layer = GraphicsLayer::default();
2716 assert!(layer.color_filter.is_none());
2717 assert!(layer.render_effect.is_none());
2718 assert!(layer.backdrop_effect.is_none());
2719 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
2720 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
2721 assert_eq!(layer.alpha, 1.0);
2722 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
2723 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
2724 assert_eq!(layer.shape, LayerShape::Rectangle);
2725 assert!(!layer.clip);
2726 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
2727 assert_eq!(layer.spot_shadow_color, Color::BLACK);
2728 }
2729
2730 #[test]
2731 fn transform_origin_construction() {
2732 let origin = TransformOrigin::new(0.25, 0.75);
2733 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
2734 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
2735 }
2736
2737 #[test]
2738 fn layer_shape_default_is_rectangle() {
2739 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
2740 }
2741
2742 use crate::{StrokeCap, StrokeJoin};
2745 use std::f32::consts::{FRAC_PI_2, PI};
2746
2747 fn approx(a: f32, b: f32) -> bool {
2752 (a - b).abs() < 0.25
2753 }
2754
2755 fn scope(size: f32) -> DrawScopeDefault {
2756 DrawScopeDefault::new(Size::new(size, size))
2757 }
2758
2759 #[test]
2760 fn draw_rect_stroked_records_scope_rect_and_stroke() {
2761 let mut scope = scope(20.0);
2762 scope.draw_rect_stroked(
2763 Brush::solid(Color::RED),
2764 Stroke::new(3.0).with_join(StrokeJoin::Bevel),
2765 );
2766
2767 let primitives = scope.into_primitives();
2768 assert_eq!(primitives.len(), 1);
2769 match &primitives[0] {
2770 DrawPrimitive::Rect {
2771 rect,
2772 stroke: Some(stroke),
2773 ..
2774 } => {
2775 assert_eq!(*rect, Rect::from_size(Size::new(20.0, 20.0)));
2778 assert_eq!(stroke.width, 3.0);
2779 assert_eq!(stroke.join, StrokeJoin::Bevel);
2780 }
2781 other => panic!("expected stroked rect, got {other:?}"),
2782 }
2783 }
2784
2785 #[test]
2786 fn draw_rect_at_stroked_records_requested_rect() {
2787 let mut scope = scope(50.0);
2788 let rect = Rect {
2789 x: 4.0,
2790 y: 6.0,
2791 width: 12.0,
2792 height: 9.0,
2793 };
2794 scope.draw_rect_at_stroked(rect, Brush::solid(Color::BLUE), Stroke::new(2.0));
2795 match &scope.into_primitives()[0] {
2796 DrawPrimitive::Rect {
2797 rect: actual,
2798 stroke: Some(stroke),
2799 ..
2800 } => {
2801 assert_eq!(*actual, rect);
2802 assert_eq!(stroke.width, 2.0);
2803 }
2804 other => panic!("expected stroked rect, got {other:?}"),
2805 }
2806 }
2807
2808 #[test]
2809 fn draw_round_rect_stroked_keeps_radii_and_stroke() {
2810 let mut scope = scope(30.0);
2811 scope.draw_round_rect_stroked(
2812 Brush::solid(Color::GREEN),
2813 CornerRadii::uniform(5.0),
2814 Stroke::new(4.0).with_join(StrokeJoin::Round),
2815 );
2816 match &scope.into_primitives()[0] {
2817 DrawPrimitive::RoundRect {
2818 rect,
2819 radii,
2820 stroke: Some(stroke),
2821 ..
2822 } => {
2823 assert_eq!(*rect, Rect::from_size(Size::new(30.0, 30.0)));
2824 assert_eq!(*radii, CornerRadii::uniform(5.0));
2825 assert_eq!(stroke.width, 4.0);
2826 assert_eq!(stroke.join, StrokeJoin::Round);
2827 }
2828 other => panic!("expected stroked round rect, got {other:?}"),
2829 }
2830 }
2831
2832 #[test]
2833 fn draw_round_rect_at_stroked_records_requested_rect() {
2834 let mut scope = scope(60.0);
2835 let rect = Rect {
2836 x: 1.0,
2837 y: 2.0,
2838 width: 20.0,
2839 height: 10.0,
2840 };
2841 scope.draw_round_rect_at_stroked(
2842 rect,
2843 Brush::solid(Color::WHITE),
2844 CornerRadii::uniform(3.0),
2845 Stroke::new(1.5),
2846 );
2847 match &scope.into_primitives()[0] {
2848 DrawPrimitive::RoundRect {
2849 rect: actual,
2850 radii,
2851 stroke: Some(stroke),
2852 ..
2853 } => {
2854 assert_eq!(*actual, rect);
2855 assert_eq!(*radii, CornerRadii::uniform(3.0));
2856 assert_eq!(stroke.width, 1.5);
2857 }
2858 other => panic!("expected stroked round rect, got {other:?}"),
2859 }
2860 }
2861
2862 #[test]
2863 fn draw_circle_stroked_lowers_to_stroked_round_rect() {
2864 let mut scope = scope(40.0);
2867 scope.draw_circle_stroked(
2868 Brush::solid(Color::BLUE),
2869 Point::new(12.0, 16.0),
2870 5.0,
2871 Stroke::new(2.0),
2872 );
2873 match &scope.into_primitives()[0] {
2874 DrawPrimitive::RoundRect {
2875 rect,
2876 radii,
2877 stroke: Some(stroke),
2878 ..
2879 } => {
2880 assert_eq!(
2881 *rect,
2882 Rect {
2883 x: 7.0,
2884 y: 11.0,
2885 width: 10.0,
2886 height: 10.0,
2887 }
2888 );
2889 assert_eq!(*radii, CornerRadii::uniform(5.0));
2890 assert_eq!(stroke.width, 2.0);
2891 }
2892 other => panic!("expected stroked circular round rect, got {other:?}"),
2893 }
2894 }
2895
2896 #[test]
2897 fn draw_arc_records_arc_primitive_with_tight_bounds() {
2898 let mut scope = scope(200.0);
2899 scope.draw_arc(
2900 Brush::solid(Color::RED),
2901 Point::new(100.0, 100.0),
2902 50.0,
2903 0.0,
2904 FRAC_PI_2,
2905 Stroke::new(10.0),
2906 );
2907 let primitives = scope.into_primitives();
2908 assert_eq!(primitives.len(), 1);
2909 match &primitives[0] {
2910 DrawPrimitive::Arc {
2911 rect,
2912 center,
2913 radius,
2914 start_angle,
2915 sweep_angle,
2916 stroke: Some(stroke),
2917 inner_radius,
2918 ..
2919 } => {
2920 assert_eq!(*center, Point::new(100.0, 100.0));
2921 assert_eq!(*radius, 50.0);
2922 assert_eq!(*start_angle, 0.0);
2923 assert!(approx(*sweep_angle, FRAC_PI_2));
2924 assert_eq!(stroke.width, 10.0);
2925 assert_eq!(*inner_radius, 0.0);
2926 assert!(approx(rect.x, 100.0), "{rect:?}");
2929 assert!(approx(rect.y, 100.0), "{rect:?}");
2930 assert!(approx(rect.width, 55.0), "{rect:?}");
2931 assert!(approx(rect.height, 55.0), "{rect:?}");
2932 }
2933 other => panic!("expected arc primitive, got {other:?}"),
2934 }
2935 }
2936
2937 #[test]
2938 fn draw_arc_bounds_cover_a_quadrant_spanning_sweep() {
2939 let mut scope = scope(200.0);
2940 scope.draw_arc(
2943 Brush::solid(Color::RED),
2944 Point::new(100.0, 100.0),
2945 50.0,
2946 0.0,
2947 3.0 * FRAC_PI_2,
2948 Stroke::new(4.0),
2949 );
2950 let DrawPrimitive::Arc { rect, .. } = &scope.into_primitives()[0] else {
2951 panic!("expected arc primitive");
2952 };
2953 assert!(approx(rect.x, 48.0), "{rect:?}");
2954 assert!(approx(rect.y, 48.0), "{rect:?}");
2955 assert!(approx(rect.width, 104.0), "{rect:?}");
2956 assert!(approx(rect.height, 104.0), "{rect:?}");
2957 }
2958
2959 #[test]
2960 fn draw_annular_sector_records_inner_radius_and_no_stroke() {
2961 let mut scope = scope(200.0);
2962 scope.draw_annular_sector(
2963 Brush::solid(Color::WHITE),
2964 Point::new(100.0, 100.0),
2965 30.0,
2966 50.0,
2967 0.0,
2968 PI,
2969 );
2970 match &scope.into_primitives()[0] {
2971 DrawPrimitive::Arc {
2972 rect,
2973 center,
2974 radius,
2975 inner_radius,
2976 stroke,
2977 sweep_angle,
2978 ..
2979 } => {
2980 assert!(stroke.is_none(), "annular sectors are filled, not stroked");
2981 assert_eq!(*center, Point::new(100.0, 100.0));
2982 assert_eq!(*radius, 50.0);
2983 assert_eq!(*inner_radius, 30.0);
2984 assert!(approx(*sweep_angle, PI));
2985 assert!(approx(rect.x, 50.0), "{rect:?}");
2987 assert!(approx(rect.y, 100.0), "{rect:?}");
2988 assert!(approx(rect.width, 100.0), "{rect:?}");
2989 assert!(approx(rect.height, 50.0), "{rect:?}");
2990 }
2991 other => panic!("expected arc primitive, got {other:?}"),
2992 }
2993 }
2994
2995 #[test]
2996 fn draw_arc_blend_wraps_non_default_modes() {
2997 let mut scope = scope(100.0);
2998 scope.draw_arc_blend(
2999 Brush::solid(Color::RED),
3000 Point::new(50.0, 50.0),
3001 20.0,
3002 0.0,
3003 1.0,
3004 Stroke::new(2.0),
3005 BlendMode::DstOut,
3006 );
3007 match &scope.into_primitives()[0] {
3008 DrawPrimitive::Blend {
3009 primitive,
3010 blend_mode,
3011 } => {
3012 assert_eq!(*blend_mode, BlendMode::DstOut);
3013 assert!(matches!(**primitive, DrawPrimitive::Arc { .. }));
3014 }
3015 other => panic!("expected blended arc, got {other:?}"),
3016 }
3017 }
3018
3019 #[test]
3020 fn draw_annular_sector_blend_wraps_non_default_modes() {
3021 let mut scope = scope(100.0);
3022 scope.draw_annular_sector_blend(
3023 Brush::solid(Color::RED),
3024 Point::new(50.0, 50.0),
3025 5.0,
3026 20.0,
3027 0.0,
3028 1.0,
3029 BlendMode::Plus,
3030 );
3031 assert!(matches!(
3032 &scope.into_primitives()[0],
3033 DrawPrimitive::Blend {
3034 blend_mode: BlendMode::Plus,
3035 ..
3036 }
3037 ));
3038 }
3039
3040 #[test]
3041 fn stroked_blend_variants_wrap_non_default_modes() {
3042 let mut scope = scope(20.0);
3043 scope.draw_rect_stroked_blend(
3044 Brush::solid(Color::RED),
3045 Stroke::new(2.0),
3046 BlendMode::DstOut,
3047 );
3048 scope.draw_round_rect_stroked_blend(
3049 Brush::solid(Color::RED),
3050 CornerRadii::uniform(2.0),
3051 Stroke::new(2.0),
3052 BlendMode::DstOut,
3053 );
3054 scope.draw_circle_stroked_blend(
3055 Brush::solid(Color::RED),
3056 Point::new(10.0, 10.0),
3057 5.0,
3058 Stroke::new(2.0),
3059 BlendMode::DstOut,
3060 );
3061 let primitives = scope.into_primitives();
3062 assert_eq!(primitives.len(), 3);
3063 for primitive in &primitives {
3064 assert!(
3065 matches!(
3066 primitive,
3067 DrawPrimitive::Blend {
3068 blend_mode: BlendMode::DstOut,
3069 ..
3070 }
3071 ),
3072 "expected blended primitive, got {primitive:?}"
3073 );
3074 }
3075 }
3076
3077 #[test]
3078 fn negative_sweeps_and_overlong_sweeps_produce_finite_bounds() {
3079 let mut scope = scope(200.0);
3080 scope.draw_arc(
3081 Brush::solid(Color::RED),
3082 Point::new(100.0, 100.0),
3083 40.0,
3084 FRAC_PI_2,
3085 -FRAC_PI_2,
3086 Stroke::new(4.0),
3087 );
3088 scope.draw_arc(
3089 Brush::solid(Color::RED),
3090 Point::new(100.0, 100.0),
3091 40.0,
3092 0.3,
3093 crate::stroke::TAU * 4.0,
3094 Stroke::new(4.0),
3095 );
3096 let primitives = scope.into_primitives();
3097 assert_eq!(primitives.len(), 2);
3098
3099 let DrawPrimitive::Arc { rect: negative, .. } = &primitives[0] else {
3100 panic!("expected arc");
3101 };
3102 assert!(approx(negative.x, 100.0), "{negative:?}");
3104 assert!(approx(negative.y, 100.0), "{negative:?}");
3105 assert!(approx(negative.width, 42.0), "{negative:?}");
3106
3107 let DrawPrimitive::Arc { rect: full, .. } = &primitives[1] else {
3108 panic!("expected arc");
3109 };
3110 assert!(approx(full.x, 58.0), "{full:?}");
3112 assert!(approx(full.width, 84.0), "{full:?}");
3113 assert!(approx(full.height, 84.0), "{full:?}");
3114 }
3115
3116 #[test]
3117 fn degenerate_stroke_and_arc_inputs_emit_nothing_and_never_panic() {
3118 let mut scope = scope(50.0);
3119 let brush = Brush::solid(Color::RED);
3120 let center = Point::new(25.0, 25.0);
3121
3122 scope.draw_rect_stroked(brush.clone(), Stroke::new(0.0));
3124 scope.draw_rect_stroked(brush.clone(), Stroke::new(-4.0));
3125 scope.draw_rect_stroked(brush.clone(), Stroke::new(f32::NAN));
3126 scope.draw_round_rect_stroked(brush.clone(), CornerRadii::uniform(2.0), Stroke::new(0.0));
3127 scope.draw_circle_stroked(brush.clone(), center, 10.0, Stroke::new(0.0));
3128 scope.draw_circle_stroked(brush.clone(), center, f32::NAN, Stroke::new(2.0));
3129 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 0.0, Stroke::new(2.0));
3131 scope.draw_arc(brush.clone(), center, 10.0, 0.0, f32::NAN, Stroke::new(2.0));
3132 scope.draw_arc(
3133 brush.clone(),
3134 center,
3135 f32::INFINITY,
3136 0.0,
3137 1.0,
3138 Stroke::new(2.0),
3139 );
3140 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 1.0, Stroke::new(0.0));
3142 scope.draw_arc(brush.clone(), center, 0.0, 0.0, 1.0, Stroke::new(0.0));
3143 scope.draw_annular_sector(brush.clone(), center, 10.0, 10.0, 0.0, 1.0);
3145 scope.draw_annular_sector(brush.clone(), center, 20.0, 10.0, 0.0, 1.0);
3146 scope.draw_annular_sector(brush.clone(), center, 0.0, 0.0, 0.0, 1.0);
3147 scope.draw_annular_sector(brush.clone(), center, 0.0, 10.0, 0.0, 0.0);
3148 scope.draw_annular_sector(brush, center, f32::NAN, 10.0, 0.0, 1.0);
3149
3150 assert!(
3151 scope.into_primitives().is_empty(),
3152 "degenerate stroke/arc requests must not reach the renderer"
3153 );
3154 }
3155
3156 #[test]
3157 fn zero_radius_arc_with_positive_width_stays_finite() {
3158 let mut scope = scope(50.0);
3161 scope.draw_arc(
3162 Brush::solid(Color::RED),
3163 Point::new(25.0, 25.0),
3164 0.0,
3165 0.0,
3166 FRAC_PI_2,
3167 Stroke::new(6.0).with_cap(StrokeCap::Round),
3168 );
3169 let primitives = scope.into_primitives();
3170 assert_eq!(primitives.len(), 1);
3171 let DrawPrimitive::Arc { rect, .. } = &primitives[0] else {
3172 panic!("expected arc");
3173 };
3174 for value in [rect.x, rect.y, rect.width, rect.height] {
3175 assert!(value.is_finite(), "{rect:?}");
3176 }
3177 assert!(rect.width > 0.0 && rect.height > 0.0, "{rect:?}");
3178 }
3179
3180 struct FixedAdvanceTextMeasurer {
3185 advance: f32,
3186 line_height: f32,
3187 calls: std::cell::Cell<usize>,
3188 }
3189
3190 impl FixedAdvanceTextMeasurer {
3191 fn shared(advance: f32, line_height: f32) -> Rc<Self> {
3192 Rc::new(Self {
3193 advance,
3194 line_height,
3195 calls: std::cell::Cell::new(0),
3196 })
3197 }
3198 }
3199
3200 impl DrawTextMeasurer for FixedAdvanceTextMeasurer {
3201 fn measure_text(&self, text: &str, _style: &TextStyle) -> TextMeasurement {
3202 self.calls.set(self.calls.get() + 1);
3203 let lines: Vec<&str> = text.split('\n').collect();
3204 let width = lines
3205 .iter()
3206 .map(|line| line.chars().count() as f32 * self.advance)
3207 .fold(0.0_f32, f32::max);
3208 TextMeasurement {
3209 size: Size::new(width, lines.len() as f32 * self.line_height),
3210 line_height: self.line_height,
3211 first_baseline: self.line_height * 0.75,
3212 line_count: lines.len(),
3213 }
3214 }
3215 }
3216
3217 fn text_scope(size: Size) -> (DrawScopeDefault, Rc<FixedAdvanceTextMeasurer>) {
3218 let measurer = FixedAdvanceTextMeasurer::shared(10.0, 20.0);
3219 (
3220 DrawScopeDefault::with_text_measurer(size, measurer.clone()),
3221 measurer,
3222 )
3223 }
3224
3225 fn unwrap_text(primitive: &DrawPrimitive) -> &TextPrimitive {
3226 match primitive {
3227 DrawPrimitive::Text(text) => text,
3228 other => panic!("expected text primitive, got {other:?}"),
3229 }
3230 }
3231
3232 #[test]
3233 fn drawn_text_occupies_exactly_the_box_measure_text_reported() {
3234 let (mut scope, _) = text_scope(Size::new(200.0, 100.0));
3235 let style = TextStyle::new(16.0);
3236 let measured = scope.measure_text("ABCD", &style);
3237
3238 scope.draw_text_from(
3239 Point::new(7.0, 11.0),
3240 Brush::solid(Color::WHITE),
3241 "ABCD",
3242 &style,
3243 );
3244
3245 let primitives = scope.into_primitives();
3246 assert_eq!(primitives.len(), 1);
3247 let text = unwrap_text(&primitives[0]);
3248 assert_eq!(
3249 text.rect,
3250 Rect {
3251 x: 7.0,
3252 y: 11.0,
3253 width: measured.size.width,
3254 height: measured.size.height,
3255 },
3256 "the drawn block must be the measured block, or callers cannot center text"
3257 );
3258 assert_eq!(&*text.text, "ABCD");
3259 assert_eq!(text.color, Color::WHITE);
3260 }
3261
3262 #[test]
3263 fn text_alignment_positions_the_measured_block_inside_the_box() {
3264 let box_rect = Rect {
3265 x: 100.0,
3266 y: 50.0,
3267 width: 200.0,
3268 height: 80.0,
3269 };
3270 let cases = [
3272 (TextAlign::Left, TextVerticalAlign::Top, 100.0, 50.0),
3273 (TextAlign::Center, TextVerticalAlign::Center, 190.0, 80.0),
3274 (TextAlign::Right, TextVerticalAlign::Bottom, 280.0, 110.0),
3275 ];
3276 for (align, vertical_align, expected_x, expected_y) in cases {
3277 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3278 let style = TextStyle::new(16.0)
3279 .with_align(align)
3280 .with_vertical_align(vertical_align);
3281 scope.draw_text_at(box_rect, 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, expected_x) && approx(text.rect.y, expected_y),
3286 "{align:?}/{vertical_align:?} placed the block at {:?}",
3287 text.rect
3288 );
3289 assert!(approx(text.rect.width, 20.0) && approx(text.rect.height, 20.0));
3290 }
3291 }
3292
3293 #[test]
3294 fn baseline_aligned_text_hangs_above_the_box_edge() {
3295 let (mut scope, _) = text_scope(Size::new(200.0, 200.0));
3296 let style = TextStyle::new(16.0).with_vertical_align(TextVerticalAlign::Baseline);
3297 let measured = scope.measure_text("Ag", &style);
3298 scope.draw_text_at(
3299 Rect {
3300 x: 0.0,
3301 y: 100.0,
3302 width: 200.0,
3303 height: 0.0,
3304 },
3305 Brush::solid(Color::WHITE),
3306 "Ag",
3307 &style,
3308 );
3309 let primitives = scope.into_primitives();
3310 let text = unwrap_text(&primitives[0]);
3311 assert!(
3313 approx(text.rect.y, 100.0 - measured.first_baseline),
3314 "{:?}",
3315 text.rect
3316 );
3317 }
3318
3319 #[test]
3320 fn draw_text_fills_the_whole_scope_rect() {
3321 let (mut scope, _) = text_scope(Size::new(120.0, 60.0));
3322 let style = TextStyle::new(16.0)
3323 .with_align(TextAlign::Right)
3324 .with_vertical_align(TextVerticalAlign::Bottom);
3325 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3326 let primitives = scope.into_primitives();
3327 let text = unwrap_text(&primitives[0]);
3328 assert!(
3329 approx(text.rect.x, 100.0) && approx(text.rect.y, 40.0),
3330 "{:?}",
3331 text.rect
3332 );
3333 }
3334
3335 #[test]
3336 fn draw_text_from_ignores_alignment_and_anchors_the_top_left() {
3337 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3338 let style = TextStyle::new(16.0)
3340 .with_align(TextAlign::Center)
3341 .with_vertical_align(TextVerticalAlign::Bottom);
3342 scope.draw_text_from(
3343 Point::new(30.0, 40.0),
3344 Brush::solid(Color::WHITE),
3345 "AB",
3346 &style,
3347 );
3348 let primitives = scope.into_primitives();
3349 let text = unwrap_text(&primitives[0]);
3350 assert!(
3351 approx(text.rect.x, 30.0) && approx(text.rect.y, 40.0),
3352 "{:?}",
3353 text.rect
3354 );
3355 }
3356
3357 #[test]
3358 fn multiline_text_measures_the_widest_line_and_stacks_the_lines() {
3359 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3360 let style = TextStyle::new(16.0);
3361 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "AB\nABCDE", &style);
3362 let primitives = scope.into_primitives();
3363 let text = unwrap_text(&primitives[0]);
3364 assert!(approx(text.rect.width, 50.0), "{:?}", text.rect);
3365 assert!(approx(text.rect.height, 40.0), "{:?}", text.rect);
3366 }
3367
3368 #[test]
3369 fn empty_text_draws_nothing_and_never_measures() {
3370 let (mut scope, measurer) = text_scope(Size::new(100.0, 100.0));
3371 scope.draw_text(Brush::solid(Color::WHITE), "", &TextStyle::new(16.0));
3372 scope.draw_text_at(
3373 Rect::from_size(Size::new(10.0, 10.0)),
3374 Brush::solid(Color::WHITE),
3375 "",
3376 &TextStyle::new(16.0),
3377 );
3378 scope.draw_text_from(
3379 Point::ZERO,
3380 Brush::solid(Color::WHITE),
3381 "",
3382 &TextStyle::new(16.0),
3383 );
3384 assert!(scope.into_primitives().is_empty());
3385 assert_eq!(
3386 measurer.calls.get(),
3387 0,
3388 "an empty string must not cost a measurement"
3389 );
3390 }
3391
3392 #[test]
3393 fn invisible_text_draws_nothing() {
3394 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3395 let style = TextStyle::new(16.0);
3396 scope.draw_text(Brush::solid(Color(1.0, 1.0, 1.0, 0.0)), "AB", &style);
3397 scope.draw_text(
3398 Brush::LinearGradient {
3399 colors: Vec::new(),
3400 stops: None,
3401 start: Point::ZERO,
3402 end: Point::new(1.0, 1.0),
3403 tile_mode: crate::render_effect::TileMode::Clamp,
3404 },
3405 "AB",
3406 &style,
3407 );
3408 assert!(scope.into_primitives().is_empty());
3409 }
3410
3411 #[test]
3412 fn gradient_text_brushes_fall_back_to_their_first_stop() {
3413 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3414 scope.draw_text(
3415 Brush::linear_gradient(vec![Color::RED, Color::BLUE]),
3416 "AB",
3417 &TextStyle::new(16.0),
3418 );
3419 let primitives = scope.into_primitives();
3420 assert_eq!(unwrap_text(&primitives[0]).color, Color::RED);
3421 }
3422
3423 #[test]
3424 fn a_scope_without_a_measurer_falls_back_to_the_font_free_estimate() {
3425 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
3426 let style = TextStyle::new(16.0);
3427 assert_eq!(
3428 scope.measure_text("ABC", &style),
3429 crate::estimate_text_measurement("ABC", &style)
3430 );
3431 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "ABC", &style);
3432 let primitives = scope.into_primitives();
3433 let text = unwrap_text(&primitives[0]);
3434 assert!(text.rect.width > 0.0 && text.rect.height > 0.0);
3435 }
3436
3437 #[test]
3438 fn degenerate_text_geometry_emits_nothing_and_never_panics() {
3439 struct DegenerateTextMeasurer;
3440 impl DrawTextMeasurer for DegenerateTextMeasurer {
3441 fn measure_text(&self, _text: &str, _style: &TextStyle) -> TextMeasurement {
3442 TextMeasurement {
3443 size: Size::new(f32::NAN, 0.0),
3444 line_height: f32::NAN,
3445 first_baseline: f32::NAN,
3446 line_count: 1,
3447 }
3448 }
3449 }
3450
3451 let mut scope = DrawScopeDefault::with_text_measurer(
3452 Size::new(50.0, 50.0),
3453 Rc::new(DegenerateTextMeasurer),
3454 );
3455 scope.draw_text(Brush::solid(Color::WHITE), "AB", &TextStyle::new(16.0));
3456 scope.draw_text_at(
3457 Rect {
3458 x: f32::NAN,
3459 y: 0.0,
3460 width: 10.0,
3461 height: 10.0,
3462 },
3463 Brush::solid(Color::WHITE),
3464 "AB",
3465 &TextStyle::new(16.0),
3466 );
3467 assert!(
3468 scope.into_primitives().is_empty(),
3469 "unmeasurable text must not reach the renderer"
3470 );
3471 }
3472
3473 #[test]
3474 fn text_style_survives_lowering_into_the_primitive() {
3475 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3476 let style = TextStyle::new(21.0)
3477 .with_font_family("Fira Sans")
3478 .with_weight(FontWeight::BOLD)
3479 .with_style(FontStyle::Italic)
3480 .with_letter_spacing(2.0)
3481 .with_line_height(26.0);
3482 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3483 let primitives = scope.into_primitives();
3484 assert_eq!(unwrap_text(&primitives[0]).style, style);
3485 }
3486
3487 #[test]
3488 fn a_layers_composite_alpha_is_a_truncated_byte() {
3489 for byte in 0..=255u32 {
3490 let exact = byte as f32 / 255.0;
3491 assert!(
3492 (GraphicsLayer::composite_alpha_8bit(exact) - exact).abs() < 1e-6,
3493 "byte {byte} moved"
3494 );
3495 if byte < 255 {
3496 let nearly_next = (byte as f32 + 0.999) / 255.0;
3501 assert!(
3502 (GraphicsLayer::composite_alpha_8bit(nearly_next) - exact).abs() < 1e-6,
3503 "byte {byte} + 0.999 did not truncate"
3504 );
3505 }
3506 }
3507 assert_eq!(GraphicsLayer::composite_alpha_8bit(1.0), 1.0);
3508 assert_eq!(GraphicsLayer::composite_alpha_8bit(0.0), 0.0);
3509 assert_eq!(GraphicsLayer::composite_alpha_8bit(-3.0), 0.0);
3510 assert_eq!(GraphicsLayer::composite_alpha_8bit(7.0), 1.0);
3511 }
3512}