1use crate::color::Color;
18use crate::display::{ClipId, Quad, QuadKind};
19use crate::geom::{Rect, Vec2};
20use crate::spec::UnderlineStyle;
21
22#[allow(clippy::too_many_arguments)]
26pub(crate) fn push_line(
27 out: &mut Vec<Quad>,
28 style: UnderlineStyle,
29 x: f32,
30 y: f32,
31 w: f32,
32 stroke: f32,
33 color: Color,
34 clip: ClipId,
35) {
36 if w <= 0.0 {
37 return;
38 }
39 let stroke = stroke.max(1.0);
40 match style {
41 UnderlineStyle::Solid => out.push(Quad {
42 rect: Rect::new(x, y, w, stroke),
43 color,
44 border_color: Color::TRANSPARENT,
45 radius: crate::display::SQUARE,
46 border_w: 0.0,
47 blur: 0.0,
48 kind: QuadKind::Solid,
49 clip,
50 uv: [0; 4],
51 }),
52 UnderlineStyle::Wavy => {
53 let cy = y + stroke * 0.5;
58 let amp = stroke;
59 let half = stroke * 3.0;
60 let mut px = x;
61 let mut py = cy + amp;
62 let mut up = true;
63 let end = x + w;
64 while px < end {
65 let nx = (px + half).min(end);
66 let ny = if up { cy - amp } else { cy + amp };
67 let ny = if nx < px + half {
68 py + (ny - py) * ((nx - px) / half)
69 } else {
70 ny
71 };
72 push_piece(
73 out,
74 Vec2::new(px, py),
75 Vec2::new(nx, ny),
76 stroke,
77 color,
78 clip,
79 );
80 px = nx;
81 py = ny;
82 up = !up;
83 }
84 }
85 UnderlineStyle::Dotted => {
86 let cy = y + stroke * 0.5;
88 let step = stroke * 4.0;
89 let mut px = x + stroke;
90 while px + stroke <= x + w + 0.5 {
91 let p = Vec2::new(px, cy);
92 push_piece(out, p, p, stroke * 2.0, color, clip);
93 px += step;
94 }
95 }
96 }
97}
98
99fn push_piece(out: &mut Vec<Quad>, a: Vec2, b: Vec2, width: f32, color: Color, clip: ClipId) {
103 let pad = width * 0.5 + 2.0;
104 let (x0, x1) = (a.x.min(b.x) - pad, a.x.max(b.x) + pad);
105 let (y0, y1) = (a.y.min(b.y) - pad, a.y.max(b.y) + pad);
106 out.push(Quad {
107 rect: Rect::new(x0, y0, x1 - x0, y1 - y0),
108 color,
109 border_color: Color::TRANSPARENT,
110 radius: crate::display::SQUARE,
111 border_w: width,
112 blur: 0.0,
113 kind: QuadKind::Segment,
114 clip,
115 uv: Quad::segment_uv([a.x, a.y, b.x, b.y]),
116 });
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 fn quads(style: UnderlineStyle, w: f32) -> Vec<Quad> {
124 let mut out = Vec::new();
125 push_line(&mut out, style, 10.0, 100.0, w, 1.0, Color::WHITE, 0);
126 out
127 }
128
129 #[test]
130 fn a_solid_line_is_the_rect_it_always_was() {
131 let q = quads(UnderlineStyle::Solid, 50.0);
132 assert_eq!(q.len(), 1);
133 assert_eq!(q[0].kind, QuadKind::Solid);
134 assert_eq!(q[0].rect, Rect::new(10.0, 100.0, 50.0, 1.0));
135 }
136
137 #[test]
138 fn a_wave_is_pieces_that_meet_and_end_at_the_edge() {
139 let q = quads(UnderlineStyle::Wavy, 20.0);
140 assert_eq!(q.len(), 7);
143 assert!(q.iter().all(|q| q.kind == QuadKind::Segment));
144 let ends: Vec<[f32; 4]> = q.iter().map(Quad::segment_ends).collect();
145 for pair in ends.windows(2) {
146 assert_eq!(
147 (pair[0][2], pair[0][3]),
148 (pair[1][0], pair[1][1]),
149 "pieces meet"
150 );
151 }
152 assert_eq!(ends[0][0], 10.0);
153 assert_eq!(ends[6][2], 30.0, "the last piece ends at the run's edge");
154 assert_eq!(ends[0][1], 101.5);
157 assert_eq!(ends[0][3], 99.5);
158 assert_eq!(ends[1][3], 101.5);
159 }
160
161 #[test]
162 fn dots_are_zero_length_pieces_four_strokes_apart() {
163 let q = quads(UnderlineStyle::Dotted, 20.0);
164 assert_eq!(q.len(), 5);
165 for (i, q) in q.iter().enumerate() {
166 let [x0, y0, x1, y1] = q.segment_ends();
167 assert_eq!((x0, y0), (x1, y1), "a dot");
168 assert_eq!(x0, 11.0 + 4.0 * i as f32);
169 assert_eq!(q.border_w, 2.0);
170 }
171 }
172
173 #[test]
174 fn nothing_for_an_empty_run() {
175 assert!(quads(UnderlineStyle::Wavy, 0.0).is_empty());
176 assert!(quads(UnderlineStyle::Dotted, 0.0).is_empty());
177 }
178}