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 Default for GraphicsLayer {
310 fn default() -> Self {
311 Self {
312 alpha: 1.0,
313 scale: 1.0,
314 scale_x: 1.0,
315 scale_y: 1.0,
316 rotation_x: 0.0,
317 rotation_y: 0.0,
318 rotation_z: 0.0,
319 camera_distance: 8.0,
320 transform_origin: TransformOrigin::CENTER,
321 translation_x: 0.0,
322 translation_y: 0.0,
323 shadow_elevation: 0.0,
324 ambient_shadow_color: Color::BLACK,
325 spot_shadow_color: Color::BLACK,
326 shape: LayerShape::Rectangle,
327 clip: false,
328 compositing_strategy: CompositingStrategy::Auto,
329 blend_mode: BlendMode::SrcOver,
330 color_filter: None,
331 render_effect: None,
332 backdrop_effect: None,
333 }
334 }
335}
336
337#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
342pub enum BlendMode {
343 Clear,
344 Src,
345 Dst,
346 #[default]
347 SrcOver,
348 DstOver,
349 SrcIn,
350 DstIn,
351 SrcOut,
352 DstOut,
353 SrcAtop,
354 DstAtop,
355 Xor,
356 Plus,
357 Modulate,
358 Screen,
359 Overlay,
360 Darken,
361 Lighten,
362 ColorDodge,
363 ColorBurn,
364 HardLight,
365 SoftLight,
366 Difference,
367 Exclusion,
368 Multiply,
369 Hue,
370 Saturation,
371 Color,
372 Luminosity,
373}
374
375#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
377pub enum CompositingStrategy {
378 #[default]
380 Auto,
381 Offscreen,
383 ModulateAlpha,
385}
386
387#[derive(Clone, Debug, PartialEq)]
388pub enum DrawPrimitive {
389 Content,
392 Blend {
394 primitive: Box<DrawPrimitive>,
395 blend_mode: BlendMode,
396 },
397 Rect {
398 rect: Rect,
399 brush: Brush,
400 stroke: Option<Stroke>,
403 },
404 RoundRect {
405 rect: Rect,
406 brush: Brush,
407 radii: CornerRadii,
408 stroke: Option<Stroke>,
411 },
412 Arc {
422 rect: Rect,
426 brush: Brush,
427 center: Point,
428 radius: f32,
429 start_angle: f32,
430 sweep_angle: f32,
431 stroke: Option<Stroke>,
432 inner_radius: f32,
434 },
435 Image {
436 rect: Rect,
437 image: ImageBitmap,
438 alpha: f32,
439 color_filter: Option<ColorFilter>,
440 sampling: ImageSampling,
441 src_rect: Option<Rect>,
445 },
446 Text(Box<TextPrimitive>),
448 Shadow(ShadowPrimitive),
451}
452
453#[derive(Clone, Debug, PartialEq)]
462pub struct TextPrimitive {
463 pub rect: Rect,
466 pub text: std::rc::Rc<str>,
469 pub style: TextStyle,
470 pub color: Color,
474}
475
476fn shared_text_str(text: &str) -> Rc<str> {
486 use std::cell::RefCell;
487 use std::collections::HashMap;
488 use std::hash::{Hash, Hasher};
489
490 const POOL_CAPACITY: usize = 256;
491 thread_local! {
492 static POOL: RefCell<HashMap<u64, Rc<str>>> = RefCell::new(HashMap::new());
493 }
494
495 let mut hasher = crate::FxHasher::default();
496 text.hash(&mut hasher);
497 let key = hasher.finish();
498
499 POOL.with(|pool| {
500 let mut pool = pool.borrow_mut();
501 if let Some(shared) = pool.get(&key) {
502 if &**shared == text {
503 return Rc::clone(shared);
504 }
505 }
506 let shared: Rc<str> = Rc::from(text);
507 if pool.len() >= POOL_CAPACITY {
508 pool.clear();
509 }
510 pool.insert(key, Rc::clone(&shared));
511 shared
512 })
513}
514
515#[derive(Clone, Debug, PartialEq)]
517pub enum ShadowPrimitive {
518 Drop {
522 shape: Box<DrawPrimitive>,
523 cutout: Option<Box<DrawPrimitive>>,
524 blur_radius: f32,
525 blend_mode: BlendMode,
526 },
527 Inner {
529 fill: Box<DrawPrimitive>,
530 cutout: Box<DrawPrimitive>,
531 blur_radius: f32,
532 blend_mode: BlendMode,
533 clip_rect: Rect,
535 },
536}
537
538pub trait DrawScope {
539 fn size(&self) -> Size;
540 fn draw_content(&mut self);
541 fn draw_rect(&mut self, brush: Brush);
542 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
543 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
545 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
546 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
547 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
548 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii);
550 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32);
551 fn draw_circle_blend(
552 &mut self,
553 brush: Brush,
554 center: Point,
555 radius: f32,
556 blend_mode: BlendMode,
557 );
558
559 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke);
567 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode);
568 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke);
570 fn draw_rect_at_stroked_blend(
571 &mut self,
572 rect: Rect,
573 brush: Brush,
574 stroke: Stroke,
575 blend_mode: BlendMode,
576 );
577 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke);
579 fn draw_round_rect_stroked_blend(
580 &mut self,
581 brush: Brush,
582 radii: CornerRadii,
583 stroke: Stroke,
584 blend_mode: BlendMode,
585 );
586 fn draw_round_rect_at_stroked(
588 &mut self,
589 rect: Rect,
590 brush: Brush,
591 radii: CornerRadii,
592 stroke: Stroke,
593 );
594 #[allow(clippy::too_many_arguments)]
595 fn draw_round_rect_at_stroked_blend(
596 &mut self,
597 rect: Rect,
598 brush: Brush,
599 radii: CornerRadii,
600 stroke: Stroke,
601 blend_mode: BlendMode,
602 );
603 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke);
606 fn draw_circle_stroked_blend(
607 &mut self,
608 brush: Brush,
609 center: Point,
610 radius: f32,
611 stroke: Stroke,
612 blend_mode: BlendMode,
613 );
614
615 #[allow(clippy::too_many_arguments)]
630 fn draw_arc(
631 &mut self,
632 brush: Brush,
633 center: Point,
634 radius: f32,
635 start_angle: f32,
636 sweep_angle: f32,
637 stroke: Stroke,
638 );
639 #[allow(clippy::too_many_arguments)]
640 fn draw_arc_blend(
641 &mut self,
642 brush: Brush,
643 center: Point,
644 radius: f32,
645 start_angle: f32,
646 sweep_angle: f32,
647 stroke: Stroke,
648 blend_mode: BlendMode,
649 );
650
651 #[allow(clippy::too_many_arguments)]
660 fn draw_annular_sector(
661 &mut self,
662 brush: Brush,
663 center: Point,
664 inner_radius: f32,
665 outer_radius: f32,
666 start_angle: f32,
667 sweep_angle: f32,
668 );
669 #[allow(clippy::too_many_arguments)]
670 fn draw_annular_sector_blend(
671 &mut self,
672 brush: Brush,
673 center: Point,
674 inner_radius: f32,
675 outer_radius: f32,
676 start_angle: f32,
677 sweep_angle: f32,
678 blend_mode: BlendMode,
679 );
680
681 fn draw_image(&mut self, image: ImageBitmap);
682 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
683 fn draw_image_at(
684 &mut self,
685 rect: Rect,
686 image: ImageBitmap,
687 alpha: f32,
688 color_filter: Option<ColorFilter>,
689 );
690 fn draw_image_at_sampled(
691 &mut self,
692 rect: Rect,
693 image: ImageBitmap,
694 alpha: f32,
695 color_filter: Option<ColorFilter>,
696 sampling: ImageSampling,
697 );
698 fn draw_image_at_blend(
699 &mut self,
700 rect: Rect,
701 image: ImageBitmap,
702 alpha: f32,
703 color_filter: Option<ColorFilter>,
704 blend_mode: BlendMode,
705 );
706 fn draw_image_src(
709 &mut self,
710 image: ImageBitmap,
711 src_rect: Rect,
712 dst_rect: Rect,
713 alpha: f32,
714 color_filter: Option<ColorFilter>,
715 );
716 fn draw_image_src_sampled(
717 &mut self,
718 image: ImageBitmap,
719 src_rect: Rect,
720 dst_rect: Rect,
721 alpha: f32,
722 color_filter: Option<ColorFilter>,
723 sampling: ImageSampling,
724 );
725 fn draw_image_src_blend(
726 &mut self,
727 image: ImageBitmap,
728 src_rect: Rect,
729 dst_rect: Rect,
730 alpha: f32,
731 color_filter: Option<ColorFilter>,
732 blend_mode: BlendMode,
733 );
734 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush);
743 fn draw_svg_path(&mut self, d: &str, brush: Brush) {
749 if let Ok(path) = crate::VectorPath::parse(d) {
750 self.draw_vector_path(&path, brush);
751 }
752 }
753
754 fn measure_text(&self, text: &str, style: &TextStyle) -> TextMeasurement;
774
775 fn draw_text(&mut self, brush: Brush, text: &str, style: &TextStyle) {
778 self.draw_text_at(Rect::from_size(self.size()), brush, text, style);
779 }
780
781 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &TextStyle);
788
789 fn draw_text_from(&mut self, top_left: Point, brush: Brush, text: &str, style: &TextStyle) {
795 if text.is_empty() {
796 return;
797 }
798 let measurement = self.measure_text(text, style);
799 self.draw_text_at(
800 Rect::from_origin_size(top_left, measurement.size),
801 brush,
802 text,
803 &TextStyle {
804 align: TextAlign::Left,
805 vertical_align: TextVerticalAlign::Top,
806 ..style.clone()
807 },
808 );
809 }
810
811 fn into_primitives(self) -> Vec<DrawPrimitive>;
812}
813
814pub fn align_text_block(rect: Rect, measurement: TextMeasurement, style: &TextStyle) -> Point {
820 let x = match style.align {
821 TextAlign::Left => rect.x,
822 TextAlign::Center => rect.x + (rect.width - measurement.size.width) * 0.5,
823 TextAlign::Right => rect.x + rect.width - measurement.size.width,
824 };
825 let y = match style.vertical_align {
826 TextVerticalAlign::Top => rect.y,
827 TextVerticalAlign::Center => rect.y + (rect.height - measurement.size.height) * 0.5,
828 TextVerticalAlign::Bottom => rect.y + rect.height - measurement.size.height,
829 TextVerticalAlign::Baseline => rect.y - measurement.first_baseline,
830 };
831 Point::new(x, y)
832}
833
834#[derive(Clone, Copy, Debug, PartialEq, Eq)]
837#[repr(u8)]
838pub(crate) enum RecordKind {
839 SolidRect,
840 SolidRoundRect,
841 SolidArc,
842 Other,
843}
844
845#[derive(Clone, Copy, Debug, PartialEq, Eq)]
850pub(crate) struct TapeRef(u32);
851
852impl TapeRef {
853 const KIND_SHIFT: u32 = 30;
854 const INDEX_MASK: u32 = (1 << Self::KIND_SHIFT) - 1;
855
856 pub(crate) fn new(kind: RecordKind, index: usize) -> Self {
857 debug_assert!(index < (1usize << Self::KIND_SHIFT));
858 Self(((kind as u32) << Self::KIND_SHIFT) | index as u32)
859 }
860
861 pub(crate) fn kind(self) -> RecordKind {
862 match self.0 >> Self::KIND_SHIFT {
863 0 => RecordKind::SolidRect,
864 1 => RecordKind::SolidRoundRect,
865 2 => RecordKind::SolidArc,
866 _ => RecordKind::Other,
867 }
868 }
869
870 pub(crate) fn index(self) -> usize {
871 (self.0 & Self::INDEX_MASK) as usize
872 }
873}
874
875#[derive(Clone, Copy, Debug, PartialEq)]
877pub struct SolidRectRecord {
878 pub rect: Rect,
879 pub color: Color,
880 pub stroke: Option<Stroke>,
881}
882
883#[derive(Clone, Copy, Debug, PartialEq)]
886pub struct SolidRoundRectRecord {
887 pub rect: Rect,
888 pub radii: CornerRadii,
889 pub color: Color,
890 pub stroke: Option<Stroke>,
891}
892
893#[derive(Clone, Copy, Debug, PartialEq)]
898pub struct SolidArcRecord {
899 pub center: Point,
900 pub radius: f32,
901 pub start_angle: f32,
902 pub sweep_angle: f32,
903 pub inner_radius: f32,
904 pub color: Color,
905 pub stroke: Option<Stroke>,
906}
907
908#[derive(Clone, Debug, Default)]
916pub struct CommandRecording {
917 pub(crate) tape: Vec<TapeRef>,
922 pub(crate) rects: Vec<SolidRectRecord>,
923 pub(crate) round_rects: Vec<SolidRoundRectRecord>,
924 pub(crate) arcs: Vec<SolidArcRecord>,
925 pub(crate) others: Vec<DrawPrimitive>,
926}
927
928impl CommandRecording {
929 pub fn len(&self) -> usize {
931 self.tape.len()
932 }
933
934 pub fn materialize_range(
939 &self,
940 tape_start: usize,
941 tape_end: usize,
942 ) -> Option<Vec<DrawPrimitive>> {
943 if tape_start > tape_end || tape_end > self.tape.len() {
944 return None;
945 }
946 let mut out = Vec::with_capacity(tape_end - tape_start);
947 for entry in &self.tape[tape_start..tape_end] {
948 match entry.kind() {
949 RecordKind::SolidRect => {
950 let record = self.rects.get(entry.index())?;
951 out.push(DrawPrimitive::Rect {
952 rect: record.rect,
953 brush: Brush::Solid(record.color),
954 stroke: record.stroke,
955 });
956 }
957 RecordKind::SolidRoundRect => {
958 let record = self.round_rects.get(entry.index())?;
959 out.push(DrawPrimitive::RoundRect {
960 rect: record.rect,
961 brush: Brush::Solid(record.color),
962 radii: record.radii,
963 stroke: record.stroke,
964 });
965 }
966 RecordKind::SolidArc => {
967 let record = self.arcs.get(entry.index())?;
968 if let Some(primitive) = materialize_solid_arc(record) {
969 out.push(primitive);
970 }
971 }
972 RecordKind::Other => {
973 out.push(self.others.get(entry.index())?.clone());
974 }
975 }
976 }
977 Some(out)
978 }
979
980 pub fn is_empty(&self) -> bool {
981 self.tape.is_empty()
982 }
983
984 #[doc(hidden)]
988 pub fn tape_ptr(&self) -> *const u8 {
989 self.tape.as_ptr() as *const u8
990 }
991
992 fn clear(&mut self) {
993 self.tape.clear();
994 self.rects.clear();
995 self.round_rects.clear();
996 self.arcs.clear();
997 self.others.clear();
998 }
999}
1000
1001pub struct FinishedRecording {
1005 pub primitives: Vec<DrawPrimitive>,
1006 pub content_markers: u32,
1007 pub recording: CommandRecording,
1008 pub dropped: Vec<u32>,
1013}
1014
1015#[derive(Default)]
1016pub struct DrawScopeDefault {
1017 size: Size,
1018 rec: CommandRecording,
1021 out: Vec<DrawPrimitive>,
1024 content_markers: u32,
1028 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1032}
1033
1034const RECORDED_PRIMITIVE_COUNTS_LIMIT: usize = 64;
1041
1042thread_local! {
1043 static RECORDED_PRIMITIVE_COUNTS: std::cell::RefCell<std::collections::HashMap<(u32, u32), usize>> =
1044 std::cell::RefCell::new(std::collections::HashMap::new());
1045}
1046
1047fn recorded_primitive_capacity(size: Size) -> usize {
1048 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1049 counts
1050 .borrow()
1051 .get(&(size.width.to_bits(), size.height.to_bits()))
1052 .copied()
1053 .unwrap_or(0)
1054 })
1055}
1056
1057fn note_recorded_primitive_count(size: Size, count: usize) {
1058 RECORDED_PRIMITIVE_COUNTS.with(|counts| {
1059 let mut counts = counts.borrow_mut();
1060 if counts.len() >= RECORDED_PRIMITIVE_COUNTS_LIMIT {
1061 counts.clear();
1062 }
1063 counts.insert((size.width.to_bits(), size.height.to_bits()), count);
1064 });
1065}
1066
1067impl DrawScopeDefault {
1068 pub fn new(size: Size) -> Self {
1069 Self::with_recording(size, None, CommandRecording::default(), Vec::new())
1070 }
1071
1072 pub fn with_text_measurer(size: Size, text_measurer: Rc<dyn DrawTextMeasurer>) -> Self {
1077 Self::with_recording(
1078 size,
1079 Some(text_measurer),
1080 CommandRecording::default(),
1081 Vec::new(),
1082 )
1083 }
1084
1085 pub fn with_text_measurer_reusing(
1091 size: Size,
1092 text_measurer: Rc<dyn DrawTextMeasurer>,
1093 storage: Vec<DrawPrimitive>,
1094 ) -> Self {
1095 Self::with_recording(
1096 size,
1097 Some(text_measurer),
1098 CommandRecording::default(),
1099 storage,
1100 )
1101 }
1102
1103 pub fn with_recording(
1107 size: Size,
1108 text_measurer: Option<Rc<dyn DrawTextMeasurer>>,
1109 mut recording: CommandRecording,
1110 out: Vec<DrawPrimitive>,
1111 ) -> Self {
1112 recording.clear();
1113 recording.tape.reserve(recorded_primitive_capacity(size));
1114 Self {
1115 size,
1116 rec: recording,
1117 out,
1118 content_markers: 0,
1119 text_measurer,
1120 }
1121 }
1122
1123 pub fn content_marker_count(&self) -> u32 {
1127 self.content_markers
1128 }
1129
1130 pub fn recorded(&self) -> &CommandRecording {
1133 &self.rec
1134 }
1135
1136 pub fn push_recorded(&mut self, primitives: Vec<DrawPrimitive>) {
1141 self.content_markers += primitives
1142 .iter()
1143 .filter(|primitive| matches!(primitive, DrawPrimitive::Content))
1144 .count() as u32;
1145 let base = self.rec.others.len();
1146 self.rec
1147 .tape
1148 .extend((0..primitives.len()).map(|i| TapeRef::new(RecordKind::Other, base + i)));
1149 self.rec.others.extend(primitives);
1150 }
1151
1152 pub fn finish(mut self) -> FinishedRecording {
1158 if std::env::var_os("CRANPOSE_RECORD_MIX_DIAG").is_some() && self.rec.tape.len() > 400 {
1161 eprintln!(
1162 "[record-mix] tape={} rects={} round_rects={} arcs={} others={}",
1163 self.rec.tape.len(),
1164 self.rec.rects.len(),
1165 self.rec.round_rects.len(),
1166 self.rec.arcs.len(),
1167 self.rec.others.len(),
1168 );
1169 }
1170 let mut out = std::mem::take(&mut self.out);
1171 out.clear();
1172 out.reserve(self.rec.tape.len());
1173 let mut dropped: Vec<u32> = Vec::new();
1174 {
1175 let mut others = self.rec.others.drain(..);
1179 for (tape_index, entry) in self.rec.tape.iter().enumerate() {
1180 match entry.kind() {
1181 RecordKind::SolidRect => {
1182 let record = &self.rec.rects[entry.index()];
1183 out.push(DrawPrimitive::Rect {
1184 rect: record.rect,
1185 brush: Brush::Solid(record.color),
1186 stroke: record.stroke,
1187 });
1188 }
1189 RecordKind::SolidRoundRect => {
1190 let record = &self.rec.round_rects[entry.index()];
1191 out.push(DrawPrimitive::RoundRect {
1192 rect: record.rect,
1193 brush: Brush::Solid(record.color),
1194 radii: record.radii,
1195 stroke: record.stroke,
1196 });
1197 }
1198 RecordKind::SolidArc => {
1199 let record = &self.rec.arcs[entry.index()];
1200 if let Some(primitive) = materialize_solid_arc(record) {
1201 out.push(primitive);
1202 } else {
1203 dropped.push(tape_index as u32);
1204 }
1205 }
1206 RecordKind::Other => {
1207 out.push(others.next().expect("tape/others in sync"));
1208 }
1209 }
1210 }
1211 }
1212 self.rec.clear();
1213 note_recorded_primitive_count(self.size, out.len());
1214 FinishedRecording {
1215 primitives: out,
1216 content_markers: self.content_markers,
1217 recording: self.rec,
1218 dropped,
1219 }
1220 }
1221
1222 pub fn finish_replay(
1230 mut self,
1231 center: Point,
1232 outcome: crate::record_replay::ReplayOutcome,
1233 bypass: &mut dyn FnMut(u32) -> bool,
1234 ) -> (
1235 FinishedRecording,
1236 Option<crate::record_replay::CommandReplayFrame>,
1237 ) {
1238 use crate::record_replay::{CommandReplayFrame, FrameSpan, ReplayOutcome, ReplaySpan};
1239 let ReplayOutcome::Spans(replay_spans) = outcome else {
1240 return (self.finish(), None);
1241 };
1242 let tape_len = self.rec.tape.len();
1243 let mut out = std::mem::take(&mut self.out);
1244 out.clear();
1245 let mut dropped: Vec<u32> = Vec::new();
1246 let mut spans: Vec<FrameSpan> = Vec::with_capacity(replay_spans.len());
1247 let mut any_retained = false;
1248 {
1249 let mut others = self.rec.others.drain(..);
1254 let tape = &self.rec.tape;
1255 let rects = &self.rec.rects;
1256 let round_rects = &self.rec.round_rects;
1257 let arcs = &self.rec.arcs;
1258 macro_rules! materialize_range {
1262 ($start:expr, $end:expr) => {{
1263 let prim_start = out.len() as u32;
1264 for tape_index in $start..$end {
1265 let entry = tape[tape_index];
1266 match entry.kind() {
1267 RecordKind::SolidRect => {
1268 let record = &rects[entry.index()];
1269 out.push(DrawPrimitive::Rect {
1270 rect: record.rect,
1271 brush: Brush::Solid(record.color),
1272 stroke: record.stroke,
1273 });
1274 }
1275 RecordKind::SolidRoundRect => {
1276 let record = &round_rects[entry.index()];
1277 out.push(DrawPrimitive::RoundRect {
1278 rect: record.rect,
1279 brush: Brush::Solid(record.color),
1280 radii: record.radii,
1281 stroke: record.stroke,
1282 });
1283 }
1284 RecordKind::SolidArc => {
1285 let record = &arcs[entry.index()];
1286 if let Some(primitive) = materialize_solid_arc(record) {
1287 out.push(primitive);
1288 } else {
1289 dropped.push(tape_index as u32);
1290 }
1291 }
1292 RecordKind::Other => {
1293 out.push(others.next().expect("tape/others in sync"));
1294 }
1295 }
1296 }
1297 (prim_start, out.len() as u32)
1298 }};
1299 }
1300 for span in replay_spans {
1301 match span {
1302 ReplaySpan::Dynamic {
1303 tape_start,
1304 tape_end,
1305 } => {
1306 let range = materialize_range!(tape_start, tape_end);
1307 if range.1 > range.0 {
1308 spans.push(FrameSpan::Dynamic { range });
1309 }
1310 }
1311 ReplaySpan::Retained {
1312 slot,
1313 capture,
1314 slot_offset,
1315 tape_start,
1316 tape_end,
1317 transform,
1318 recolors,
1319 bounds,
1320 } => {
1321 let compact = tape[tape_start..tape_end]
1325 .iter()
1326 .all(|entry| entry.kind() != RecordKind::Other);
1327 if compact && !capture && bypass(slot) {
1328 any_retained = true;
1334 let position = out.len() as u32;
1335 spans.push(FrameSpan::Retained {
1336 slot,
1337 capture: false,
1338 slot_offset: slot_offset as u32,
1339 range: (position, position),
1340 tape_range: (tape_start as u32, tape_end as u32),
1341 transform,
1342 recolors,
1343 bounds,
1344 });
1345 continue;
1346 }
1347 let drops_before = dropped.len();
1348 let range = materialize_range!(tape_start, tape_end);
1349 if compact && dropped.len() == drops_before {
1350 any_retained = true;
1351 spans.push(FrameSpan::Retained {
1352 slot,
1353 capture,
1354 slot_offset: slot_offset as u32,
1355 range,
1356 tape_range: (tape_start as u32, tape_end as u32),
1357 transform,
1358 recolors,
1359 bounds,
1360 });
1361 } else if range.1 > range.0 {
1362 spans.push(FrameSpan::Dynamic { range });
1363 }
1364 }
1365 }
1366 }
1367 }
1368 note_recorded_primitive_count(self.size, tape_len);
1375 let frame = any_retained.then_some(CommandReplayFrame {
1379 center,
1380 spans,
1381 fallback: None,
1382 });
1383 (
1384 FinishedRecording {
1385 primitives: out,
1386 content_markers: self.content_markers,
1387 recording: self.rec,
1388 dropped,
1389 },
1390 frame,
1391 )
1392 }
1393
1394 fn push_other(&mut self, primitive: DrawPrimitive) {
1395 let idx = self.rec.others.len();
1396 self.rec.tape.push(TapeRef::new(RecordKind::Other, idx));
1397 self.rec.others.push(primitive);
1398 }
1399
1400 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
1401 if blend_mode != BlendMode::SrcOver {
1402 self.push_other(DrawPrimitive::Blend {
1403 primitive: Box::new(primitive),
1404 blend_mode,
1405 });
1406 return;
1407 }
1408 match primitive {
1409 DrawPrimitive::Rect {
1410 rect,
1411 brush: Brush::Solid(color),
1412 stroke,
1413 } => {
1414 let idx = self.rec.rects.len();
1415 self.rec.tape.push(TapeRef::new(RecordKind::SolidRect, idx));
1416 self.rec.rects.push(SolidRectRecord {
1417 rect,
1418 color,
1419 stroke,
1420 });
1421 }
1422 DrawPrimitive::RoundRect {
1423 rect,
1424 brush: Brush::Solid(color),
1425 radii,
1426 stroke,
1427 } => {
1428 let idx = self.rec.round_rects.len();
1429 self.rec
1430 .tape
1431 .push(TapeRef::new(RecordKind::SolidRoundRect, idx));
1432 self.rec.round_rects.push(SolidRoundRectRecord {
1433 rect,
1434 radii,
1435 color,
1436 stroke,
1437 });
1438 }
1439 other => self.push_other(other),
1440 }
1441 }
1442
1443 #[allow(clippy::too_many_arguments)]
1450 fn push_arc(
1451 &mut self,
1452 brush: Brush,
1453 center: Point,
1454 radius: f32,
1455 start_angle: f32,
1456 sweep_angle: f32,
1457 stroke: Option<Stroke>,
1458 inner_radius: f32,
1459 blend_mode: BlendMode,
1460 ) {
1461 if blend_mode == BlendMode::SrcOver {
1465 if let Brush::Solid(color) = brush {
1466 let idx = self.rec.arcs.len();
1467 self.rec.tape.push(TapeRef::new(RecordKind::SolidArc, idx));
1468 self.rec.arcs.push(SolidArcRecord {
1469 center,
1470 radius,
1471 start_angle,
1472 sweep_angle,
1473 inner_radius,
1474 color,
1475 stroke,
1476 });
1477 return;
1478 }
1479 }
1480 let (band_inner, band_outer, cap) = arc_band(radius, inner_radius, stroke);
1481 let geometry = ArcGeometry::new(
1482 center,
1483 band_inner,
1484 band_outer,
1485 start_angle,
1486 sweep_angle,
1487 cap,
1488 );
1489 if geometry.is_degenerate() {
1490 return;
1491 }
1492 self.push_blended_primitive(
1493 DrawPrimitive::Arc {
1494 rect: geometry.bounds(),
1495 brush,
1496 center,
1497 radius,
1498 start_angle,
1499 sweep_angle,
1500 stroke,
1501 inner_radius,
1502 },
1503 blend_mode,
1504 );
1505 }
1506}
1507
1508fn materialize_solid_arc(record: &SolidArcRecord) -> Option<DrawPrimitive> {
1513 let (band_inner, band_outer, cap) = arc_band(record.radius, record.inner_radius, record.stroke);
1514 let geometry = ArcGeometry::new(
1515 record.center,
1516 band_inner,
1517 band_outer,
1518 record.start_angle,
1519 record.sweep_angle,
1520 cap,
1521 );
1522 if geometry.is_degenerate() {
1523 return None;
1524 }
1525 Some(DrawPrimitive::Arc {
1526 rect: geometry.bounds(),
1527 brush: Brush::Solid(record.color),
1528 center: record.center,
1529 radius: record.radius,
1530 start_angle: record.start_angle,
1531 sweep_angle: record.sweep_angle,
1532 stroke: record.stroke,
1533 inner_radius: record.inner_radius,
1534 })
1535}
1536
1537impl DrawScope for DrawScopeDefault {
1538 fn size(&self) -> Size {
1539 self.size
1540 }
1541
1542 fn draw_content(&mut self) {
1543 self.content_markers += 1;
1544 self.push_other(DrawPrimitive::Content);
1545 }
1546
1547 fn draw_rect(&mut self, brush: Brush) {
1548 self.draw_rect_blend(brush, BlendMode::SrcOver);
1549 }
1550
1551 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
1552 self.push_blended_primitive(
1553 DrawPrimitive::Rect {
1554 rect: Rect::from_size(self.size),
1555 brush,
1556 stroke: None,
1557 },
1558 blend_mode,
1559 );
1560 }
1561
1562 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
1563 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
1564 }
1565
1566 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
1567 self.push_blended_primitive(
1568 DrawPrimitive::Rect {
1569 rect,
1570 brush,
1571 stroke: None,
1572 },
1573 blend_mode,
1574 );
1575 }
1576
1577 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
1578 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
1579 }
1580
1581 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
1582 self.push_blended_primitive(
1583 DrawPrimitive::RoundRect {
1584 rect: Rect::from_size(self.size),
1585 brush,
1586 radii,
1587 stroke: None,
1588 },
1589 blend_mode,
1590 );
1591 }
1592
1593 fn draw_round_rect_at(&mut self, rect: Rect, brush: Brush, radii: CornerRadii) {
1594 self.push_blended_primitive(
1595 DrawPrimitive::RoundRect {
1596 rect,
1597 brush,
1598 radii,
1599 stroke: None,
1600 },
1601 BlendMode::SrcOver,
1602 );
1603 }
1604
1605 fn draw_rect_stroked(&mut self, brush: Brush, stroke: Stroke) {
1606 self.draw_rect_stroked_blend(brush, stroke, BlendMode::SrcOver);
1607 }
1608
1609 fn draw_rect_stroked_blend(&mut self, brush: Brush, stroke: Stroke, blend_mode: BlendMode) {
1610 self.draw_rect_at_stroked_blend(Rect::from_size(self.size), brush, stroke, blend_mode);
1611 }
1612
1613 fn draw_rect_at_stroked(&mut self, rect: Rect, brush: Brush, stroke: Stroke) {
1614 self.draw_rect_at_stroked_blend(rect, brush, stroke, BlendMode::SrcOver);
1615 }
1616
1617 fn draw_rect_at_stroked_blend(
1618 &mut self,
1619 rect: Rect,
1620 brush: Brush,
1621 stroke: Stroke,
1622 blend_mode: BlendMode,
1623 ) {
1624 if !stroke.is_visible() {
1625 return;
1626 }
1627 self.push_blended_primitive(
1628 DrawPrimitive::Rect {
1629 rect,
1630 brush,
1631 stroke: Some(stroke),
1632 },
1633 blend_mode,
1634 );
1635 }
1636
1637 fn draw_round_rect_stroked(&mut self, brush: Brush, radii: CornerRadii, stroke: Stroke) {
1638 self.draw_round_rect_stroked_blend(brush, radii, stroke, BlendMode::SrcOver);
1639 }
1640
1641 fn draw_round_rect_stroked_blend(
1642 &mut self,
1643 brush: Brush,
1644 radii: CornerRadii,
1645 stroke: Stroke,
1646 blend_mode: BlendMode,
1647 ) {
1648 self.draw_round_rect_at_stroked_blend(
1649 Rect::from_size(self.size),
1650 brush,
1651 radii,
1652 stroke,
1653 blend_mode,
1654 );
1655 }
1656
1657 fn draw_round_rect_at_stroked(
1658 &mut self,
1659 rect: Rect,
1660 brush: Brush,
1661 radii: CornerRadii,
1662 stroke: Stroke,
1663 ) {
1664 self.draw_round_rect_at_stroked_blend(rect, brush, radii, stroke, BlendMode::SrcOver);
1665 }
1666
1667 fn draw_round_rect_at_stroked_blend(
1668 &mut self,
1669 rect: Rect,
1670 brush: Brush,
1671 radii: CornerRadii,
1672 stroke: Stroke,
1673 blend_mode: BlendMode,
1674 ) {
1675 if !stroke.is_visible() {
1676 return;
1677 }
1678 self.push_blended_primitive(
1679 DrawPrimitive::RoundRect {
1680 rect,
1681 brush,
1682 radii,
1683 stroke: Some(stroke),
1684 },
1685 blend_mode,
1686 );
1687 }
1688
1689 fn draw_circle_stroked(&mut self, brush: Brush, center: Point, radius: f32, stroke: Stroke) {
1690 self.draw_circle_stroked_blend(brush, center, radius, stroke, BlendMode::SrcOver);
1691 }
1692
1693 fn draw_circle_stroked_blend(
1694 &mut self,
1695 brush: Brush,
1696 center: Point,
1697 radius: f32,
1698 stroke: Stroke,
1699 blend_mode: BlendMode,
1700 ) {
1701 if !stroke.is_visible() || !radius.is_finite() {
1702 return;
1703 }
1704 let radius = radius.max(0.0);
1705 let diameter = radius * 2.0;
1706 self.draw_round_rect_at_stroked_blend(
1707 Rect {
1708 x: center.x - radius,
1709 y: center.y - radius,
1710 width: diameter,
1711 height: diameter,
1712 },
1713 brush,
1714 CornerRadii::uniform(radius),
1715 stroke,
1716 blend_mode,
1717 );
1718 }
1719
1720 fn draw_arc(
1721 &mut self,
1722 brush: Brush,
1723 center: Point,
1724 radius: f32,
1725 start_angle: f32,
1726 sweep_angle: f32,
1727 stroke: Stroke,
1728 ) {
1729 self.draw_arc_blend(
1730 brush,
1731 center,
1732 radius,
1733 start_angle,
1734 sweep_angle,
1735 stroke,
1736 BlendMode::SrcOver,
1737 );
1738 }
1739
1740 fn draw_arc_blend(
1741 &mut self,
1742 brush: Brush,
1743 center: Point,
1744 radius: f32,
1745 start_angle: f32,
1746 sweep_angle: f32,
1747 stroke: Stroke,
1748 blend_mode: BlendMode,
1749 ) {
1750 if !stroke.is_visible() {
1751 return;
1752 }
1753 self.push_arc(
1754 brush,
1755 center,
1756 radius,
1757 start_angle,
1758 sweep_angle,
1759 Some(stroke),
1760 0.0,
1761 blend_mode,
1762 );
1763 }
1764
1765 fn draw_annular_sector(
1766 &mut self,
1767 brush: Brush,
1768 center: Point,
1769 inner_radius: f32,
1770 outer_radius: f32,
1771 start_angle: f32,
1772 sweep_angle: f32,
1773 ) {
1774 self.draw_annular_sector_blend(
1775 brush,
1776 center,
1777 inner_radius,
1778 outer_radius,
1779 start_angle,
1780 sweep_angle,
1781 BlendMode::SrcOver,
1782 );
1783 }
1784
1785 fn draw_annular_sector_blend(
1786 &mut self,
1787 brush: Brush,
1788 center: Point,
1789 inner_radius: f32,
1790 outer_radius: f32,
1791 start_angle: f32,
1792 sweep_angle: f32,
1793 blend_mode: BlendMode,
1794 ) {
1795 self.push_arc(
1796 brush,
1797 center,
1798 outer_radius,
1799 start_angle,
1800 sweep_angle,
1801 None,
1802 inner_radius,
1803 blend_mode,
1804 );
1805 }
1806
1807 fn draw_circle(&mut self, brush: Brush, center: Point, radius: f32) {
1808 self.draw_circle_blend(brush, center, radius, BlendMode::SrcOver);
1809 }
1810
1811 fn draw_circle_blend(
1812 &mut self,
1813 brush: Brush,
1814 center: Point,
1815 radius: f32,
1816 blend_mode: BlendMode,
1817 ) {
1818 let radius = radius.max(0.0);
1819 let diameter = radius * 2.0;
1820 self.push_blended_primitive(
1821 DrawPrimitive::RoundRect {
1822 rect: Rect {
1823 x: center.x - radius,
1824 y: center.y - radius,
1825 width: diameter,
1826 height: diameter,
1827 },
1828 brush,
1829 radii: CornerRadii::uniform(radius),
1830 stroke: None,
1831 },
1832 blend_mode,
1833 );
1834 }
1835
1836 fn draw_image(&mut self, image: ImageBitmap) {
1837 self.draw_image_blend(image, BlendMode::SrcOver);
1838 }
1839
1840 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
1841 self.push_blended_primitive(
1842 DrawPrimitive::Image {
1843 rect: Rect::from_size(self.size),
1844 image,
1845 alpha: 1.0,
1846 color_filter: None,
1847 sampling: ImageSampling::Nearest,
1848 src_rect: None,
1849 },
1850 blend_mode,
1851 );
1852 }
1853
1854 fn draw_image_at(
1855 &mut self,
1856 rect: Rect,
1857 image: ImageBitmap,
1858 alpha: f32,
1859 color_filter: Option<ColorFilter>,
1860 ) {
1861 self.draw_image_at_sampled(rect, image, alpha, color_filter, ImageSampling::Nearest);
1862 }
1863
1864 fn draw_image_at_sampled(
1865 &mut self,
1866 rect: Rect,
1867 image: ImageBitmap,
1868 alpha: f32,
1869 color_filter: Option<ColorFilter>,
1870 sampling: ImageSampling,
1871 ) {
1872 self.push_blended_primitive(
1873 DrawPrimitive::Image {
1874 rect,
1875 image,
1876 alpha: alpha.clamp(0.0, 1.0),
1877 color_filter,
1878 sampling,
1879 src_rect: None,
1880 },
1881 BlendMode::SrcOver,
1882 );
1883 }
1884
1885 fn draw_image_at_blend(
1886 &mut self,
1887 rect: Rect,
1888 image: ImageBitmap,
1889 alpha: f32,
1890 color_filter: Option<ColorFilter>,
1891 blend_mode: BlendMode,
1892 ) {
1893 self.push_blended_primitive(
1894 DrawPrimitive::Image {
1895 rect,
1896 image,
1897 alpha: alpha.clamp(0.0, 1.0),
1898 color_filter,
1899 sampling: ImageSampling::Nearest,
1900 src_rect: None,
1901 },
1902 blend_mode,
1903 );
1904 }
1905
1906 fn draw_image_src(
1907 &mut self,
1908 image: ImageBitmap,
1909 src_rect: Rect,
1910 dst_rect: Rect,
1911 alpha: f32,
1912 color_filter: Option<ColorFilter>,
1913 ) {
1914 self.draw_image_src_blend(
1915 image,
1916 src_rect,
1917 dst_rect,
1918 alpha,
1919 color_filter,
1920 BlendMode::SrcOver,
1921 );
1922 }
1923
1924 fn draw_image_src_sampled(
1925 &mut self,
1926 image: ImageBitmap,
1927 src_rect: Rect,
1928 dst_rect: Rect,
1929 alpha: f32,
1930 color_filter: Option<ColorFilter>,
1931 sampling: ImageSampling,
1932 ) {
1933 self.push_blended_primitive(
1934 DrawPrimitive::Image {
1935 rect: dst_rect,
1936 image,
1937 alpha: alpha.clamp(0.0, 1.0),
1938 color_filter,
1939 sampling,
1940 src_rect: Some(src_rect),
1941 },
1942 BlendMode::SrcOver,
1943 );
1944 }
1945
1946 fn draw_image_src_blend(
1947 &mut self,
1948 image: ImageBitmap,
1949 src_rect: Rect,
1950 dst_rect: Rect,
1951 alpha: f32,
1952 color_filter: Option<ColorFilter>,
1953 blend_mode: BlendMode,
1954 ) {
1955 self.push_blended_primitive(
1956 DrawPrimitive::Image {
1957 rect: dst_rect,
1958 image,
1959 alpha: alpha.clamp(0.0, 1.0),
1960 color_filter,
1961 sampling: ImageSampling::Nearest,
1962 src_rect: Some(src_rect),
1963 },
1964 blend_mode,
1965 );
1966 }
1967
1968 fn draw_vector_path(&mut self, path: &crate::VectorPath, brush: Brush) {
1969 const SUPERSAMPLE: f32 = 2.0;
1974 const MAX_MASK_PIXELS: f32 = 4096.0;
1976
1977 if path.is_empty() {
1978 return;
1979 }
1980 let bounds = path.bounds();
1981 if bounds.width <= 0.0 || bounds.height <= 0.0 {
1982 return;
1983 }
1984
1985 let color = match &brush {
1986 Brush::Solid(color) => *color,
1987 Brush::LinearGradient { colors, .. }
1988 | Brush::RadialGradient { colors, .. }
1989 | Brush::SweepGradient { colors, .. } => match colors.first() {
1990 Some(color) => *color,
1991 None => return,
1992 },
1993 };
1994 if color.3 <= 0.0 {
1995 return;
1996 }
1997
1998 let origin = Point::new(bounds.x.floor() - 1.0, bounds.y.floor() - 1.0);
2001 let rect_width = (bounds.x + bounds.width).ceil() - origin.x + 1.0;
2002 let rect_height = (bounds.y + bounds.height).ceil() - origin.y + 1.0;
2003 let mask_width = (rect_width * SUPERSAMPLE)
2004 .ceil()
2005 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2006 let mask_height = (rect_height * SUPERSAMPLE)
2007 .ceil()
2008 .clamp(1.0, MAX_MASK_PIXELS) as usize;
2009
2010 let mask = path.coverage_mask(mask_width, mask_height, origin, SUPERSAMPLE);
2011
2012 let red = (color.0.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2013 let green = (color.1.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2014 let blue = (color.2.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
2015 let alpha = color.3.clamp(0.0, 1.0);
2016
2017 let mut pixels = Vec::with_capacity(mask.len() * 4);
2018 for coverage in mask {
2019 pixels.extend_from_slice(&[red, green, blue, (alpha * coverage as f32 + 0.5) as u8]);
2020 }
2021
2022 let Ok(image) = ImageBitmap::from_rgba8(mask_width as u32, mask_height as u32, pixels)
2023 else {
2024 return;
2025 };
2026
2027 self.push_other(DrawPrimitive::Image {
2028 rect: Rect {
2029 x: origin.x,
2030 y: origin.y,
2031 width: rect_width,
2032 height: rect_height,
2033 },
2034 image,
2035 alpha: 1.0,
2036 color_filter: None,
2037 sampling: ImageSampling::Linear,
2038 src_rect: None,
2039 });
2040 }
2041
2042 fn measure_text(&self, text: &str, style: &TextStyle) -> TextMeasurement {
2043 match &self.text_measurer {
2044 Some(measurer) => measurer.measure_text(text, style),
2045 None => estimate_text_measurement(text, style),
2046 }
2047 }
2048
2049 fn draw_text_at(&mut self, rect: Rect, brush: Brush, text: &str, style: &TextStyle) {
2050 if text.is_empty() {
2053 return;
2054 }
2055 let Some(color) = solid_fill_color(&brush) else {
2056 return;
2057 };
2058 if color.3 <= 0.0 {
2059 return;
2060 }
2061 let measurement = self.measure_text(text, style);
2062 if !(measurement.size.width > 0.0 && measurement.size.height > 0.0) {
2063 return;
2064 }
2065 let origin = align_text_block(rect, measurement, style);
2066 if !origin.x.is_finite() || !origin.y.is_finite() {
2067 return;
2068 }
2069 self.push_other(DrawPrimitive::Text(Box::new(TextPrimitive {
2070 rect: Rect::from_origin_size(origin, measurement.size),
2071 text: shared_text_str(text),
2072 style: style.clone(),
2073 color,
2074 })));
2075 }
2076
2077 fn into_primitives(self) -> Vec<DrawPrimitive> {
2078 self.finish().primitives
2079 }
2080}
2081
2082fn solid_fill_color(brush: &Brush) -> Option<Color> {
2087 match brush {
2088 Brush::Solid(color) => Some(*color),
2089 Brush::LinearGradient { colors, .. }
2090 | Brush::RadialGradient { colors, .. }
2091 | Brush::SweepGradient { colors, .. } => colors.first().copied(),
2092 }
2093}
2094
2095#[cfg(test)]
2096mod tests {
2097 use super::*;
2098 use crate::{Color, FontStyle, FontWeight, ImageBitmap, RenderEffect};
2099
2100 #[test]
2105 fn compact_recording_materializes_in_recorded_order() {
2106 let size = Size::new(100.0, 100.0);
2107 let solid = Brush::solid(Color::WHITE);
2108 let gradient = Brush::vertical_gradient(vec![Color::RED, Color::BLUE], 0.0, 100.0);
2109 let center = Point::new(50.0, 50.0);
2110 let stroke = Stroke::new(4.0);
2111 let rect = Rect {
2112 x: 10.0,
2113 y: 20.0,
2114 width: 30.0,
2115 height: 40.0,
2116 };
2117 let batch = vec![
2118 DrawPrimitive::Content,
2119 DrawPrimitive::Rect {
2120 rect,
2121 brush: solid.clone(),
2122 stroke: None,
2123 },
2124 ];
2125
2126 let record = |scope: &mut DrawScopeDefault| {
2128 scope.draw_rect_at(rect, solid.clone());
2129 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 1.5, stroke);
2130 scope.draw_rect_at(rect, gradient.clone());
2131 scope.draw_circle(solid.clone(), center, 12.0);
2132 scope.draw_arc(solid.clone(), center, 30.0, 0.5, 0.0, stroke); scope.draw_rect_at_blend(rect, solid.clone(), BlendMode::Plus);
2134 scope.draw_content();
2135 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2136 scope.push_recorded(batch.clone());
2137 };
2138
2139 let mut compact = DrawScopeDefault::new(size);
2140 record(&mut compact);
2141 let finished = compact.finish();
2142
2143 let arc_via_ordinary = |brush: Brush, radius: f32, start: f32, sweep: f32| {
2147 let mut scope = DrawScopeDefault::new(size);
2148 scope.draw_arc(brush, center, radius, start, sweep, stroke);
2149 scope.into_primitives().remove(0)
2150 };
2151 let expected = vec![
2152 DrawPrimitive::Rect {
2153 rect,
2154 brush: solid.clone(),
2155 stroke: None,
2156 },
2157 arc_via_ordinary(solid.clone(), 30.0, 0.5, 1.5),
2158 DrawPrimitive::Rect {
2159 rect,
2160 brush: gradient.clone(),
2161 stroke: None,
2162 },
2163 DrawPrimitive::RoundRect {
2164 rect: Rect {
2165 x: center.x - 12.0,
2166 y: center.y - 12.0,
2167 width: 24.0,
2168 height: 24.0,
2169 },
2170 brush: solid.clone(),
2171 radii: CornerRadii::uniform(12.0),
2172 stroke: None,
2173 },
2174 DrawPrimitive::Blend {
2175 primitive: Box::new(DrawPrimitive::Rect {
2176 rect,
2177 brush: solid.clone(),
2178 stroke: None,
2179 }),
2180 blend_mode: BlendMode::Plus,
2181 },
2182 DrawPrimitive::Content,
2183 {
2184 let mut scope = DrawScopeDefault::new(size);
2185 scope.draw_annular_sector(gradient.clone(), center, 10.0, 20.0, 0.0, 2.0);
2186 scope.into_primitives().remove(0)
2187 },
2188 DrawPrimitive::Content,
2189 DrawPrimitive::Rect {
2190 rect,
2191 brush: solid.clone(),
2192 stroke: None,
2193 },
2194 ];
2195 assert_eq!(finished.primitives, expected);
2196 assert_eq!(finished.content_markers, 2);
2197 }
2198
2199 #[test]
2202 fn reused_recording_buffers_record_identically_to_fresh() {
2203 let size = Size::new(64.0, 64.0);
2204 let record = |scope: &mut DrawScopeDefault| {
2205 scope.draw_circle(Brush::solid(Color::RED), Point::new(32.0, 32.0), 10.0);
2206 scope.draw_arc(
2207 Brush::solid(Color::BLUE),
2208 Point::new(32.0, 32.0),
2209 20.0,
2210 0.0,
2211 3.0,
2212 Stroke::new(2.0),
2213 );
2214 };
2215
2216 let mut fresh = DrawScopeDefault::new(size);
2217 record(&mut fresh);
2218 let fresh = fresh.finish();
2219
2220 let mut dirty = DrawScopeDefault::new(size);
2222 dirty.draw_rect(Brush::solid(Color::BLACK));
2223 dirty.draw_content();
2224 dirty.draw_arc(
2225 Brush::solid(Color::WHITE),
2226 Point::new(1.0, 1.0),
2227 5.0,
2228 1.0,
2229 1.0,
2230 Stroke::new(1.0),
2231 );
2232 let dirty = dirty.finish();
2233
2234 let mut reused =
2235 DrawScopeDefault::with_recording(size, None, dirty.recording, dirty.primitives);
2236 record(&mut reused);
2237 let reused = reused.finish();
2238
2239 assert_eq!(fresh.primitives, reused.primitives);
2240 assert_eq!(fresh.content_markers, reused.content_markers);
2241 }
2242
2243 #[test]
2244 fn redrawing_the_same_text_shares_one_str_allocation() {
2245 let first = shared_text_str("BREAK THE RING");
2246 let second = shared_text_str("BREAK THE RING");
2247 assert!(Rc::ptr_eq(&first, &second));
2248 assert_eq!(&*second, "BREAK THE RING");
2249 }
2250
2251 #[test]
2252 fn different_text_gets_its_own_str() {
2253 let first = shared_text_str("340");
2254 let second = shared_text_str("350");
2255 assert!(!Rc::ptr_eq(&first, &second));
2256 assert_eq!(&*first, "340");
2257 assert_eq!(&*second, "350");
2258 }
2259
2260 #[test]
2261 fn the_text_pool_survives_overflowing_its_capacity() {
2262 for index in 0..600 {
2263 let text = format!("run-{index}");
2264 assert_eq!(&*shared_text_str(&text), text.as_str());
2265 }
2266 assert_eq!(&*shared_text_str("still correct"), "still correct");
2267 }
2268
2269 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
2270 match primitive {
2271 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
2272 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
2273 other => panic!("expected image primitive, got {other:?}"),
2274 }
2275 }
2276
2277 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
2278 match primitive {
2279 DrawPrimitive::Image { .. } => primitive,
2280 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
2281 other => panic!("expected image primitive, got {other:?}"),
2282 }
2283 }
2284
2285 #[test]
2286 fn draw_svg_path_emits_supersampled_image_over_path_bounds() {
2287 let mut scope = DrawScopeDefault::new(Size::new(32.0, 32.0));
2288 scope.draw_svg_path("M 4 4 H 20 V 20 H 4 Z", Brush::solid(Color::RED));
2289
2290 let primitives = scope.into_primitives();
2291 assert_eq!(primitives.len(), 1);
2292 let DrawPrimitive::Image { rect, image, .. } = &primitives[0] else {
2293 panic!("expected image primitive, got {:?}", primitives[0]);
2294 };
2295
2296 assert_eq!((rect.x, rect.y), (3.0, 3.0));
2298 assert_eq!((rect.width, rect.height), (18.0, 18.0));
2299 assert_eq!((image.width(), image.height()), (36, 36));
2301
2302 let pixels = image.pixels();
2305 let index = (18 * 36 + 18) * 4;
2306 assert_eq!(
2307 &pixels[index..index + 4],
2308 &[255, 0, 0, 255],
2309 "path interior must be opaque brush color"
2310 );
2311 assert_eq!(pixels[3], 0, "outside the path must stay transparent");
2313 }
2314
2315 #[test]
2316 fn draw_svg_path_ignores_invalid_data() {
2317 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2318 scope.draw_svg_path("definitely not a path", Brush::solid(Color::WHITE));
2319 assert!(scope.into_primitives().is_empty());
2320 }
2321
2322 #[test]
2323 fn draw_vector_path_applies_brush_alpha() {
2324 let path = crate::VectorPath::parse("M 0 0 H 8 V 8 H 0 Z").expect("valid path");
2325 let mut scope = DrawScopeDefault::new(Size::new(16.0, 16.0));
2326 scope.draw_vector_path(&path, Brush::solid(Color::rgba(0.0, 0.0, 1.0, 0.5)));
2327
2328 let primitives = scope.into_primitives();
2329 let DrawPrimitive::Image { image, .. } = &primitives[0] else {
2330 panic!("expected image primitive");
2331 };
2332 let pixels = image.pixels();
2333 let width = image.width() as usize;
2335 let index = ((image.height() as usize / 2) * width + width / 2) * 4;
2336 assert_eq!(&pixels[index..index + 3], &[0, 0, 255]);
2337 let alpha = pixels[index + 3];
2338 assert!(
2339 (alpha as i32 - 128).abs() <= 2,
2340 "interior alpha must honor the brush alpha, got {alpha}"
2341 );
2342 }
2343
2344 #[test]
2345 fn draw_content_inserts_content_marker() {
2346 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
2347 scope.draw_rect(Brush::solid(Color::WHITE));
2348 scope.draw_content();
2349 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
2350
2351 let primitives = scope.into_primitives();
2352 assert!(matches!(primitives[1], DrawPrimitive::Content));
2353 assert!(matches!(
2354 primitives[2],
2355 DrawPrimitive::Blend {
2356 blend_mode: BlendMode::DstOut,
2357 ..
2358 }
2359 ));
2360 }
2361
2362 #[test]
2363 fn draw_rect_blend_wraps_non_default_modes() {
2364 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2365 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
2366
2367 let primitives = scope.into_primitives();
2368 assert_eq!(primitives.len(), 1);
2369 match &primitives[0] {
2370 DrawPrimitive::Blend {
2371 primitive,
2372 blend_mode,
2373 } => {
2374 assert_eq!(*blend_mode, BlendMode::DstOut);
2375 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
2376 }
2377 other => panic!("expected blended primitive, got {other:?}"),
2378 }
2379 }
2380
2381 #[test]
2382 fn draw_circle_records_centered_round_rect() {
2383 let mut scope = DrawScopeDefault::new(Size::new(40.0, 40.0));
2384 scope.draw_circle(Brush::solid(Color::BLUE), Point::new(12.0, 16.0), 5.0);
2385
2386 let primitives = scope.into_primitives();
2387 assert_eq!(primitives.len(), 1);
2388 match &primitives[0] {
2389 DrawPrimitive::RoundRect { rect, radii, .. } => {
2390 assert_eq!(
2391 *rect,
2392 Rect {
2393 x: 7.0,
2394 y: 11.0,
2395 width: 10.0,
2396 height: 10.0,
2397 }
2398 );
2399 assert_eq!(*radii, CornerRadii::uniform(5.0));
2400 }
2401 other => panic!("expected circular round rect, got {other:?}"),
2402 }
2403 }
2404
2405 #[test]
2406 fn draw_circle_blend_wraps_non_default_modes() {
2407 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2408 scope.draw_circle_blend(
2409 Brush::solid(Color::RED),
2410 Point::new(5.0, 5.0),
2411 3.0,
2412 BlendMode::Plus,
2413 );
2414
2415 let primitives = scope.into_primitives();
2416 assert_eq!(primitives.len(), 1);
2417 match &primitives[0] {
2418 DrawPrimitive::Blend {
2419 primitive,
2420 blend_mode,
2421 } => {
2422 assert_eq!(*blend_mode, BlendMode::Plus);
2423 assert!(matches!(**primitive, DrawPrimitive::RoundRect { .. }));
2424 }
2425 other => panic!("expected blended circle primitive, got {other:?}"),
2426 }
2427 }
2428
2429 #[test]
2430 fn rect_union_encloses_both_inputs() {
2431 let lhs = Rect {
2432 x: 10.0,
2433 y: 5.0,
2434 width: 8.0,
2435 height: 4.0,
2436 };
2437 let rhs = Rect {
2438 x: 4.0,
2439 y: 7.0,
2440 width: 10.0,
2441 height: 6.0,
2442 };
2443
2444 assert_eq!(
2445 lhs.union(rhs),
2446 Rect {
2447 x: 4.0,
2448 y: 5.0,
2449 width: 14.0,
2450 height: 8.0,
2451 }
2452 );
2453 }
2454
2455 #[test]
2456 fn draw_image_uses_scope_size_as_default_rect() {
2457 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
2458 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
2459 scope.draw_image(image.clone());
2460 let primitives = scope.into_primitives();
2461 assert_eq!(primitives.len(), 1);
2462 match unwrap_image(&primitives[0]) {
2463 DrawPrimitive::Image {
2464 rect,
2465 image: actual,
2466 alpha,
2467 color_filter,
2468 sampling,
2469 src_rect,
2470 } => {
2471 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
2472 assert_eq!(*actual, image);
2473 assert_eq!(*alpha, 1.0);
2474 assert!(color_filter.is_none());
2475 assert_eq!(*sampling, ImageSampling::Nearest);
2476 assert!(src_rect.is_none());
2477 }
2478 other => panic!("expected image primitive, got {other:?}"),
2479 }
2480 }
2481
2482 #[test]
2483 fn draw_image_src_stores_src_rect() {
2484 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2485 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2486 let src = Rect {
2487 x: 10.0,
2488 y: 20.0,
2489 width: 30.0,
2490 height: 40.0,
2491 };
2492 let dst = Rect {
2493 x: 0.0,
2494 y: 0.0,
2495 width: 60.0,
2496 height: 80.0,
2497 };
2498 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
2499 let primitives = scope.into_primitives();
2500 assert_eq!(primitives.len(), 1);
2501 match unwrap_image(&primitives[0]) {
2502 DrawPrimitive::Image {
2503 rect,
2504 image: actual,
2505 alpha,
2506 sampling,
2507 src_rect,
2508 ..
2509 } => {
2510 assert_eq!(*rect, dst);
2511 assert_eq!(*actual, image);
2512 assert!((alpha - 0.8).abs() < 1e-5);
2513 assert_eq!(*sampling, ImageSampling::Nearest);
2514 assert_eq!(*src_rect, Some(src));
2515 }
2516 other => panic!("expected image primitive, got {other:?}"),
2517 }
2518 }
2519
2520 #[test]
2521 fn draw_image_at_sampled_records_requested_sampling() {
2522 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2523 let image = ImageBitmap::from_rgba8(8, 8, vec![255; 8 * 8 * 4]).expect("image");
2524 let dst = Rect {
2525 x: 2.0,
2526 y: 3.0,
2527 width: 40.0,
2528 height: 30.0,
2529 };
2530
2531 scope.draw_image_at_sampled(dst, image.clone(), 0.7, None, ImageSampling::Linear);
2532
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 sampling,
2541 src_rect,
2542 ..
2543 } => {
2544 assert_eq!(*rect, dst);
2545 assert_eq!(*actual, image);
2546 assert!((alpha - 0.7).abs() < 1e-5);
2547 assert_eq!(*sampling, ImageSampling::Linear);
2548 assert!(src_rect.is_none());
2549 }
2550 other => panic!("expected image primitive, got {other:?}"),
2551 }
2552 }
2553
2554 #[test]
2555 fn draw_image_src_sampled_records_requested_sampling() {
2556 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
2557 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
2558 let src = Rect {
2559 x: 4.0,
2560 y: 6.0,
2561 width: 16.0,
2562 height: 20.0,
2563 };
2564 let dst = Rect {
2565 x: 8.0,
2566 y: 10.0,
2567 width: 32.0,
2568 height: 40.0,
2569 };
2570
2571 scope.draw_image_src_sampled(image.clone(), src, dst, 0.5, None, ImageSampling::Linear);
2572
2573 let primitives = scope.into_primitives();
2574 assert_eq!(primitives.len(), 1);
2575 match unwrap_image(&primitives[0]) {
2576 DrawPrimitive::Image {
2577 rect,
2578 image: actual,
2579 alpha,
2580 sampling,
2581 src_rect,
2582 ..
2583 } => {
2584 assert_eq!(*rect, dst);
2585 assert_eq!(*actual, image);
2586 assert!((alpha - 0.5).abs() < 1e-5);
2587 assert_eq!(*sampling, ImageSampling::Linear);
2588 assert_eq!(*src_rect, Some(src));
2589 }
2590 other => panic!("expected image primitive, got {other:?}"),
2591 }
2592 }
2593
2594 #[test]
2595 fn draw_image_at_clamps_alpha() {
2596 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
2597 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
2598 scope.draw_image_at(
2599 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
2600 image,
2601 3.0,
2602 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
2603 );
2604 assert_image_alpha(&scope.into_primitives()[0], 1.0);
2605 }
2606
2607 #[test]
2608 fn graphics_layer_clone_with_render_effect() {
2609 let layer = GraphicsLayer {
2610 render_effect: Some(RenderEffect::blur(10.0)),
2611 backdrop_effect: Some(RenderEffect::blur(6.0)),
2612 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
2613 alpha: 0.5,
2614 rotation_z: 12.0,
2615 shadow_elevation: 4.0,
2616 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
2617 clip: true,
2618 compositing_strategy: CompositingStrategy::Offscreen,
2619 blend_mode: BlendMode::SrcOver,
2620 ..Default::default()
2621 };
2622 let cloned = layer.clone();
2623 assert_eq!(cloned.alpha, 0.5);
2624 assert!(cloned.render_effect.is_some());
2625 assert!(cloned.backdrop_effect.is_some());
2626 assert_eq!(layer.color_filter, cloned.color_filter);
2627 assert_eq!(layer.render_effect, cloned.render_effect);
2628 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
2629 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
2630 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
2631 assert_eq!(
2632 cloned.shape,
2633 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
2634 );
2635 assert!(cloned.clip);
2636 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
2637 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
2638 }
2639
2640 #[test]
2641 fn graphics_layer_default_has_no_effect() {
2642 let layer = GraphicsLayer::default();
2643 assert!(layer.color_filter.is_none());
2644 assert!(layer.render_effect.is_none());
2645 assert!(layer.backdrop_effect.is_none());
2646 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
2647 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
2648 assert_eq!(layer.alpha, 1.0);
2649 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
2650 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
2651 assert_eq!(layer.shape, LayerShape::Rectangle);
2652 assert!(!layer.clip);
2653 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
2654 assert_eq!(layer.spot_shadow_color, Color::BLACK);
2655 }
2656
2657 #[test]
2658 fn transform_origin_construction() {
2659 let origin = TransformOrigin::new(0.25, 0.75);
2660 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
2661 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
2662 }
2663
2664 #[test]
2665 fn layer_shape_default_is_rectangle() {
2666 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
2667 }
2668
2669 use crate::{StrokeCap, StrokeJoin};
2672 use std::f32::consts::{FRAC_PI_2, PI};
2673
2674 fn approx(a: f32, b: f32) -> bool {
2679 (a - b).abs() < 0.25
2680 }
2681
2682 fn scope(size: f32) -> DrawScopeDefault {
2683 DrawScopeDefault::new(Size::new(size, size))
2684 }
2685
2686 #[test]
2687 fn draw_rect_stroked_records_scope_rect_and_stroke() {
2688 let mut scope = scope(20.0);
2689 scope.draw_rect_stroked(
2690 Brush::solid(Color::RED),
2691 Stroke::new(3.0).with_join(StrokeJoin::Bevel),
2692 );
2693
2694 let primitives = scope.into_primitives();
2695 assert_eq!(primitives.len(), 1);
2696 match &primitives[0] {
2697 DrawPrimitive::Rect {
2698 rect,
2699 stroke: Some(stroke),
2700 ..
2701 } => {
2702 assert_eq!(*rect, Rect::from_size(Size::new(20.0, 20.0)));
2705 assert_eq!(stroke.width, 3.0);
2706 assert_eq!(stroke.join, StrokeJoin::Bevel);
2707 }
2708 other => panic!("expected stroked rect, got {other:?}"),
2709 }
2710 }
2711
2712 #[test]
2713 fn draw_rect_at_stroked_records_requested_rect() {
2714 let mut scope = scope(50.0);
2715 let rect = Rect {
2716 x: 4.0,
2717 y: 6.0,
2718 width: 12.0,
2719 height: 9.0,
2720 };
2721 scope.draw_rect_at_stroked(rect, Brush::solid(Color::BLUE), Stroke::new(2.0));
2722 match &scope.into_primitives()[0] {
2723 DrawPrimitive::Rect {
2724 rect: actual,
2725 stroke: Some(stroke),
2726 ..
2727 } => {
2728 assert_eq!(*actual, rect);
2729 assert_eq!(stroke.width, 2.0);
2730 }
2731 other => panic!("expected stroked rect, got {other:?}"),
2732 }
2733 }
2734
2735 #[test]
2736 fn draw_round_rect_stroked_keeps_radii_and_stroke() {
2737 let mut scope = scope(30.0);
2738 scope.draw_round_rect_stroked(
2739 Brush::solid(Color::GREEN),
2740 CornerRadii::uniform(5.0),
2741 Stroke::new(4.0).with_join(StrokeJoin::Round),
2742 );
2743 match &scope.into_primitives()[0] {
2744 DrawPrimitive::RoundRect {
2745 rect,
2746 radii,
2747 stroke: Some(stroke),
2748 ..
2749 } => {
2750 assert_eq!(*rect, Rect::from_size(Size::new(30.0, 30.0)));
2751 assert_eq!(*radii, CornerRadii::uniform(5.0));
2752 assert_eq!(stroke.width, 4.0);
2753 assert_eq!(stroke.join, StrokeJoin::Round);
2754 }
2755 other => panic!("expected stroked round rect, got {other:?}"),
2756 }
2757 }
2758
2759 #[test]
2760 fn draw_round_rect_at_stroked_records_requested_rect() {
2761 let mut scope = scope(60.0);
2762 let rect = Rect {
2763 x: 1.0,
2764 y: 2.0,
2765 width: 20.0,
2766 height: 10.0,
2767 };
2768 scope.draw_round_rect_at_stroked(
2769 rect,
2770 Brush::solid(Color::WHITE),
2771 CornerRadii::uniform(3.0),
2772 Stroke::new(1.5),
2773 );
2774 match &scope.into_primitives()[0] {
2775 DrawPrimitive::RoundRect {
2776 rect: actual,
2777 radii,
2778 stroke: Some(stroke),
2779 ..
2780 } => {
2781 assert_eq!(*actual, rect);
2782 assert_eq!(*radii, CornerRadii::uniform(3.0));
2783 assert_eq!(stroke.width, 1.5);
2784 }
2785 other => panic!("expected stroked round rect, got {other:?}"),
2786 }
2787 }
2788
2789 #[test]
2790 fn draw_circle_stroked_lowers_to_stroked_round_rect() {
2791 let mut scope = scope(40.0);
2794 scope.draw_circle_stroked(
2795 Brush::solid(Color::BLUE),
2796 Point::new(12.0, 16.0),
2797 5.0,
2798 Stroke::new(2.0),
2799 );
2800 match &scope.into_primitives()[0] {
2801 DrawPrimitive::RoundRect {
2802 rect,
2803 radii,
2804 stroke: Some(stroke),
2805 ..
2806 } => {
2807 assert_eq!(
2808 *rect,
2809 Rect {
2810 x: 7.0,
2811 y: 11.0,
2812 width: 10.0,
2813 height: 10.0,
2814 }
2815 );
2816 assert_eq!(*radii, CornerRadii::uniform(5.0));
2817 assert_eq!(stroke.width, 2.0);
2818 }
2819 other => panic!("expected stroked circular round rect, got {other:?}"),
2820 }
2821 }
2822
2823 #[test]
2824 fn draw_arc_records_arc_primitive_with_tight_bounds() {
2825 let mut scope = scope(200.0);
2826 scope.draw_arc(
2827 Brush::solid(Color::RED),
2828 Point::new(100.0, 100.0),
2829 50.0,
2830 0.0,
2831 FRAC_PI_2,
2832 Stroke::new(10.0),
2833 );
2834 let primitives = scope.into_primitives();
2835 assert_eq!(primitives.len(), 1);
2836 match &primitives[0] {
2837 DrawPrimitive::Arc {
2838 rect,
2839 center,
2840 radius,
2841 start_angle,
2842 sweep_angle,
2843 stroke: Some(stroke),
2844 inner_radius,
2845 ..
2846 } => {
2847 assert_eq!(*center, Point::new(100.0, 100.0));
2848 assert_eq!(*radius, 50.0);
2849 assert_eq!(*start_angle, 0.0);
2850 assert!(approx(*sweep_angle, FRAC_PI_2));
2851 assert_eq!(stroke.width, 10.0);
2852 assert_eq!(*inner_radius, 0.0);
2853 assert!(approx(rect.x, 100.0), "{rect:?}");
2856 assert!(approx(rect.y, 100.0), "{rect:?}");
2857 assert!(approx(rect.width, 55.0), "{rect:?}");
2858 assert!(approx(rect.height, 55.0), "{rect:?}");
2859 }
2860 other => panic!("expected arc primitive, got {other:?}"),
2861 }
2862 }
2863
2864 #[test]
2865 fn draw_arc_bounds_cover_a_quadrant_spanning_sweep() {
2866 let mut scope = scope(200.0);
2867 scope.draw_arc(
2870 Brush::solid(Color::RED),
2871 Point::new(100.0, 100.0),
2872 50.0,
2873 0.0,
2874 3.0 * FRAC_PI_2,
2875 Stroke::new(4.0),
2876 );
2877 let DrawPrimitive::Arc { rect, .. } = &scope.into_primitives()[0] else {
2878 panic!("expected arc primitive");
2879 };
2880 assert!(approx(rect.x, 48.0), "{rect:?}");
2881 assert!(approx(rect.y, 48.0), "{rect:?}");
2882 assert!(approx(rect.width, 104.0), "{rect:?}");
2883 assert!(approx(rect.height, 104.0), "{rect:?}");
2884 }
2885
2886 #[test]
2887 fn draw_annular_sector_records_inner_radius_and_no_stroke() {
2888 let mut scope = scope(200.0);
2889 scope.draw_annular_sector(
2890 Brush::solid(Color::WHITE),
2891 Point::new(100.0, 100.0),
2892 30.0,
2893 50.0,
2894 0.0,
2895 PI,
2896 );
2897 match &scope.into_primitives()[0] {
2898 DrawPrimitive::Arc {
2899 rect,
2900 center,
2901 radius,
2902 inner_radius,
2903 stroke,
2904 sweep_angle,
2905 ..
2906 } => {
2907 assert!(stroke.is_none(), "annular sectors are filled, not stroked");
2908 assert_eq!(*center, Point::new(100.0, 100.0));
2909 assert_eq!(*radius, 50.0);
2910 assert_eq!(*inner_radius, 30.0);
2911 assert!(approx(*sweep_angle, PI));
2912 assert!(approx(rect.x, 50.0), "{rect:?}");
2914 assert!(approx(rect.y, 100.0), "{rect:?}");
2915 assert!(approx(rect.width, 100.0), "{rect:?}");
2916 assert!(approx(rect.height, 50.0), "{rect:?}");
2917 }
2918 other => panic!("expected arc primitive, got {other:?}"),
2919 }
2920 }
2921
2922 #[test]
2923 fn draw_arc_blend_wraps_non_default_modes() {
2924 let mut scope = scope(100.0);
2925 scope.draw_arc_blend(
2926 Brush::solid(Color::RED),
2927 Point::new(50.0, 50.0),
2928 20.0,
2929 0.0,
2930 1.0,
2931 Stroke::new(2.0),
2932 BlendMode::DstOut,
2933 );
2934 match &scope.into_primitives()[0] {
2935 DrawPrimitive::Blend {
2936 primitive,
2937 blend_mode,
2938 } => {
2939 assert_eq!(*blend_mode, BlendMode::DstOut);
2940 assert!(matches!(**primitive, DrawPrimitive::Arc { .. }));
2941 }
2942 other => panic!("expected blended arc, got {other:?}"),
2943 }
2944 }
2945
2946 #[test]
2947 fn draw_annular_sector_blend_wraps_non_default_modes() {
2948 let mut scope = scope(100.0);
2949 scope.draw_annular_sector_blend(
2950 Brush::solid(Color::RED),
2951 Point::new(50.0, 50.0),
2952 5.0,
2953 20.0,
2954 0.0,
2955 1.0,
2956 BlendMode::Plus,
2957 );
2958 assert!(matches!(
2959 &scope.into_primitives()[0],
2960 DrawPrimitive::Blend {
2961 blend_mode: BlendMode::Plus,
2962 ..
2963 }
2964 ));
2965 }
2966
2967 #[test]
2968 fn stroked_blend_variants_wrap_non_default_modes() {
2969 let mut scope = scope(20.0);
2970 scope.draw_rect_stroked_blend(
2971 Brush::solid(Color::RED),
2972 Stroke::new(2.0),
2973 BlendMode::DstOut,
2974 );
2975 scope.draw_round_rect_stroked_blend(
2976 Brush::solid(Color::RED),
2977 CornerRadii::uniform(2.0),
2978 Stroke::new(2.0),
2979 BlendMode::DstOut,
2980 );
2981 scope.draw_circle_stroked_blend(
2982 Brush::solid(Color::RED),
2983 Point::new(10.0, 10.0),
2984 5.0,
2985 Stroke::new(2.0),
2986 BlendMode::DstOut,
2987 );
2988 let primitives = scope.into_primitives();
2989 assert_eq!(primitives.len(), 3);
2990 for primitive in &primitives {
2991 assert!(
2992 matches!(
2993 primitive,
2994 DrawPrimitive::Blend {
2995 blend_mode: BlendMode::DstOut,
2996 ..
2997 }
2998 ),
2999 "expected blended primitive, got {primitive:?}"
3000 );
3001 }
3002 }
3003
3004 #[test]
3005 fn negative_sweeps_and_overlong_sweeps_produce_finite_bounds() {
3006 let mut scope = scope(200.0);
3007 scope.draw_arc(
3008 Brush::solid(Color::RED),
3009 Point::new(100.0, 100.0),
3010 40.0,
3011 FRAC_PI_2,
3012 -FRAC_PI_2,
3013 Stroke::new(4.0),
3014 );
3015 scope.draw_arc(
3016 Brush::solid(Color::RED),
3017 Point::new(100.0, 100.0),
3018 40.0,
3019 0.3,
3020 crate::stroke::TAU * 4.0,
3021 Stroke::new(4.0),
3022 );
3023 let primitives = scope.into_primitives();
3024 assert_eq!(primitives.len(), 2);
3025
3026 let DrawPrimitive::Arc { rect: negative, .. } = &primitives[0] else {
3027 panic!("expected arc");
3028 };
3029 assert!(approx(negative.x, 100.0), "{negative:?}");
3031 assert!(approx(negative.y, 100.0), "{negative:?}");
3032 assert!(approx(negative.width, 42.0), "{negative:?}");
3033
3034 let DrawPrimitive::Arc { rect: full, .. } = &primitives[1] else {
3035 panic!("expected arc");
3036 };
3037 assert!(approx(full.x, 58.0), "{full:?}");
3039 assert!(approx(full.width, 84.0), "{full:?}");
3040 assert!(approx(full.height, 84.0), "{full:?}");
3041 }
3042
3043 #[test]
3044 fn degenerate_stroke_and_arc_inputs_emit_nothing_and_never_panic() {
3045 let mut scope = scope(50.0);
3046 let brush = Brush::solid(Color::RED);
3047 let center = Point::new(25.0, 25.0);
3048
3049 scope.draw_rect_stroked(brush.clone(), Stroke::new(0.0));
3051 scope.draw_rect_stroked(brush.clone(), Stroke::new(-4.0));
3052 scope.draw_rect_stroked(brush.clone(), Stroke::new(f32::NAN));
3053 scope.draw_round_rect_stroked(brush.clone(), CornerRadii::uniform(2.0), Stroke::new(0.0));
3054 scope.draw_circle_stroked(brush.clone(), center, 10.0, Stroke::new(0.0));
3055 scope.draw_circle_stroked(brush.clone(), center, f32::NAN, Stroke::new(2.0));
3056 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 0.0, Stroke::new(2.0));
3058 scope.draw_arc(brush.clone(), center, 10.0, 0.0, f32::NAN, Stroke::new(2.0));
3059 scope.draw_arc(
3060 brush.clone(),
3061 center,
3062 f32::INFINITY,
3063 0.0,
3064 1.0,
3065 Stroke::new(2.0),
3066 );
3067 scope.draw_arc(brush.clone(), center, 10.0, 0.0, 1.0, Stroke::new(0.0));
3069 scope.draw_arc(brush.clone(), center, 0.0, 0.0, 1.0, Stroke::new(0.0));
3070 scope.draw_annular_sector(brush.clone(), center, 10.0, 10.0, 0.0, 1.0);
3072 scope.draw_annular_sector(brush.clone(), center, 20.0, 10.0, 0.0, 1.0);
3073 scope.draw_annular_sector(brush.clone(), center, 0.0, 0.0, 0.0, 1.0);
3074 scope.draw_annular_sector(brush.clone(), center, 0.0, 10.0, 0.0, 0.0);
3075 scope.draw_annular_sector(brush, center, f32::NAN, 10.0, 0.0, 1.0);
3076
3077 assert!(
3078 scope.into_primitives().is_empty(),
3079 "degenerate stroke/arc requests must not reach the renderer"
3080 );
3081 }
3082
3083 #[test]
3084 fn zero_radius_arc_with_positive_width_stays_finite() {
3085 let mut scope = scope(50.0);
3088 scope.draw_arc(
3089 Brush::solid(Color::RED),
3090 Point::new(25.0, 25.0),
3091 0.0,
3092 0.0,
3093 FRAC_PI_2,
3094 Stroke::new(6.0).with_cap(StrokeCap::Round),
3095 );
3096 let primitives = scope.into_primitives();
3097 assert_eq!(primitives.len(), 1);
3098 let DrawPrimitive::Arc { rect, .. } = &primitives[0] else {
3099 panic!("expected arc");
3100 };
3101 for value in [rect.x, rect.y, rect.width, rect.height] {
3102 assert!(value.is_finite(), "{rect:?}");
3103 }
3104 assert!(rect.width > 0.0 && rect.height > 0.0, "{rect:?}");
3105 }
3106
3107 struct FixedAdvanceTextMeasurer {
3112 advance: f32,
3113 line_height: f32,
3114 calls: std::cell::Cell<usize>,
3115 }
3116
3117 impl FixedAdvanceTextMeasurer {
3118 fn shared(advance: f32, line_height: f32) -> Rc<Self> {
3119 Rc::new(Self {
3120 advance,
3121 line_height,
3122 calls: std::cell::Cell::new(0),
3123 })
3124 }
3125 }
3126
3127 impl DrawTextMeasurer for FixedAdvanceTextMeasurer {
3128 fn measure_text(&self, text: &str, _style: &TextStyle) -> TextMeasurement {
3129 self.calls.set(self.calls.get() + 1);
3130 let lines: Vec<&str> = text.split('\n').collect();
3131 let width = lines
3132 .iter()
3133 .map(|line| line.chars().count() as f32 * self.advance)
3134 .fold(0.0_f32, f32::max);
3135 TextMeasurement {
3136 size: Size::new(width, lines.len() as f32 * self.line_height),
3137 line_height: self.line_height,
3138 first_baseline: self.line_height * 0.75,
3139 line_count: lines.len(),
3140 }
3141 }
3142 }
3143
3144 fn text_scope(size: Size) -> (DrawScopeDefault, Rc<FixedAdvanceTextMeasurer>) {
3145 let measurer = FixedAdvanceTextMeasurer::shared(10.0, 20.0);
3146 (
3147 DrawScopeDefault::with_text_measurer(size, measurer.clone()),
3148 measurer,
3149 )
3150 }
3151
3152 fn unwrap_text(primitive: &DrawPrimitive) -> &TextPrimitive {
3153 match primitive {
3154 DrawPrimitive::Text(text) => text,
3155 other => panic!("expected text primitive, got {other:?}"),
3156 }
3157 }
3158
3159 #[test]
3160 fn drawn_text_occupies_exactly_the_box_measure_text_reported() {
3161 let (mut scope, _) = text_scope(Size::new(200.0, 100.0));
3162 let style = TextStyle::new(16.0);
3163 let measured = scope.measure_text("ABCD", &style);
3164
3165 scope.draw_text_from(
3166 Point::new(7.0, 11.0),
3167 Brush::solid(Color::WHITE),
3168 "ABCD",
3169 &style,
3170 );
3171
3172 let primitives = scope.into_primitives();
3173 assert_eq!(primitives.len(), 1);
3174 let text = unwrap_text(&primitives[0]);
3175 assert_eq!(
3176 text.rect,
3177 Rect {
3178 x: 7.0,
3179 y: 11.0,
3180 width: measured.size.width,
3181 height: measured.size.height,
3182 },
3183 "the drawn block must be the measured block, or callers cannot center text"
3184 );
3185 assert_eq!(&*text.text, "ABCD");
3186 assert_eq!(text.color, Color::WHITE);
3187 }
3188
3189 #[test]
3190 fn text_alignment_positions_the_measured_block_inside_the_box() {
3191 let box_rect = Rect {
3192 x: 100.0,
3193 y: 50.0,
3194 width: 200.0,
3195 height: 80.0,
3196 };
3197 let cases = [
3199 (TextAlign::Left, TextVerticalAlign::Top, 100.0, 50.0),
3200 (TextAlign::Center, TextVerticalAlign::Center, 190.0, 80.0),
3201 (TextAlign::Right, TextVerticalAlign::Bottom, 280.0, 110.0),
3202 ];
3203 for (align, vertical_align, expected_x, expected_y) in cases {
3204 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3205 let style = TextStyle::new(16.0)
3206 .with_align(align)
3207 .with_vertical_align(vertical_align);
3208 scope.draw_text_at(box_rect, Brush::solid(Color::WHITE), "AB", &style);
3209 let primitives = scope.into_primitives();
3210 let text = unwrap_text(&primitives[0]);
3211 assert!(
3212 approx(text.rect.x, expected_x) && approx(text.rect.y, expected_y),
3213 "{align:?}/{vertical_align:?} placed the block at {:?}",
3214 text.rect
3215 );
3216 assert!(approx(text.rect.width, 20.0) && approx(text.rect.height, 20.0));
3217 }
3218 }
3219
3220 #[test]
3221 fn baseline_aligned_text_hangs_above_the_box_edge() {
3222 let (mut scope, _) = text_scope(Size::new(200.0, 200.0));
3223 let style = TextStyle::new(16.0).with_vertical_align(TextVerticalAlign::Baseline);
3224 let measured = scope.measure_text("Ag", &style);
3225 scope.draw_text_at(
3226 Rect {
3227 x: 0.0,
3228 y: 100.0,
3229 width: 200.0,
3230 height: 0.0,
3231 },
3232 Brush::solid(Color::WHITE),
3233 "Ag",
3234 &style,
3235 );
3236 let primitives = scope.into_primitives();
3237 let text = unwrap_text(&primitives[0]);
3238 assert!(
3240 approx(text.rect.y, 100.0 - measured.first_baseline),
3241 "{:?}",
3242 text.rect
3243 );
3244 }
3245
3246 #[test]
3247 fn draw_text_fills_the_whole_scope_rect() {
3248 let (mut scope, _) = text_scope(Size::new(120.0, 60.0));
3249 let style = TextStyle::new(16.0)
3250 .with_align(TextAlign::Right)
3251 .with_vertical_align(TextVerticalAlign::Bottom);
3252 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3253 let primitives = scope.into_primitives();
3254 let text = unwrap_text(&primitives[0]);
3255 assert!(
3256 approx(text.rect.x, 100.0) && approx(text.rect.y, 40.0),
3257 "{:?}",
3258 text.rect
3259 );
3260 }
3261
3262 #[test]
3263 fn draw_text_from_ignores_alignment_and_anchors_the_top_left() {
3264 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3265 let style = TextStyle::new(16.0)
3267 .with_align(TextAlign::Center)
3268 .with_vertical_align(TextVerticalAlign::Bottom);
3269 scope.draw_text_from(
3270 Point::new(30.0, 40.0),
3271 Brush::solid(Color::WHITE),
3272 "AB",
3273 &style,
3274 );
3275 let primitives = scope.into_primitives();
3276 let text = unwrap_text(&primitives[0]);
3277 assert!(
3278 approx(text.rect.x, 30.0) && approx(text.rect.y, 40.0),
3279 "{:?}",
3280 text.rect
3281 );
3282 }
3283
3284 #[test]
3285 fn multiline_text_measures_the_widest_line_and_stacks_the_lines() {
3286 let (mut scope, _) = text_scope(Size::new(400.0, 400.0));
3287 let style = TextStyle::new(16.0);
3288 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "AB\nABCDE", &style);
3289 let primitives = scope.into_primitives();
3290 let text = unwrap_text(&primitives[0]);
3291 assert!(approx(text.rect.width, 50.0), "{:?}", text.rect);
3292 assert!(approx(text.rect.height, 40.0), "{:?}", text.rect);
3293 }
3294
3295 #[test]
3296 fn empty_text_draws_nothing_and_never_measures() {
3297 let (mut scope, measurer) = text_scope(Size::new(100.0, 100.0));
3298 scope.draw_text(Brush::solid(Color::WHITE), "", &TextStyle::new(16.0));
3299 scope.draw_text_at(
3300 Rect::from_size(Size::new(10.0, 10.0)),
3301 Brush::solid(Color::WHITE),
3302 "",
3303 &TextStyle::new(16.0),
3304 );
3305 scope.draw_text_from(
3306 Point::ZERO,
3307 Brush::solid(Color::WHITE),
3308 "",
3309 &TextStyle::new(16.0),
3310 );
3311 assert!(scope.into_primitives().is_empty());
3312 assert_eq!(
3313 measurer.calls.get(),
3314 0,
3315 "an empty string must not cost a measurement"
3316 );
3317 }
3318
3319 #[test]
3320 fn invisible_text_draws_nothing() {
3321 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3322 let style = TextStyle::new(16.0);
3323 scope.draw_text(Brush::solid(Color(1.0, 1.0, 1.0, 0.0)), "AB", &style);
3324 scope.draw_text(
3325 Brush::LinearGradient {
3326 colors: Vec::new(),
3327 stops: None,
3328 start: Point::ZERO,
3329 end: Point::new(1.0, 1.0),
3330 tile_mode: crate::render_effect::TileMode::Clamp,
3331 },
3332 "AB",
3333 &style,
3334 );
3335 assert!(scope.into_primitives().is_empty());
3336 }
3337
3338 #[test]
3339 fn gradient_text_brushes_fall_back_to_their_first_stop() {
3340 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3341 scope.draw_text(
3342 Brush::linear_gradient(vec![Color::RED, Color::BLUE]),
3343 "AB",
3344 &TextStyle::new(16.0),
3345 );
3346 let primitives = scope.into_primitives();
3347 assert_eq!(unwrap_text(&primitives[0]).color, Color::RED);
3348 }
3349
3350 #[test]
3351 fn a_scope_without_a_measurer_falls_back_to_the_font_free_estimate() {
3352 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
3353 let style = TextStyle::new(16.0);
3354 assert_eq!(
3355 scope.measure_text("ABC", &style),
3356 crate::estimate_text_measurement("ABC", &style)
3357 );
3358 scope.draw_text_from(Point::ZERO, Brush::solid(Color::WHITE), "ABC", &style);
3359 let primitives = scope.into_primitives();
3360 let text = unwrap_text(&primitives[0]);
3361 assert!(text.rect.width > 0.0 && text.rect.height > 0.0);
3362 }
3363
3364 #[test]
3365 fn degenerate_text_geometry_emits_nothing_and_never_panics() {
3366 struct DegenerateTextMeasurer;
3367 impl DrawTextMeasurer for DegenerateTextMeasurer {
3368 fn measure_text(&self, _text: &str, _style: &TextStyle) -> TextMeasurement {
3369 TextMeasurement {
3370 size: Size::new(f32::NAN, 0.0),
3371 line_height: f32::NAN,
3372 first_baseline: f32::NAN,
3373 line_count: 1,
3374 }
3375 }
3376 }
3377
3378 let mut scope = DrawScopeDefault::with_text_measurer(
3379 Size::new(50.0, 50.0),
3380 Rc::new(DegenerateTextMeasurer),
3381 );
3382 scope.draw_text(Brush::solid(Color::WHITE), "AB", &TextStyle::new(16.0));
3383 scope.draw_text_at(
3384 Rect {
3385 x: f32::NAN,
3386 y: 0.0,
3387 width: 10.0,
3388 height: 10.0,
3389 },
3390 Brush::solid(Color::WHITE),
3391 "AB",
3392 &TextStyle::new(16.0),
3393 );
3394 assert!(
3395 scope.into_primitives().is_empty(),
3396 "unmeasurable text must not reach the renderer"
3397 );
3398 }
3399
3400 #[test]
3401 fn text_style_survives_lowering_into_the_primitive() {
3402 let (mut scope, _) = text_scope(Size::new(100.0, 100.0));
3403 let style = TextStyle::new(21.0)
3404 .with_font_family("Fira Sans")
3405 .with_weight(FontWeight::BOLD)
3406 .with_style(FontStyle::Italic)
3407 .with_letter_spacing(2.0)
3408 .with_line_height(26.0);
3409 scope.draw_text(Brush::solid(Color::WHITE), "AB", &style);
3410 let primitives = scope.into_primitives();
3411 assert_eq!(unwrap_text(&primitives[0]).style, style);
3412 }
3413}