1use bytemuck::{Pod, Zeroable};
5
6use teksilo_canvas::render_frame::PaintData;
7use teksilo_canvas::{DecorationRect, GlyphQuad, PathEntry, ShadowQuad, ShapeQuad, Transform2D};
8
9#[inline]
17fn srgb_to_linear(c: f32) -> f32 {
18 if c <= 0.04045 {
19 c / 12.92
20 } else {
21 ((c + 0.055) / 1.055).powf(2.4)
22 }
23}
24
25#[inline]
28pub fn srgb_to_linear_rgba(c: [f32; 4]) -> [f32; 4] {
29 [
30 srgb_to_linear(c[0]),
31 srgb_to_linear(c[1]),
32 srgb_to_linear(c[2]),
33 c[3],
34 ]
35}
36
37pub const QUAD_FLAG_COLOR_GLYPH: u32 = 1;
40
41#[repr(C)]
43#[derive(Debug, Clone, Copy, Pod, Zeroable)]
44pub struct QuadVertex {
45 pub position: [f32; 2],
46 pub tex_coord: [f32; 2],
47 pub color: [f32; 4],
48 pub flags: u32,
51 pub _pad: u32,
54}
55
56const GLYPH_SNAP_AXIS_EPS: f32 = 1e-4;
60
61const GLYPH_SNAP_SIZE_EPS: f32 = 1.0 / 16.0;
67
68#[inline]
72fn apply_affine(p: [f32; 2], t: &Transform2D) -> [f32; 2] {
73 let [a, b, c, d, tx, ty] = t.m;
74 [a * p[0] + c * p[1] + tx, b * p[0] + d * p[1] + ty]
75}
76
77impl QuadVertex {
78 pub fn from_glyph_quad_transformed(
104 quad: &GlyphQuad,
105 scale_factor: f32,
106 atlas_width: u32,
107 atlas_height: u32,
108 transform: &Transform2D,
109 ) -> [QuadVertex; 4] {
110 let [x, y, w, h] = quad.screen;
111 let [ax, ay, aw, ah] = quad.atlas;
112 let sx = x * scale_factor;
113 let sy = y * scale_factor;
114 let sw = w * scale_factor;
115 let sh = h * scale_factor;
116
117 let aw_f = atlas_width.max(1) as f32;
119 let ah_f = atlas_height.max(1) as f32;
120 let u0 = ax / aw_f;
121 let v0 = ay / ah_f;
122 let u1 = (ax + aw) / aw_f;
123 let v1 = (ay + ah) / ah_f;
124
125 let [a, b, c, d, _, _] = transform.m;
126 let axis_aligned = b.abs() < GLYPH_SNAP_AXIS_EPS && c.abs() < GLYPH_SNAP_AXIS_EPS;
127 let one_to_one = axis_aligned
128 && (a * sw - aw).abs() < GLYPH_SNAP_SIZE_EPS
129 && (d * sh - ah).abs() < GLYPH_SNAP_SIZE_EPS;
130 let positions: [[f32; 2]; 4] = if one_to_one {
131 let [ox, oy] = apply_affine([sx, sy], transform);
132 let ox = ox.round();
133 let oy = oy.round();
134 [[ox, oy], [ox + aw, oy], [ox + aw, oy + ah], [ox, oy + ah]]
135 } else {
136 [
137 apply_affine([sx, sy], transform),
138 apply_affine([sx + sw, sy], transform),
139 apply_affine([sx + sw, sy + sh], transform),
140 apply_affine([sx, sy + sh], transform),
141 ]
142 };
143
144 let flags = if quad.is_color {
153 QUAD_FLAG_COLOR_GLYPH
154 } else {
155 0
156 };
157 let color = srgb_to_linear_rgba(quad.color);
158
159 [
160 QuadVertex {
161 position: positions[0],
162 tex_coord: [u0, v0],
163 color,
164 flags,
165 _pad: 0,
166 },
167 QuadVertex {
168 position: positions[1],
169 tex_coord: [u1, v0],
170 color,
171 flags,
172 _pad: 0,
173 },
174 QuadVertex {
175 position: positions[2],
176 tex_coord: [u1, v1],
177 color,
178 flags,
179 _pad: 0,
180 },
181 QuadVertex {
182 position: positions[3],
183 tex_coord: [u0, v1],
184 color,
185 flags,
186 _pad: 0,
187 },
188 ]
189 }
190}
191
192#[repr(C)]
194#[derive(Debug, Clone, Copy, Pod, Zeroable)]
195pub struct RectVertex {
196 pub position: [f32; 2],
197 pub color: [f32; 4],
198}
199
200impl RectVertex {
201 pub fn from_decoration(rect: &DecorationRect, scale_factor: f32) -> [RectVertex; 4] {
203 let [x, y, w, h] = rect.rect;
204 let sx = x * scale_factor;
205 let sy = y * scale_factor;
206 let sw = w * scale_factor;
207 let sh = h * scale_factor;
208
209 [
210 RectVertex {
211 position: [sx, sy],
212 color: srgb_to_linear_rgba(rect.color),
213 },
214 RectVertex {
215 position: [sx + sw, sy],
216 color: srgb_to_linear_rgba(rect.color),
217 },
218 RectVertex {
219 position: [sx + sw, sy + sh],
220 color: srgb_to_linear_rgba(rect.color),
221 },
222 RectVertex {
223 position: [sx, sy + sh],
224 color: srgb_to_linear_rgba(rect.color),
225 },
226 ]
227 }
228}
229
230#[repr(C)]
232#[derive(Debug, Clone, Copy, Pod, Zeroable)]
233pub struct SdfVertex {
234 pub position: [f32; 2],
235 pub local_uv: [f32; 2],
237 pub color: [f32; 4],
238 pub corner_radii: [f32; 4],
239 pub shape_params: [f32; 4],
242 pub gradient_geo: [f32; 4],
244 pub gradient_color0: [f32; 4],
246 pub gradient_color1: [f32; 4],
248 pub gradient_color2: [f32; 4],
250 pub gradient_color3: [f32; 4],
252 pub gradient_offsets: [f32; 4],
254}
255
256impl SdfVertex {
257 pub fn from_shape_quad(shape: &ShapeQuad, scale_factor: f32) -> [SdfVertex; 4] {
270 Self::shape_quad_verts(shape, scale_factor, shape.stroke_width * scale_factor)
271 }
272
273 pub fn from_shape_quad_cosmetic(
286 shape: &ShapeQuad,
287 scale_factor: f32,
288 zoom: f32,
289 ) -> [SdfVertex; 4] {
290 let zoom = zoom.max(1e-3);
291 Self::shape_quad_verts(
292 shape,
293 scale_factor,
294 shape.stroke_width * scale_factor / zoom,
295 )
296 }
297
298 fn shape_quad_verts(shape: &ShapeQuad, scale_factor: f32, stroke_px: f32) -> [SdfVertex; 4] {
303 let [x, y, w, h] = shape.screen;
304 let sx = x * scale_factor;
305 let sy = y * scale_factor;
306 let sw = w * scale_factor;
307 let sh = h * scale_factor;
308
309 let (paint_type, gradient_geo, colors, offsets) =
311 encode_paint_data(&shape.paint_data, [0.0, 0.0], w, h);
312
313 let stroke = stroke_px;
314 let pad = stroke * 0.5 + 1.0;
318 let u_pad = if sw > 0.0 { pad / sw } else { 0.0 };
319 let v_pad = if sh > 0.0 { pad / sh } else { 0.0 };
320
321 let params = [sw, sh, stroke, paint_type as f32];
322 let scaled_corner_radii = [
329 shape.corner_radii[0] * scale_factor,
330 shape.corner_radii[1] * scale_factor,
331 shape.corner_radii[2] * scale_factor,
332 shape.corner_radii[3] * scale_factor,
333 ];
334
335 let base = SdfVertex {
336 position: [0.0, 0.0],
337 local_uv: [0.0, 0.0],
338 color: srgb_to_linear_rgba(shape.color),
339 corner_radii: scaled_corner_radii,
340 shape_params: params,
341 gradient_geo,
342 gradient_color0: srgb_to_linear_rgba(colors[0]),
343 gradient_color1: srgb_to_linear_rgba(colors[1]),
344 gradient_color2: srgb_to_linear_rgba(colors[2]),
345 gradient_color3: srgb_to_linear_rgba(colors[3]),
346 gradient_offsets: offsets,
347 };
348
349 [
350 SdfVertex {
351 position: [sx - pad, sy - pad],
352 local_uv: [-u_pad, -v_pad],
353 ..base
354 },
355 SdfVertex {
356 position: [sx + sw + pad, sy - pad],
357 local_uv: [1.0 + u_pad, -v_pad],
358 ..base
359 },
360 SdfVertex {
361 position: [sx + sw + pad, sy + sh + pad],
362 local_uv: [1.0 + u_pad, 1.0 + v_pad],
363 ..base
364 },
365 SdfVertex {
366 position: [sx - pad, sy + sh + pad],
367 local_uv: [-u_pad, 1.0 + v_pad],
368 ..base
369 },
370 ]
371 }
372}
373
374pub(crate) fn encode_paint_data(
390 paint_data: &PaintData,
391 origin: [f32; 2],
392 width: f32,
393 height: f32,
394) -> (u32, [f32; 4], [[f32; 4]; 4], [f32; 4]) {
395 let zero_colors = [[0.0; 4]; 4];
396 let zero_offsets = [0.0; 4];
397 let u = |x: f32| (x - origin[0]) / width;
398 let v = |y: f32| (y - origin[1]) / height;
399
400 match paint_data {
401 PaintData::Solid => (0, [0.0; 4], zero_colors, zero_offsets),
402 PaintData::LinearGradient { start, end, stops } => {
403 let geo = [u(start[0]), v(start[1]), u(end[0]), v(end[1])];
405 let (colors, offsets) = encode_stops(stops);
406 (1, geo, colors, offsets)
407 }
408 PaintData::RadialGradient {
409 center,
410 radius,
411 stops,
412 } => {
413 let aspect = height / width.max(0.0001);
418 let geo = [u(center[0]), v(center[1]), *radius / width, aspect];
419 let (colors, offsets) = encode_stops(stops);
420 (2, geo, colors, offsets)
421 }
422 PaintData::ConicGradient {
423 center,
424 start_angle,
425 stops,
426 } => {
427 let geo = [u(center[0]), v(center[1]), *start_angle, 0.0];
428 let (colors, offsets) = encode_stops(stops);
429 (3, geo, colors, offsets)
430 }
431 }
432}
433
434pub(crate) fn encode_stops(stops: &[teksilo_canvas::GradientStop]) -> ([[f32; 4]; 4], [f32; 4]) {
437 let mut colors = [[0.0f32; 4]; 4];
438 let mut offsets = [0.0f32; 4];
439 for (i, stop) in stops.iter().take(4).enumerate() {
440 colors[i] = stop.color.to_array();
441 offsets[i] = stop.offset;
442 }
443 if !stops.is_empty() {
445 let last_idx = stops.len().min(4) - 1;
446 for i in stops.len()..4 {
447 colors[i] = colors[last_idx];
448 offsets[i] = offsets[last_idx];
449 }
450 }
451 (colors, offsets)
452}
453
454#[repr(C)]
470#[derive(Debug, Clone, Copy, Pod, Zeroable)]
471pub struct PathGradientVertex {
472 pub position: [f32; 2],
473 pub tex_coord: [f32; 2],
475 pub local_uv: [f32; 2],
477 pub paint_type: u32,
482 pub _pad: u32,
484 pub gradient_geo: [f32; 4],
488 pub gradient_color0: [f32; 4],
490 pub gradient_color1: [f32; 4],
492 pub gradient_color2: [f32; 4],
494 pub gradient_color3: [f32; 4],
496 pub gradient_offsets: [f32; 4],
498}
499
500impl PathGradientVertex {
501 pub(crate) fn from_path_entry(
515 entry: &PathEntry,
516 placement: &crate::path_atlas::PathPlacement,
517 scale_factor: f32,
518 atlas_width: u32,
519 atlas_height: u32,
520 opacity: f32,
521 transform: &Transform2D,
522 ) -> [PathGradientVertex; 4] {
523 let region = &placement.region;
526 let [sx, sy, sw, sh] = placement.device_rect;
527
528 let aw = atlas_width.max(1) as f32;
529 let ah = atlas_height.max(1) as f32;
530 let u0 = region.x as f32 / aw;
531 let v0 = region.y as f32 / ah;
532 let u1 = (region.x + region.w) as f32 / aw;
533 let v1 = (region.y + region.h) as f32 / ah;
534
535 let sf = scale_factor.max(1e-4);
541 let origin = [
542 (sx - entry.bounds[0] * scale_factor) / sf,
543 (sy - entry.bounds[1] * scale_factor) / sf,
544 ];
545 let (paint_type, gradient_geo, raw_colors, gradient_offsets) =
546 encode_paint_data(&entry.paint_data, origin, sw / sf, sh / sf);
547 let colors: [[f32; 4]; 4] = std::array::from_fn(|i| {
550 let mut c = srgb_to_linear_rgba(raw_colors[i]);
551 c[3] *= opacity;
552 c
553 });
554
555 let positions = [
556 apply_affine([sx, sy], transform),
557 apply_affine([sx + sw, sy], transform),
558 apply_affine([sx + sw, sy + sh], transform),
559 apply_affine([sx, sy + sh], transform),
560 ];
561 let tex_coords = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];
562 let local_uvs: [[f32; 2]; 4] = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
563
564 std::array::from_fn(|i| PathGradientVertex {
565 position: positions[i],
566 tex_coord: tex_coords[i],
567 local_uv: local_uvs[i],
568 paint_type,
569 _pad: 0,
570 gradient_geo,
571 gradient_color0: colors[0],
572 gradient_color1: colors[1],
573 gradient_color2: colors[2],
574 gradient_color3: colors[3],
575 gradient_offsets,
576 })
577 }
578}
579
580pub const QUAD_INDICES: [u32; 6] = [0, 1, 2, 0, 2, 3];
588
589pub fn generate_quad_indices(count: usize) -> Vec<u32> {
591 let mut indices = Vec::with_capacity(count * 6);
592 for i in 0..count {
593 let base = (i * 4) as u32;
594 for &offset in &QUAD_INDICES {
595 indices.push(base + offset);
596 }
597 }
598 indices
599}
600
601#[repr(C)]
603#[derive(Debug, Clone, Copy, Pod, Zeroable)]
604pub struct ShadowVertex {
605 pub position: [f32; 2],
606 pub local_uv: [f32; 2],
608 pub shadow_color: [f32; 4],
609 pub corner_radii: [f32; 4],
610 pub shadow_params: [f32; 4],
612 pub shape_offset: [f32; 4],
614}
615
616impl ShadowVertex {
617 pub fn from_shadow_quad(shadow: &ShadowQuad, scale_factor: f32) -> [ShadowVertex; 4] {
619 let [x, y, w, h] = shadow.screen;
620 let sx = x * scale_factor;
621 let sy = y * scale_factor;
622 let sw = w * scale_factor;
623 let sh = h * scale_factor;
624
625 let [sr_x, sr_y, sr_w, sr_h] = shadow.shape_rect;
626 let shape_w = sr_w * scale_factor;
627 let shape_h = sr_h * scale_factor;
628
629 let shadow_cx = sx + sw * 0.5;
631 let shadow_cy = sy + sh * 0.5;
632 let shape_cx = (sr_x + sr_w * 0.5) * scale_factor;
633 let shape_cy = (sr_y + sr_h * 0.5) * scale_factor;
634 let offset_x = shape_cx - shadow_cx;
635 let offset_y = shape_cy - shadow_cy;
636
637 let params = [
638 shape_w,
639 shape_h,
640 shadow.blur_radius * scale_factor,
641 shadow.spread * scale_factor,
642 ];
643 let offset = [offset_x, offset_y, 0.0, 0.0];
644 let scaled_corner_radii = [
649 shadow.corner_radii[0] * scale_factor,
650 shadow.corner_radii[1] * scale_factor,
651 shadow.corner_radii[2] * scale_factor,
652 shadow.corner_radii[3] * scale_factor,
653 ];
654
655 [
656 ShadowVertex {
657 position: [sx, sy],
658 local_uv: [0.0, 0.0],
659 shadow_color: srgb_to_linear_rgba(shadow.color),
660 corner_radii: scaled_corner_radii,
661 shadow_params: params,
662 shape_offset: offset,
663 },
664 ShadowVertex {
665 position: [sx + sw, sy],
666 local_uv: [1.0, 0.0],
667 shadow_color: srgb_to_linear_rgba(shadow.color),
668 corner_radii: scaled_corner_radii,
669 shadow_params: params,
670 shape_offset: offset,
671 },
672 ShadowVertex {
673 position: [sx + sw, sy + sh],
674 local_uv: [1.0, 1.0],
675 shadow_color: srgb_to_linear_rgba(shadow.color),
676 corner_radii: scaled_corner_radii,
677 shadow_params: params,
678 shape_offset: offset,
679 },
680 ShadowVertex {
681 position: [sx, sy + sh],
682 local_uv: [0.0, 1.0],
683 shadow_color: srgb_to_linear_rgba(shadow.color),
684 corner_radii: scaled_corner_radii,
685 shadow_params: params,
686 shape_offset: offset,
687 },
688 ]
689 }
690}
691
692#[repr(C)]
700#[derive(Debug, Clone, Copy, Pod, Zeroable)]
701pub struct AnimQuadVertex {
702 pub position: [f32; 2],
704 pub uv: [f32; 2],
709 pub slot: u32,
713 pub _pad: u32,
716}
717
718impl AnimQuadVertex {
719 pub fn from_animated_quad(
720 draw: &teksilo_canvas::AnimatedQuadDraw,
721 scale_factor: f32,
722 ) -> [AnimQuadVertex; 4] {
723 let [x, y, w, h] = draw.screen;
724 let sx = x * scale_factor;
725 let sy = y * scale_factor;
726 let sw = w * scale_factor;
727 let sh = h * scale_factor;
728 [
729 AnimQuadVertex {
730 position: [sx, sy],
731 uv: [0.0, 0.0],
732 slot: draw.slot,
733 _pad: 0,
734 },
735 AnimQuadVertex {
736 position: [sx + sw, sy],
737 uv: [1.0, 0.0],
738 slot: draw.slot,
739 _pad: 0,
740 },
741 AnimQuadVertex {
742 position: [sx + sw, sy + sh],
743 uv: [1.0, 1.0],
744 slot: draw.slot,
745 _pad: 0,
746 },
747 AnimQuadVertex {
748 position: [sx, sy + sh],
749 uv: [0.0, 1.0],
750 slot: draw.slot,
751 _pad: 0,
752 },
753 ]
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760 use teksilo_canvas::{DecorationKind, GradientStop, PaintData, ShapeKind, StrokeSpace};
761 use teksilo_tokens::Color;
762
763 fn glyph(screen: [f32; 4], atlas: [f32; 4], is_color: bool) -> GlyphQuad {
765 GlyphQuad {
766 screen,
767 atlas,
768 color: [1.0, 1.0, 1.0, 1.0],
769 is_color,
770 }
771 }
772
773 fn assert_pos_near(actual: [f32; 2], expected: [f32; 2]) {
774 assert!(
775 (actual[0] - expected[0]).abs() < 1e-3 && (actual[1] - expected[1]).abs() < 1e-3,
776 "position {actual:?} != expected {expected:?}"
777 );
778 }
779
780 #[test]
781 fn glyph_quad_to_vertices() {
782 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 64.0, 64.0], false);
785 let verts =
786 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
787 assert_eq!(verts.len(), 4);
788 assert_eq!(verts[0].position, [10.0, 20.0]);
789 assert_eq!(verts[1].position, [40.0, 20.0]); assert_eq!(verts[2].position, [40.0, 60.0]); assert_eq!(verts[0].tex_coord, [0.0, 0.0]);
793 assert_eq!(verts[2].tex_coord, [0.25, 0.25]);
794 }
795
796 #[test]
797 fn scale_factor_applied_to_glyph_coords() {
798 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 128.0, 128.0], false);
800 let verts =
801 QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &Transform2D::IDENTITY);
802 assert_eq!(verts[0].position, [20.0, 40.0]);
803 assert_eq!(verts[1].position, [80.0, 40.0]);
804 }
805
806 #[test]
807 fn glyph_snap_identity_fractional_origin() {
808 let quad = glyph([10.3, 20.7, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
812 let verts =
813 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
814 assert_eq!(verts[0].position, [10.0, 21.0]);
815 assert_eq!(verts[1].position, [40.0, 21.0]);
816 assert_eq!(verts[2].position, [40.0, 61.0]);
817 assert_eq!(verts[3].position, [10.0, 61.0]);
818 }
819
820 #[test]
821 fn glyph_snap_hidpi_scale_factor() {
822 let quad = glyph([5.7, 8.3, 16.0, 16.0], [0.0, 0.0, 32.0, 32.0], false);
826 let verts =
827 QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &Transform2D::IDENTITY);
828 assert_eq!(verts[0].position, [11.0, 17.0]);
829 assert_eq!(verts[2].position, [43.0, 49.0]);
830 }
831
832 #[test]
833 fn glyph_snap_fractional_dpi() {
834 let quad = glyph([4.2, 7.8, 20.0, 20.0], [0.0, 0.0, 25.0, 25.0], false);
838 let verts =
839 QuadVertex::from_glyph_quad_transformed(&quad, 1.25, 256, 256, &Transform2D::IDENTITY);
840 assert_eq!(verts[0].position, [5.0, 10.0]);
841 assert_eq!(verts[2].position, [30.0, 35.0]);
842 }
843
844 #[test]
845 fn glyph_snap_exact_bucket_zoom() {
846 let quad = glyph([4.0, 8.0, 20.0, 20.0], [0.0, 0.0, 50.0, 50.0], false);
852 let zoom = Transform2D {
853 m: [1.25, 0.0, 0.0, 1.25, 3.3, 7.8],
854 };
855 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &zoom);
856 assert_eq!(verts[0].position, [13.0, 28.0]);
858 assert_eq!(verts[2].position, [63.0, 78.0]);
859 }
860
861 #[test]
862 fn glyph_no_snap_mid_bucket_residual() {
863 let quad = glyph([4.0, 8.0, 20.0, 20.0], [0.0, 0.0, 50.0, 50.0], false);
867 let zoom = Transform2D {
868 m: [1.1, 0.0, 0.0, 1.1, 3.3, 7.8],
869 };
870 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &zoom);
871 assert_pos_near(verts[0].position, [1.1 * 8.0 + 3.3, 1.1 * 16.0 + 7.8]);
872 assert_pos_near(verts[2].position, [1.1 * 48.0 + 3.3, 1.1 * 56.0 + 7.8]);
873 }
874
875 #[test]
876 fn glyph_no_snap_rotation() {
877 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
879 let (s, c) = (0.1_f32.sin(), 0.1_f32.cos());
880 let rot = Transform2D {
881 m: [c, s, -s, c, 0.0, 0.0],
882 };
883 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &rot);
884 assert_pos_near(
885 verts[0].position,
886 [c * 10.0 - s * 20.0, s * 10.0 + c * 20.0],
887 );
888 assert_pos_near(
889 verts[2].position,
890 [c * 40.0 - s * 60.0, s * 40.0 + c * 60.0],
891 );
892 }
893
894 #[test]
895 fn glyph_snap_translation_only_transform() {
896 let quad = glyph([10.3, 20.0, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
899 let pan = Transform2D {
900 m: [1.0, 0.0, 0.0, 1.0, 5.7, 3.2],
901 };
902 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &pan);
903 assert_eq!(verts[0].position, [16.0, 23.0]);
905 assert_eq!(verts[2].position, [46.0, 63.0]);
906 }
907
908 #[test]
909 fn glyph_snap_color_emoji_flag_preserved() {
910 let quad = glyph([10.3, 20.7, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], true);
911 let verts =
912 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
913 assert_eq!(verts[0].position, [10.0, 21.0]);
914 for v in &verts {
915 assert_eq!(v.flags, QUAD_FLAG_COLOR_GLYPH);
916 }
917 }
918
919 #[test]
920 fn glyph_uvs_independent_of_snapping() {
921 let quad = glyph([10.3, 20.7, 30.0, 40.0], [16.0, 32.0, 30.0, 40.0], false);
924 let snapped =
925 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
926 let residual = Transform2D {
927 m: [1.1, 0.0, 0.0, 1.1, 0.0, 0.0],
928 };
929 let unsnapped = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &residual);
930 for (a, b) in snapped.iter().zip(unsnapped.iter()) {
931 assert_eq!(a.tex_coord, b.tex_coord);
932 }
933 assert_eq!(snapped[0].tex_coord, [16.0 / 256.0, 32.0 / 256.0]);
934 assert_eq!(snapped[2].tex_coord, [46.0 / 256.0, 72.0 / 256.0]);
935 }
936
937 #[test]
938 fn decoration_rect_to_vertices() {
939 let rect = DecorationRect {
940 rect: [0.0, 0.0, 100.0, 2.0],
941 color: [1.0, 0.0, 0.0, 1.0],
942 kind: DecorationKind::FocusRing,
943 };
944 let verts = RectVertex::from_decoration(&rect, 1.0);
945 assert_eq!(verts.len(), 4);
946 assert_eq!(verts[0].position, [0.0, 0.0]);
947 assert_eq!(verts[2].position, [100.0, 2.0]);
948 }
949
950 #[test]
951 fn shape_quad_to_sdf_vertices() {
952 let shape = ShapeQuad {
953 screen: [0.0, 0.0, 100.0, 40.0],
954 color: [0.0, 0.5, 0.0, 1.0],
955 shape: ShapeKind::RoundedRect,
956 stroke_width: 0.0,
957 stroke_space: StrokeSpace::Logical,
958 corner_radii: [6.0, 6.0, 6.0, 6.0],
959 paint_data: PaintData::Solid,
960 };
961 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
962 assert_eq!(verts.len(), 4);
963 assert_eq!(verts[0].corner_radii, [6.0, 6.0, 6.0, 6.0]);
964 assert_eq!(verts[0].position, [-1.0, -1.0]);
967 assert_eq!(verts[2].position, [101.0, 41.0]);
968 assert!((verts[0].local_uv[0] - (-0.01)).abs() < 1e-5);
969 assert!((verts[0].local_uv[1] - (-0.025)).abs() < 1e-5);
970 assert!((verts[2].local_uv[0] - 1.01).abs() < 1e-5);
971 assert!((verts[2].local_uv[1] - 1.025).abs() < 1e-5);
972 }
973
974 #[test]
975 fn sdf_scale_factor() {
976 let shape = ShapeQuad {
977 screen: [10.0, 10.0, 100.0, 40.0],
978 color: [0.0, 0.0, 0.0, 1.0],
979 shape: ShapeKind::RoundedRect,
980 stroke_width: 2.0,
981 stroke_space: StrokeSpace::Logical,
982 corner_radii: [4.0; 4],
983 paint_data: PaintData::Solid,
984 };
985 let verts = SdfVertex::from_shape_quad(&shape, 2.0);
986 assert_eq!(verts[0].position, [17.0, 17.0]);
989 assert_eq!(verts[0].shape_params[2], 4.0); assert_eq!(verts[0].corner_radii, [8.0; 4]);
994 }
995
996 #[test]
997 fn sdf_circle_stays_circle_on_hidpi() {
998 let shape = ShapeQuad {
1004 screen: [0.0, 0.0, 19.0, 19.0],
1005 color: [0.0, 0.0, 0.0, 1.0],
1006 shape: ShapeKind::RoundedRect,
1007 stroke_width: 0.0,
1008 stroke_space: StrokeSpace::Logical,
1009 corner_radii: [9.5; 4],
1010 paint_data: PaintData::Solid,
1011 };
1012 let verts = SdfVertex::from_shape_quad(&shape, 2.0);
1013 assert_eq!(verts[0].shape_params[0], 38.0);
1014 assert_eq!(verts[0].shape_params[1], 38.0);
1015 assert_eq!(verts[0].corner_radii, [19.0; 4]);
1016 }
1017
1018 #[test]
1019 fn cosmetic_shape_stroke_param_is_inverse_zoom() {
1020 let shape = ShapeQuad {
1025 screen: [0.0, 0.0, 100.0, 100.0],
1026 color: [0.0, 0.0, 0.0, 1.0],
1027 shape: ShapeKind::RoundedRect,
1028 stroke_width: 2.0,
1029 stroke_space: StrokeSpace::Device,
1030 corner_radii: [10.0; 4],
1031 paint_data: PaintData::Solid,
1032 };
1033 let sf = 2.0;
1034 let logical = SdfVertex::from_shape_quad(&shape, sf);
1035 let z1 = SdfVertex::from_shape_quad_cosmetic(&shape, sf, 1.0);
1036 let z2 = SdfVertex::from_shape_quad_cosmetic(&shape, sf, 2.0);
1037 assert!((z1[0].shape_params[2] - logical[0].shape_params[2]).abs() < 1e-4);
1039 assert!((z1[0].shape_params[2] - 4.0).abs() < 1e-4);
1040 assert!((z2[0].shape_params[2] - 2.0).abs() < 1e-4);
1042 assert_eq!(z1[0].shape_params[0], z2[0].shape_params[0]);
1045 assert_eq!(z2[0].shape_params[0], 200.0);
1046 }
1047
1048 #[test]
1049 fn sdf_linear_gradient_encoding() {
1050 let shape = ShapeQuad {
1051 screen: [0.0, 0.0, 100.0, 50.0],
1052 color: [1.0, 1.0, 1.0, 1.0],
1053 shape: ShapeKind::RoundedRect,
1054 stroke_width: 0.0,
1055 stroke_space: StrokeSpace::Logical,
1056 corner_radii: [0.0; 4],
1057 paint_data: PaintData::LinearGradient {
1058 start: [0.0, 0.0],
1059 end: [100.0, 0.0],
1060 stops: vec![
1061 GradientStop {
1062 offset: 0.0,
1063 color: Color::RED,
1064 },
1065 GradientStop {
1066 offset: 1.0,
1067 color: Color::BLUE,
1068 },
1069 ],
1070 },
1071 };
1072 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
1073 assert!((verts[0].shape_params[3] - 1.0).abs() < 0.01);
1075 assert!((verts[0].gradient_geo[0]).abs() < 0.01);
1077 assert!((verts[0].gradient_geo[2] - 1.0).abs() < 0.01);
1078 assert!((verts[0].gradient_color0[0] - 1.0).abs() < 0.01);
1080 assert!((verts[0].gradient_offsets[0]).abs() < 0.01);
1082 assert!((verts[0].gradient_offsets[1] - 1.0).abs() < 0.01);
1083 }
1084
1085 #[test]
1086 fn linear_gradient_endpoints_are_rect_local_not_absolute() {
1087 let shape = ShapeQuad {
1096 screen: [50.0, 100.0, 200.0, 200.0],
1097 color: [1.0, 1.0, 1.0, 1.0],
1098 shape: ShapeKind::RoundedRect,
1099 stroke_width: 0.0,
1100 stroke_space: StrokeSpace::Logical,
1101 corner_radii: [0.0; 4],
1102 paint_data: PaintData::LinearGradient {
1103 start: [0.0, 0.0],
1104 end: [0.0, 200.0],
1105 stops: vec![
1106 GradientStop {
1107 offset: 0.0,
1108 color: Color::new(0.0, 0.0, 0.0, 0.0),
1109 },
1110 GradientStop {
1111 offset: 1.0,
1112 color: Color::BLACK,
1113 },
1114 ],
1115 },
1116 };
1117 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
1118 assert!((verts[0].gradient_geo[0]).abs() < 1e-5, "start_uv.x");
1119 assert!((verts[0].gradient_geo[1]).abs() < 1e-5, "start_uv.y");
1120 assert!((verts[0].gradient_geo[2]).abs() < 1e-5, "end_uv.x");
1121 assert!((verts[0].gradient_geo[3] - 1.0).abs() < 1e-5, "end_uv.y");
1122 }
1123
1124 fn rasterize_for_test(entry: &PathEntry, atlas_size: u32) -> crate::path_atlas::PathPlacement {
1130 place_for_test(entry, atlas_size, 1.0, false)
1131 }
1132
1133 fn place_for_test(
1134 entry: &PathEntry,
1135 atlas_size: u32,
1136 scale_factor: f32,
1137 snap: bool,
1138 ) -> crate::path_atlas::PathPlacement {
1139 let mut atlas = crate::path_atlas::PathAtlas::new(atlas_size, atlas_size);
1140 atlas.begin_frame();
1141 atlas
1142 .lookup_or_rasterize(
1143 &entry.path,
1144 &entry.stroke_style,
1145 entry.fill_rule,
1146 entry.bounds,
1147 scale_factor,
1148 1.0,
1149 snap,
1150 )
1151 .expect("test path rasterizes")
1152 }
1153
1154 #[test]
1162 fn snapping_the_quad_does_not_move_the_gradient() {
1163 let bounds_rect = teksilo_canvas::Rect::new(1.5, 1.5, 13.0, 13.0);
1165 let entry = gradient_path_entry(
1166 bounds_rect,
1167 PaintData::LinearGradient {
1168 start: [0.0, 0.0],
1169 end: [13.0, 0.0],
1170 stops: vec![
1171 GradientStop {
1172 offset: 0.0,
1173 color: Color::RED,
1174 },
1175 GradientStop {
1176 offset: 1.0,
1177 color: Color::BLUE,
1178 },
1179 ],
1180 },
1181 );
1182
1183 for snap in [false, true] {
1184 let placement = place_for_test(&entry, 256, 1.0, snap);
1185 let verts = PathGradientVertex::from_path_entry(
1186 &entry,
1187 &placement,
1188 1.0,
1189 256,
1190 256,
1191 1.0,
1192 &Transform2D::IDENTITY,
1193 );
1194 let [qx, _, qw, _] = placement.device_rect;
1195 let start_x = qx + verts[0].gradient_geo[0] * qw;
1197 let end_x = qx + verts[0].gradient_geo[2] * qw;
1198 assert!(
1199 (start_x - 1.5).abs() < 0.01,
1200 "snap={snap}: gradient start must sit at the bounds origin, got {start_x}"
1201 );
1202 assert!(
1203 (end_x - 14.5).abs() < 0.01,
1204 "snap={snap}: gradient end must sit at the bounds' far edge, got {end_x}"
1205 );
1206 }
1207 }
1208
1209 fn gradient_path_entry(bounds_rect: teksilo_canvas::Rect, paint_data: PaintData) -> PathEntry {
1210 use teksilo_canvas::{FillRule, StrokeStyle};
1211 PathEntry {
1212 path: teksilo_canvas::Path::rect(bounds_rect),
1213 color: [1.0, 1.0, 1.0, 1.0],
1214 stroke_style: StrokeStyle::solid(0.0),
1215 fill_rule: FillRule::Winding,
1216 bounds: bounds_rect.to_array(),
1217 paint_data,
1218 }
1219 }
1220
1221 #[test]
1222 fn path_gradient_linear_encoding() {
1223 let bounds_rect = teksilo_canvas::Rect::new(0.0, 0.0, 100.0, 50.0);
1224 let entry = gradient_path_entry(
1225 bounds_rect,
1226 PaintData::LinearGradient {
1227 start: [0.0, 0.0],
1228 end: [100.0, 0.0],
1229 stops: vec![
1230 GradientStop {
1231 offset: 0.0,
1232 color: Color::RED,
1233 },
1234 GradientStop {
1235 offset: 1.0,
1236 color: Color::BLUE,
1237 },
1238 ],
1239 },
1240 );
1241 let placement = rasterize_for_test(&entry, 256);
1242
1243 let verts = PathGradientVertex::from_path_entry(
1244 &entry,
1245 &placement,
1246 1.0,
1247 256,
1248 256,
1249 1.0,
1250 &Transform2D::IDENTITY,
1251 );
1252
1253 assert_eq!(verts[0].paint_type, 1);
1255 assert!((verts[0].gradient_geo[0]).abs() < 0.01);
1257 assert!((verts[0].gradient_geo[2] - 1.0).abs() < 0.01);
1258 assert!((verts[0].gradient_color0[0] - 1.0).abs() < 0.01);
1260 assert!((verts[0].gradient_color0[1]).abs() < 0.01);
1261 assert!((verts[0].gradient_offsets[0]).abs() < 0.01);
1263 assert!((verts[0].gradient_offsets[1] - 1.0).abs() < 0.01);
1264 assert_eq!(verts[0].local_uv, [0.0, 0.0]);
1266 assert_eq!(verts[1].local_uv, [1.0, 0.0]);
1267 assert_eq!(verts[2].local_uv, [1.0, 1.0]);
1268 assert_eq!(verts[3].local_uv, [0.0, 1.0]);
1269 }
1270
1271 #[test]
1272 fn path_gradient_endpoints_are_rect_local_not_absolute() {
1273 let bounds_rect = teksilo_canvas::Rect::new(50.0, 100.0, 200.0, 200.0);
1279 let entry = gradient_path_entry(
1280 bounds_rect,
1281 PaintData::LinearGradient {
1282 start: [0.0, 0.0],
1283 end: [0.0, 200.0],
1284 stops: vec![
1285 GradientStop {
1286 offset: 0.0,
1287 color: Color::new(0.0, 0.0, 0.0, 0.0),
1288 },
1289 GradientStop {
1290 offset: 1.0,
1291 color: Color::BLACK,
1292 },
1293 ],
1294 },
1295 );
1296 let placement = rasterize_for_test(&entry, 512);
1297
1298 let verts = PathGradientVertex::from_path_entry(
1299 &entry,
1300 &placement,
1301 1.0,
1302 512,
1303 512,
1304 1.0,
1305 &Transform2D::IDENTITY,
1306 );
1307 assert!((verts[0].gradient_geo[0]).abs() < 1e-5, "start_uv.x");
1308 assert!((verts[0].gradient_geo[1]).abs() < 1e-5, "start_uv.y");
1309 assert!((verts[0].gradient_geo[2]).abs() < 1e-5, "end_uv.x");
1310 assert!((verts[0].gradient_geo[3] - 1.0).abs() < 1e-5, "end_uv.y");
1311 }
1312
1313 #[test]
1314 fn path_gradient_opacity_folds_into_every_stop_alpha() {
1315 let bounds_rect = teksilo_canvas::Rect::new(0.0, 0.0, 40.0, 20.0);
1321 let entry = gradient_path_entry(
1322 bounds_rect,
1323 PaintData::LinearGradient {
1324 start: [0.0, 0.0],
1325 end: [40.0, 0.0],
1326 stops: vec![
1327 GradientStop {
1328 offset: 0.0,
1329 color: Color::RED,
1330 },
1331 GradientStop {
1332 offset: 1.0,
1333 color: Color::BLUE,
1334 },
1335 ],
1336 },
1337 );
1338 let placement = rasterize_for_test(&entry, 128);
1339
1340 let full = PathGradientVertex::from_path_entry(
1341 &entry,
1342 &placement,
1343 1.0,
1344 128,
1345 128,
1346 1.0,
1347 &Transform2D::IDENTITY,
1348 );
1349 let half = PathGradientVertex::from_path_entry(
1350 &entry,
1351 &placement,
1352 1.0,
1353 128,
1354 128,
1355 0.5,
1356 &Transform2D::IDENTITY,
1357 );
1358
1359 assert!((full[0].gradient_color0[3] - 1.0).abs() < 1e-5);
1360 assert!((half[0].gradient_color0[3] - 0.5).abs() < 1e-5);
1361 assert!((half[0].gradient_color1[3] - 0.5).abs() < 1e-5);
1362 }
1363
1364 #[test]
1365 fn generate_indices_for_multiple_quads() {
1366 let indices = generate_quad_indices(2);
1367 assert_eq!(indices.len(), 12);
1368 assert_eq!(&indices[0..6], &[0, 1, 2, 0, 2, 3]);
1369 assert_eq!(&indices[6..12], &[4, 5, 6, 4, 6, 7]);
1370 }
1371
1372 #[test]
1373 fn shadow_quad_to_vertices() {
1374 let shadow = ShadowQuad {
1375 screen: [0.0, 0.0, 120.0, 60.0],
1376 color: [0.0, 0.0, 0.0, 0.3],
1377 corner_radii: [6.0; 4],
1378 shape_rect: [10.0, 8.0, 100.0, 40.0],
1379 blur_radius: 4.0,
1380 spread: 0.0,
1381 };
1382 let verts = ShadowVertex::from_shadow_quad(&shadow, 1.0);
1383 assert_eq!(verts.len(), 4);
1384 assert_eq!(verts[0].position, [0.0, 0.0]);
1385 assert_eq!(verts[2].position, [120.0, 60.0]);
1386 assert_eq!(verts[0].shadow_params[2], 4.0); assert_eq!(verts[0].corner_radii, [6.0; 4]);
1388 }
1389
1390 #[test]
1391 fn shadow_scale_factor() {
1392 let shadow = ShadowQuad {
1393 screen: [10.0, 10.0, 120.0, 60.0],
1394 color: [0.0, 0.0, 0.0, 0.3],
1395 corner_radii: [6.0; 4],
1396 shape_rect: [20.0, 18.0, 100.0, 40.0],
1397 blur_radius: 4.0,
1398 spread: 2.0,
1399 };
1400 let verts = ShadowVertex::from_shadow_quad(&shadow, 2.0);
1401 assert_eq!(verts[0].position, [20.0, 20.0]);
1402 assert_eq!(verts[0].shadow_params[0], 200.0); assert_eq!(verts[0].shadow_params[2], 8.0); assert_eq!(verts[0].shadow_params[3], 4.0); assert_eq!(verts[0].corner_radii, [12.0; 4]);
1408 }
1409}