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, 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(
381 paint_data: &PaintData,
382 width: f32,
383 height: f32,
384) -> (u32, [f32; 4], [[f32; 4]; 4], [f32; 4]) {
385 let zero_colors = [[0.0; 4]; 4];
386 let zero_offsets = [0.0; 4];
387
388 match paint_data {
389 PaintData::Solid => (0, [0.0; 4], zero_colors, zero_offsets),
390 PaintData::LinearGradient { start, end, stops } => {
391 let geo = [
393 start[0] / width,
394 start[1] / height,
395 end[0] / width,
396 end[1] / height,
397 ];
398 let (colors, offsets) = encode_stops(stops);
399 (1, geo, colors, offsets)
400 }
401 PaintData::RadialGradient {
402 center,
403 radius,
404 stops,
405 } => {
406 let aspect = height / width.max(0.0001);
411 let geo = [
412 center[0] / width,
413 center[1] / height,
414 *radius / width,
415 aspect,
416 ];
417 let (colors, offsets) = encode_stops(stops);
418 (2, geo, colors, offsets)
419 }
420 PaintData::ConicGradient {
421 center,
422 start_angle,
423 stops,
424 } => {
425 let geo = [center[0] / width, center[1] / height, *start_angle, 0.0];
426 let (colors, offsets) = encode_stops(stops);
427 (3, geo, colors, offsets)
428 }
429 }
430}
431
432pub(crate) fn encode_stops(stops: &[teksilo_canvas::GradientStop]) -> ([[f32; 4]; 4], [f32; 4]) {
435 let mut colors = [[0.0f32; 4]; 4];
436 let mut offsets = [0.0f32; 4];
437 for (i, stop) in stops.iter().take(4).enumerate() {
438 colors[i] = stop.color.to_array();
439 offsets[i] = stop.offset;
440 }
441 if !stops.is_empty() {
443 let last_idx = stops.len().min(4) - 1;
444 for i in stops.len()..4 {
445 colors[i] = colors[last_idx];
446 offsets[i] = offsets[last_idx];
447 }
448 }
449 (colors, offsets)
450}
451
452#[repr(C)]
468#[derive(Debug, Clone, Copy, Pod, Zeroable)]
469pub struct PathGradientVertex {
470 pub position: [f32; 2],
471 pub tex_coord: [f32; 2],
473 pub local_uv: [f32; 2],
475 pub paint_type: u32,
480 pub _pad: u32,
482 pub gradient_geo: [f32; 4],
486 pub gradient_color0: [f32; 4],
488 pub gradient_color1: [f32; 4],
490 pub gradient_color2: [f32; 4],
492 pub gradient_color3: [f32; 4],
494 pub gradient_offsets: [f32; 4],
496}
497
498impl PathGradientVertex {
499 pub(crate) fn from_path_entry(
513 entry: &PathEntry,
514 region: &crate::path_atlas::AtlasRegion,
515 scale_factor: f32,
516 atlas_width: u32,
517 atlas_height: u32,
518 opacity: f32,
519 transform: &Transform2D,
520 ) -> [PathGradientVertex; 4] {
521 let [bx, by, bw, bh] = entry.bounds;
522 let sx = bx * scale_factor;
523 let sy = by * scale_factor;
524 let sw = bw * scale_factor;
525 let sh = bh * scale_factor;
526
527 let aw = atlas_width.max(1) as f32;
528 let ah = atlas_height.max(1) as f32;
529 let u0 = region.x as f32 / aw;
530 let v0 = region.y as f32 / ah;
531 let u1 = (region.x + region.w) as f32 / aw;
532 let v1 = (region.y + region.h) as f32 / ah;
533
534 let (paint_type, gradient_geo, raw_colors, gradient_offsets) =
535 encode_paint_data(&entry.paint_data, bw, bh);
536 let colors: [[f32; 4]; 4] = std::array::from_fn(|i| {
539 let mut c = srgb_to_linear_rgba(raw_colors[i]);
540 c[3] *= opacity;
541 c
542 });
543
544 let positions = [
545 apply_affine([sx, sy], transform),
546 apply_affine([sx + sw, sy], transform),
547 apply_affine([sx + sw, sy + sh], transform),
548 apply_affine([sx, sy + sh], transform),
549 ];
550 let tex_coords = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];
551 let local_uvs: [[f32; 2]; 4] = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
552
553 std::array::from_fn(|i| PathGradientVertex {
554 position: positions[i],
555 tex_coord: tex_coords[i],
556 local_uv: local_uvs[i],
557 paint_type,
558 _pad: 0,
559 gradient_geo,
560 gradient_color0: colors[0],
561 gradient_color1: colors[1],
562 gradient_color2: colors[2],
563 gradient_color3: colors[3],
564 gradient_offsets,
565 })
566 }
567}
568
569pub const QUAD_INDICES: [u32; 6] = [0, 1, 2, 0, 2, 3];
577
578pub fn generate_quad_indices(count: usize) -> Vec<u32> {
580 let mut indices = Vec::with_capacity(count * 6);
581 for i in 0..count {
582 let base = (i * 4) as u32;
583 for &offset in &QUAD_INDICES {
584 indices.push(base + offset);
585 }
586 }
587 indices
588}
589
590#[repr(C)]
592#[derive(Debug, Clone, Copy, Pod, Zeroable)]
593pub struct ShadowVertex {
594 pub position: [f32; 2],
595 pub local_uv: [f32; 2],
597 pub shadow_color: [f32; 4],
598 pub corner_radii: [f32; 4],
599 pub shadow_params: [f32; 4],
601 pub shape_offset: [f32; 4],
603}
604
605impl ShadowVertex {
606 pub fn from_shadow_quad(shadow: &ShadowQuad, scale_factor: f32) -> [ShadowVertex; 4] {
608 let [x, y, w, h] = shadow.screen;
609 let sx = x * scale_factor;
610 let sy = y * scale_factor;
611 let sw = w * scale_factor;
612 let sh = h * scale_factor;
613
614 let [sr_x, sr_y, sr_w, sr_h] = shadow.shape_rect;
615 let shape_w = sr_w * scale_factor;
616 let shape_h = sr_h * scale_factor;
617
618 let shadow_cx = sx + sw * 0.5;
620 let shadow_cy = sy + sh * 0.5;
621 let shape_cx = (sr_x + sr_w * 0.5) * scale_factor;
622 let shape_cy = (sr_y + sr_h * 0.5) * scale_factor;
623 let offset_x = shape_cx - shadow_cx;
624 let offset_y = shape_cy - shadow_cy;
625
626 let params = [
627 shape_w,
628 shape_h,
629 shadow.blur_radius * scale_factor,
630 shadow.spread * scale_factor,
631 ];
632 let offset = [offset_x, offset_y, 0.0, 0.0];
633 let scaled_corner_radii = [
638 shadow.corner_radii[0] * scale_factor,
639 shadow.corner_radii[1] * scale_factor,
640 shadow.corner_radii[2] * scale_factor,
641 shadow.corner_radii[3] * scale_factor,
642 ];
643
644 [
645 ShadowVertex {
646 position: [sx, sy],
647 local_uv: [0.0, 0.0],
648 shadow_color: srgb_to_linear_rgba(shadow.color),
649 corner_radii: scaled_corner_radii,
650 shadow_params: params,
651 shape_offset: offset,
652 },
653 ShadowVertex {
654 position: [sx + sw, sy],
655 local_uv: [1.0, 0.0],
656 shadow_color: srgb_to_linear_rgba(shadow.color),
657 corner_radii: scaled_corner_radii,
658 shadow_params: params,
659 shape_offset: offset,
660 },
661 ShadowVertex {
662 position: [sx + sw, sy + sh],
663 local_uv: [1.0, 1.0],
664 shadow_color: srgb_to_linear_rgba(shadow.color),
665 corner_radii: scaled_corner_radii,
666 shadow_params: params,
667 shape_offset: offset,
668 },
669 ShadowVertex {
670 position: [sx, sy + sh],
671 local_uv: [0.0, 1.0],
672 shadow_color: srgb_to_linear_rgba(shadow.color),
673 corner_radii: scaled_corner_radii,
674 shadow_params: params,
675 shape_offset: offset,
676 },
677 ]
678 }
679}
680
681#[repr(C)]
689#[derive(Debug, Clone, Copy, Pod, Zeroable)]
690pub struct AnimQuadVertex {
691 pub position: [f32; 2],
693 pub uv: [f32; 2],
698 pub slot: u32,
702 pub _pad: u32,
705}
706
707impl AnimQuadVertex {
708 pub fn from_animated_quad(
709 draw: &teksilo_canvas::AnimatedQuadDraw,
710 scale_factor: f32,
711 ) -> [AnimQuadVertex; 4] {
712 let [x, y, w, h] = draw.screen;
713 let sx = x * scale_factor;
714 let sy = y * scale_factor;
715 let sw = w * scale_factor;
716 let sh = h * scale_factor;
717 [
718 AnimQuadVertex {
719 position: [sx, sy],
720 uv: [0.0, 0.0],
721 slot: draw.slot,
722 _pad: 0,
723 },
724 AnimQuadVertex {
725 position: [sx + sw, sy],
726 uv: [1.0, 0.0],
727 slot: draw.slot,
728 _pad: 0,
729 },
730 AnimQuadVertex {
731 position: [sx + sw, sy + sh],
732 uv: [1.0, 1.0],
733 slot: draw.slot,
734 _pad: 0,
735 },
736 AnimQuadVertex {
737 position: [sx, sy + sh],
738 uv: [0.0, 1.0],
739 slot: draw.slot,
740 _pad: 0,
741 },
742 ]
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749 use teksilo_canvas::{DecorationKind, GradientStop, PaintData, ShapeKind, StrokeSpace};
750 use teksilo_tokens::Color;
751
752 fn glyph(screen: [f32; 4], atlas: [f32; 4], is_color: bool) -> GlyphQuad {
754 GlyphQuad {
755 screen,
756 atlas,
757 color: [1.0, 1.0, 1.0, 1.0],
758 is_color,
759 }
760 }
761
762 fn assert_pos_near(actual: [f32; 2], expected: [f32; 2]) {
763 assert!(
764 (actual[0] - expected[0]).abs() < 1e-3 && (actual[1] - expected[1]).abs() < 1e-3,
765 "position {actual:?} != expected {expected:?}"
766 );
767 }
768
769 #[test]
770 fn glyph_quad_to_vertices() {
771 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 64.0, 64.0], false);
774 let verts =
775 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
776 assert_eq!(verts.len(), 4);
777 assert_eq!(verts[0].position, [10.0, 20.0]);
778 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]);
782 assert_eq!(verts[2].tex_coord, [0.25, 0.25]);
783 }
784
785 #[test]
786 fn scale_factor_applied_to_glyph_coords() {
787 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 128.0, 128.0], false);
789 let verts =
790 QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &Transform2D::IDENTITY);
791 assert_eq!(verts[0].position, [20.0, 40.0]);
792 assert_eq!(verts[1].position, [80.0, 40.0]);
793 }
794
795 #[test]
796 fn glyph_snap_identity_fractional_origin() {
797 let quad = glyph([10.3, 20.7, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
801 let verts =
802 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
803 assert_eq!(verts[0].position, [10.0, 21.0]);
804 assert_eq!(verts[1].position, [40.0, 21.0]);
805 assert_eq!(verts[2].position, [40.0, 61.0]);
806 assert_eq!(verts[3].position, [10.0, 61.0]);
807 }
808
809 #[test]
810 fn glyph_snap_hidpi_scale_factor() {
811 let quad = glyph([5.7, 8.3, 16.0, 16.0], [0.0, 0.0, 32.0, 32.0], false);
815 let verts =
816 QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &Transform2D::IDENTITY);
817 assert_eq!(verts[0].position, [11.0, 17.0]);
818 assert_eq!(verts[2].position, [43.0, 49.0]);
819 }
820
821 #[test]
822 fn glyph_snap_fractional_dpi() {
823 let quad = glyph([4.2, 7.8, 20.0, 20.0], [0.0, 0.0, 25.0, 25.0], false);
827 let verts =
828 QuadVertex::from_glyph_quad_transformed(&quad, 1.25, 256, 256, &Transform2D::IDENTITY);
829 assert_eq!(verts[0].position, [5.0, 10.0]);
830 assert_eq!(verts[2].position, [30.0, 35.0]);
831 }
832
833 #[test]
834 fn glyph_snap_exact_bucket_zoom() {
835 let quad = glyph([4.0, 8.0, 20.0, 20.0], [0.0, 0.0, 50.0, 50.0], false);
841 let zoom = Transform2D {
842 m: [1.25, 0.0, 0.0, 1.25, 3.3, 7.8],
843 };
844 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &zoom);
845 assert_eq!(verts[0].position, [13.0, 28.0]);
847 assert_eq!(verts[2].position, [63.0, 78.0]);
848 }
849
850 #[test]
851 fn glyph_no_snap_mid_bucket_residual() {
852 let quad = glyph([4.0, 8.0, 20.0, 20.0], [0.0, 0.0, 50.0, 50.0], false);
856 let zoom = Transform2D {
857 m: [1.1, 0.0, 0.0, 1.1, 3.3, 7.8],
858 };
859 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 2.0, 256, 256, &zoom);
860 assert_pos_near(verts[0].position, [1.1 * 8.0 + 3.3, 1.1 * 16.0 + 7.8]);
861 assert_pos_near(verts[2].position, [1.1 * 48.0 + 3.3, 1.1 * 56.0 + 7.8]);
862 }
863
864 #[test]
865 fn glyph_no_snap_rotation() {
866 let quad = glyph([10.0, 20.0, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
868 let (s, c) = (0.1_f32.sin(), 0.1_f32.cos());
869 let rot = Transform2D {
870 m: [c, s, -s, c, 0.0, 0.0],
871 };
872 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &rot);
873 assert_pos_near(
874 verts[0].position,
875 [c * 10.0 - s * 20.0, s * 10.0 + c * 20.0],
876 );
877 assert_pos_near(
878 verts[2].position,
879 [c * 40.0 - s * 60.0, s * 40.0 + c * 60.0],
880 );
881 }
882
883 #[test]
884 fn glyph_snap_translation_only_transform() {
885 let quad = glyph([10.3, 20.0, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], false);
888 let pan = Transform2D {
889 m: [1.0, 0.0, 0.0, 1.0, 5.7, 3.2],
890 };
891 let verts = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &pan);
892 assert_eq!(verts[0].position, [16.0, 23.0]);
894 assert_eq!(verts[2].position, [46.0, 63.0]);
895 }
896
897 #[test]
898 fn glyph_snap_color_emoji_flag_preserved() {
899 let quad = glyph([10.3, 20.7, 30.0, 40.0], [0.0, 0.0, 30.0, 40.0], true);
900 let verts =
901 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
902 assert_eq!(verts[0].position, [10.0, 21.0]);
903 for v in &verts {
904 assert_eq!(v.flags, QUAD_FLAG_COLOR_GLYPH);
905 }
906 }
907
908 #[test]
909 fn glyph_uvs_independent_of_snapping() {
910 let quad = glyph([10.3, 20.7, 30.0, 40.0], [16.0, 32.0, 30.0, 40.0], false);
913 let snapped =
914 QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &Transform2D::IDENTITY);
915 let residual = Transform2D {
916 m: [1.1, 0.0, 0.0, 1.1, 0.0, 0.0],
917 };
918 let unsnapped = QuadVertex::from_glyph_quad_transformed(&quad, 1.0, 256, 256, &residual);
919 for (a, b) in snapped.iter().zip(unsnapped.iter()) {
920 assert_eq!(a.tex_coord, b.tex_coord);
921 }
922 assert_eq!(snapped[0].tex_coord, [16.0 / 256.0, 32.0 / 256.0]);
923 assert_eq!(snapped[2].tex_coord, [46.0 / 256.0, 72.0 / 256.0]);
924 }
925
926 #[test]
927 fn decoration_rect_to_vertices() {
928 let rect = DecorationRect {
929 rect: [0.0, 0.0, 100.0, 2.0],
930 color: [1.0, 0.0, 0.0, 1.0],
931 kind: DecorationKind::FocusRing,
932 };
933 let verts = RectVertex::from_decoration(&rect, 1.0);
934 assert_eq!(verts.len(), 4);
935 assert_eq!(verts[0].position, [0.0, 0.0]);
936 assert_eq!(verts[2].position, [100.0, 2.0]);
937 }
938
939 #[test]
940 fn shape_quad_to_sdf_vertices() {
941 let shape = ShapeQuad {
942 screen: [0.0, 0.0, 100.0, 40.0],
943 color: [0.0, 0.5, 0.0, 1.0],
944 shape: ShapeKind::RoundedRect,
945 stroke_width: 0.0,
946 stroke_space: StrokeSpace::Logical,
947 corner_radii: [6.0, 6.0, 6.0, 6.0],
948 paint_data: PaintData::Solid,
949 };
950 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
951 assert_eq!(verts.len(), 4);
952 assert_eq!(verts[0].corner_radii, [6.0, 6.0, 6.0, 6.0]);
953 assert_eq!(verts[0].position, [-1.0, -1.0]);
956 assert_eq!(verts[2].position, [101.0, 41.0]);
957 assert!((verts[0].local_uv[0] - (-0.01)).abs() < 1e-5);
958 assert!((verts[0].local_uv[1] - (-0.025)).abs() < 1e-5);
959 assert!((verts[2].local_uv[0] - 1.01).abs() < 1e-5);
960 assert!((verts[2].local_uv[1] - 1.025).abs() < 1e-5);
961 }
962
963 #[test]
964 fn sdf_scale_factor() {
965 let shape = ShapeQuad {
966 screen: [10.0, 10.0, 100.0, 40.0],
967 color: [0.0, 0.0, 0.0, 1.0],
968 shape: ShapeKind::RoundedRect,
969 stroke_width: 2.0,
970 stroke_space: StrokeSpace::Logical,
971 corner_radii: [4.0; 4],
972 paint_data: PaintData::Solid,
973 };
974 let verts = SdfVertex::from_shape_quad(&shape, 2.0);
975 assert_eq!(verts[0].position, [17.0, 17.0]);
978 assert_eq!(verts[0].shape_params[2], 4.0); assert_eq!(verts[0].corner_radii, [8.0; 4]);
983 }
984
985 #[test]
986 fn sdf_circle_stays_circle_on_hidpi() {
987 let shape = ShapeQuad {
993 screen: [0.0, 0.0, 19.0, 19.0],
994 color: [0.0, 0.0, 0.0, 1.0],
995 shape: ShapeKind::RoundedRect,
996 stroke_width: 0.0,
997 stroke_space: StrokeSpace::Logical,
998 corner_radii: [9.5; 4],
999 paint_data: PaintData::Solid,
1000 };
1001 let verts = SdfVertex::from_shape_quad(&shape, 2.0);
1002 assert_eq!(verts[0].shape_params[0], 38.0);
1003 assert_eq!(verts[0].shape_params[1], 38.0);
1004 assert_eq!(verts[0].corner_radii, [19.0; 4]);
1005 }
1006
1007 #[test]
1008 fn cosmetic_shape_stroke_param_is_inverse_zoom() {
1009 let shape = ShapeQuad {
1014 screen: [0.0, 0.0, 100.0, 100.0],
1015 color: [0.0, 0.0, 0.0, 1.0],
1016 shape: ShapeKind::RoundedRect,
1017 stroke_width: 2.0,
1018 stroke_space: StrokeSpace::Device,
1019 corner_radii: [10.0; 4],
1020 paint_data: PaintData::Solid,
1021 };
1022 let sf = 2.0;
1023 let logical = SdfVertex::from_shape_quad(&shape, sf);
1024 let z1 = SdfVertex::from_shape_quad_cosmetic(&shape, sf, 1.0);
1025 let z2 = SdfVertex::from_shape_quad_cosmetic(&shape, sf, 2.0);
1026 assert!((z1[0].shape_params[2] - logical[0].shape_params[2]).abs() < 1e-4);
1028 assert!((z1[0].shape_params[2] - 4.0).abs() < 1e-4);
1029 assert!((z2[0].shape_params[2] - 2.0).abs() < 1e-4);
1031 assert_eq!(z1[0].shape_params[0], z2[0].shape_params[0]);
1034 assert_eq!(z2[0].shape_params[0], 200.0);
1035 }
1036
1037 #[test]
1038 fn sdf_linear_gradient_encoding() {
1039 let shape = ShapeQuad {
1040 screen: [0.0, 0.0, 100.0, 50.0],
1041 color: [1.0, 1.0, 1.0, 1.0],
1042 shape: ShapeKind::RoundedRect,
1043 stroke_width: 0.0,
1044 stroke_space: StrokeSpace::Logical,
1045 corner_radii: [0.0; 4],
1046 paint_data: PaintData::LinearGradient {
1047 start: [0.0, 0.0],
1048 end: [100.0, 0.0],
1049 stops: vec![
1050 GradientStop {
1051 offset: 0.0,
1052 color: Color::RED,
1053 },
1054 GradientStop {
1055 offset: 1.0,
1056 color: Color::BLUE,
1057 },
1058 ],
1059 },
1060 };
1061 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
1062 assert!((verts[0].shape_params[3] - 1.0).abs() < 0.01);
1064 assert!((verts[0].gradient_geo[0]).abs() < 0.01);
1066 assert!((verts[0].gradient_geo[2] - 1.0).abs() < 0.01);
1067 assert!((verts[0].gradient_color0[0] - 1.0).abs() < 0.01);
1069 assert!((verts[0].gradient_offsets[0]).abs() < 0.01);
1071 assert!((verts[0].gradient_offsets[1] - 1.0).abs() < 0.01);
1072 }
1073
1074 #[test]
1075 fn linear_gradient_endpoints_are_rect_local_not_absolute() {
1076 let shape = ShapeQuad {
1085 screen: [50.0, 100.0, 200.0, 200.0],
1086 color: [1.0, 1.0, 1.0, 1.0],
1087 shape: ShapeKind::RoundedRect,
1088 stroke_width: 0.0,
1089 stroke_space: StrokeSpace::Logical,
1090 corner_radii: [0.0; 4],
1091 paint_data: PaintData::LinearGradient {
1092 start: [0.0, 0.0],
1093 end: [0.0, 200.0],
1094 stops: vec![
1095 GradientStop {
1096 offset: 0.0,
1097 color: Color::new(0.0, 0.0, 0.0, 0.0),
1098 },
1099 GradientStop {
1100 offset: 1.0,
1101 color: Color::BLACK,
1102 },
1103 ],
1104 },
1105 };
1106 let verts = SdfVertex::from_shape_quad(&shape, 1.0);
1107 assert!((verts[0].gradient_geo[0]).abs() < 1e-5, "start_uv.x");
1108 assert!((verts[0].gradient_geo[1]).abs() < 1e-5, "start_uv.y");
1109 assert!((verts[0].gradient_geo[2]).abs() < 1e-5, "end_uv.x");
1110 assert!((verts[0].gradient_geo[3] - 1.0).abs() < 1e-5, "end_uv.y");
1111 }
1112
1113 fn rasterize_for_test(entry: &PathEntry, atlas_size: u32) -> crate::path_atlas::AtlasRegion {
1119 let mut atlas = crate::path_atlas::PathAtlas::new(atlas_size, atlas_size);
1120 atlas.begin_frame();
1121 atlas
1122 .lookup_or_rasterize(
1123 &entry.path,
1124 &entry.stroke_style,
1125 entry.fill_rule,
1126 entry.bounds,
1127 1.0,
1128 1.0,
1129 )
1130 .expect("test path rasterizes")
1131 }
1132
1133 fn gradient_path_entry(bounds_rect: teksilo_canvas::Rect, paint_data: PaintData) -> PathEntry {
1134 use teksilo_canvas::{FillRule, StrokeStyle};
1135 PathEntry {
1136 path: teksilo_canvas::Path::rect(bounds_rect),
1137 color: [1.0, 1.0, 1.0, 1.0],
1138 stroke_style: StrokeStyle::solid(0.0),
1139 fill_rule: FillRule::Winding,
1140 bounds: bounds_rect.to_array(),
1141 paint_data,
1142 }
1143 }
1144
1145 #[test]
1146 fn path_gradient_linear_encoding() {
1147 let bounds_rect = teksilo_canvas::Rect::new(0.0, 0.0, 100.0, 50.0);
1148 let entry = gradient_path_entry(
1149 bounds_rect,
1150 PaintData::LinearGradient {
1151 start: [0.0, 0.0],
1152 end: [100.0, 0.0],
1153 stops: vec![
1154 GradientStop {
1155 offset: 0.0,
1156 color: Color::RED,
1157 },
1158 GradientStop {
1159 offset: 1.0,
1160 color: Color::BLUE,
1161 },
1162 ],
1163 },
1164 );
1165 let region = rasterize_for_test(&entry, 256);
1166
1167 let verts = PathGradientVertex::from_path_entry(
1168 &entry,
1169 ®ion,
1170 1.0,
1171 256,
1172 256,
1173 1.0,
1174 &Transform2D::IDENTITY,
1175 );
1176
1177 assert_eq!(verts[0].paint_type, 1);
1179 assert!((verts[0].gradient_geo[0]).abs() < 0.01);
1181 assert!((verts[0].gradient_geo[2] - 1.0).abs() < 0.01);
1182 assert!((verts[0].gradient_color0[0] - 1.0).abs() < 0.01);
1184 assert!((verts[0].gradient_color0[1]).abs() < 0.01);
1185 assert!((verts[0].gradient_offsets[0]).abs() < 0.01);
1187 assert!((verts[0].gradient_offsets[1] - 1.0).abs() < 0.01);
1188 assert_eq!(verts[0].local_uv, [0.0, 0.0]);
1190 assert_eq!(verts[1].local_uv, [1.0, 0.0]);
1191 assert_eq!(verts[2].local_uv, [1.0, 1.0]);
1192 assert_eq!(verts[3].local_uv, [0.0, 1.0]);
1193 }
1194
1195 #[test]
1196 fn path_gradient_endpoints_are_rect_local_not_absolute() {
1197 let bounds_rect = teksilo_canvas::Rect::new(50.0, 100.0, 200.0, 200.0);
1203 let entry = gradient_path_entry(
1204 bounds_rect,
1205 PaintData::LinearGradient {
1206 start: [0.0, 0.0],
1207 end: [0.0, 200.0],
1208 stops: vec![
1209 GradientStop {
1210 offset: 0.0,
1211 color: Color::new(0.0, 0.0, 0.0, 0.0),
1212 },
1213 GradientStop {
1214 offset: 1.0,
1215 color: Color::BLACK,
1216 },
1217 ],
1218 },
1219 );
1220 let region = rasterize_for_test(&entry, 512);
1221
1222 let verts = PathGradientVertex::from_path_entry(
1223 &entry,
1224 ®ion,
1225 1.0,
1226 512,
1227 512,
1228 1.0,
1229 &Transform2D::IDENTITY,
1230 );
1231 assert!((verts[0].gradient_geo[0]).abs() < 1e-5, "start_uv.x");
1232 assert!((verts[0].gradient_geo[1]).abs() < 1e-5, "start_uv.y");
1233 assert!((verts[0].gradient_geo[2]).abs() < 1e-5, "end_uv.x");
1234 assert!((verts[0].gradient_geo[3] - 1.0).abs() < 1e-5, "end_uv.y");
1235 }
1236
1237 #[test]
1238 fn path_gradient_opacity_folds_into_every_stop_alpha() {
1239 let bounds_rect = teksilo_canvas::Rect::new(0.0, 0.0, 40.0, 20.0);
1245 let entry = gradient_path_entry(
1246 bounds_rect,
1247 PaintData::LinearGradient {
1248 start: [0.0, 0.0],
1249 end: [40.0, 0.0],
1250 stops: vec![
1251 GradientStop {
1252 offset: 0.0,
1253 color: Color::RED,
1254 },
1255 GradientStop {
1256 offset: 1.0,
1257 color: Color::BLUE,
1258 },
1259 ],
1260 },
1261 );
1262 let region = rasterize_for_test(&entry, 128);
1263
1264 let full = PathGradientVertex::from_path_entry(
1265 &entry,
1266 ®ion,
1267 1.0,
1268 128,
1269 128,
1270 1.0,
1271 &Transform2D::IDENTITY,
1272 );
1273 let half = PathGradientVertex::from_path_entry(
1274 &entry,
1275 ®ion,
1276 1.0,
1277 128,
1278 128,
1279 0.5,
1280 &Transform2D::IDENTITY,
1281 );
1282
1283 assert!((full[0].gradient_color0[3] - 1.0).abs() < 1e-5);
1284 assert!((half[0].gradient_color0[3] - 0.5).abs() < 1e-5);
1285 assert!((half[0].gradient_color1[3] - 0.5).abs() < 1e-5);
1286 }
1287
1288 #[test]
1289 fn generate_indices_for_multiple_quads() {
1290 let indices = generate_quad_indices(2);
1291 assert_eq!(indices.len(), 12);
1292 assert_eq!(&indices[0..6], &[0, 1, 2, 0, 2, 3]);
1293 assert_eq!(&indices[6..12], &[4, 5, 6, 4, 6, 7]);
1294 }
1295
1296 #[test]
1297 fn shadow_quad_to_vertices() {
1298 let shadow = ShadowQuad {
1299 screen: [0.0, 0.0, 120.0, 60.0],
1300 color: [0.0, 0.0, 0.0, 0.3],
1301 corner_radii: [6.0; 4],
1302 shape_rect: [10.0, 8.0, 100.0, 40.0],
1303 blur_radius: 4.0,
1304 spread: 0.0,
1305 };
1306 let verts = ShadowVertex::from_shadow_quad(&shadow, 1.0);
1307 assert_eq!(verts.len(), 4);
1308 assert_eq!(verts[0].position, [0.0, 0.0]);
1309 assert_eq!(verts[2].position, [120.0, 60.0]);
1310 assert_eq!(verts[0].shadow_params[2], 4.0); assert_eq!(verts[0].corner_radii, [6.0; 4]);
1312 }
1313
1314 #[test]
1315 fn shadow_scale_factor() {
1316 let shadow = ShadowQuad {
1317 screen: [10.0, 10.0, 120.0, 60.0],
1318 color: [0.0, 0.0, 0.0, 0.3],
1319 corner_radii: [6.0; 4],
1320 shape_rect: [20.0, 18.0, 100.0, 40.0],
1321 blur_radius: 4.0,
1322 spread: 2.0,
1323 };
1324 let verts = ShadowVertex::from_shadow_quad(&shadow, 2.0);
1325 assert_eq!(verts[0].position, [20.0, 20.0]);
1326 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]);
1332 }
1333}