1use super::icons::Icons;
2use super::sdf::{self, Field};
3use super::{
4 BLUR, DASHED, Dash, Draw, FILL_SOLID, FILL_VERTICAL, Fill, Fit, Instance, KIND_ARC,
5 KIND_CIRCLE, KIND_RECT, KIND_RING, Material, Matrix, Shadow, Shape, Srgba, Stroke, pack_plain,
6};
7
8pub const BEVEL: u32 = 1 << 24;
9
10pub const STYLE_CALL: &str =
11 " fill = flat_bevel_over(v, kind, p, field, inverse, fill, fill_cover);\n";
12
13pub const STYLE_WGSL: &str = r#"
14const FLAT_BEVEL: u32 = 16777216u;
15
16fn flat_bevel_light(v: FlatVaryings, kind: u32, p: vec2f, inverse: mat2x2f) -> f32 {
17 let toward = v.material.yz;
18 if (kind == FLAT_RECT) {
19 let s = vec2f(flat_sign(p.x), flat_sign(p.y));
20 let q = abs(p) - max(v.shape.xy - vec2f(flat_corner(p, v.corners)), vec2f(0.0));
21 if (q.x > 0.0 && q.y > 0.0) {
22 return flat_coverage(vec3f(-dot(s * q, toward), -toward), inverse);
23 }
24 let x_lit = select(0.0, 1.0, s.x * toward.x > 0.0);
25 let y_lit = select(0.0, 1.0, s.y * toward.y > 0.0);
26 let x_side = flat_coverage(vec3f(q.y - q.x, -s.x, s.y), inverse);
27 return mix(y_lit, x_lit, x_side);
28 }
29 var side = 1.0;
30 if (kind == FLAT_RING || kind == FLAT_ARC) {
31 side = flat_sign(length(p) - v.shape.x);
32 }
33 return flat_coverage(vec3f(-side * dot(p, toward), -side * toward), inverse);
34}
35
36fn flat_bevel_over(v: FlatVaryings, kind: u32, p: vec2f, field: vec3f, inverse: mat2x2f, fill: vec4f, fill_cover: f32) -> vec4f {
37 if ((v.info.x & FLAT_BEVEL) == 0u) {
38 return fill;
39 }
40 let inner = flat_coverage(vec3f(field.x + v.material.x, field.yz), inverse);
41 let band = max(fill_cover - inner, 0.0);
42 let colour = mix(v.frame_y, v.frame_x, flat_bevel_light(v, kind, p, inverse));
43 let paint = vec4f(colour.rgb * colour.a, colour.a);
44 let share = select(0.0, band / fill_cover, fill_cover > 0.0);
45 return paint * band + fill * (1.0 - paint.a * share);
46}
47"#;
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub enum Style {
51 Outline(Outline),
52 Bevel(Bevel),
53 Flat,
54}
55
56impl Style {
57 pub fn outline(width: f32) -> Self {
58 Self::Outline(Outline::solid(width))
59 }
60
61 pub fn ghost(width: f32, on: f32, off: f32, alpha: f32) -> Self {
62 Self::Outline(Outline::ghost(width, on, off, alpha))
63 }
64
65 pub fn bevel(width: f32, light: Srgba, dark: Srgba) -> Self {
66 Self::Bevel(Bevel::new(width, light, dark))
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq)]
71pub struct Outline {
72 pub width: f32,
73 pub dash: Option<Dash>,
74 pub colour: Option<Srgba>,
75 pub alpha: f32,
76}
77
78impl Outline {
79 pub fn solid(width: f32) -> Self {
80 Self {
81 width,
82 dash: None,
83 colour: None,
84 alpha: 1.0,
85 }
86 }
87
88 pub fn dashed(width: f32, on: f32, off: f32) -> Self {
89 Self {
90 dash: Some(Dash {
91 on,
92 off,
93 phase: 0.0,
94 }),
95 ..Self::solid(width)
96 }
97 }
98
99 pub fn ghost(width: f32, on: f32, off: f32, alpha: f32) -> Self {
100 Self {
101 alpha,
102 ..Self::dashed(width, on, off)
103 }
104 }
105
106 pub fn colour(mut self, colour: Srgba) -> Self {
107 self.colour = Some(colour);
108 self
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct Bevel {
114 pub width: f32,
115 pub light: Srgba,
116 pub dark: Srgba,
117 pub toward: [f32; 2],
118}
119
120impl Bevel {
121 pub fn new(width: f32, light: Srgba, dark: Srgba) -> Self {
122 Self {
123 width,
124 light,
125 dark,
126 toward: [-1.0, -1.0],
127 }
128 }
129
130 pub fn toward(mut self, toward: [f32; 2]) -> Self {
131 self.toward = toward;
132 self
133 }
134}
135
136pub fn restyle(draws: &mut [Draw], style: Option<Style>) {
137 for draw in draws {
138 draw.style = style;
139 }
140}
141
142pub fn restyle_group(draws: &mut [Draw], group: u16, style: Option<Style>) {
143 for draw in draws.iter_mut().filter(|draw| draw.group == Some(group)) {
144 draw.style = style;
145 }
146}
147
148fn axes(transform: &Matrix) -> [f32; 4] {
149 [
150 transform[0][0],
151 transform[0][1],
152 transform[1][0],
153 transform[1][1],
154 ]
155}
156
157fn mean_scale(transform: &Matrix) -> f32 {
158 let [a, b, c, d] = axes(transform);
159 (a * d - c * b).abs().sqrt()
160}
161
162fn paint_colour(draw: &Draw) -> Option<Srgba> {
163 draw.stroke
164 .filter(|stroke| stroke.width > 0.0)
165 .map(|stroke| stroke.colour)
166 .or(match draw.fill {
167 Fill::None => None,
168 Fill::Solid(colour) => Some(colour),
169 Fill::Vertical(top, _) => Some(top),
170 Fill::Gradient(gradient) => gradient.stops().next().map(|(_, colour)| Srgba(colour)),
171 Fill::Image(image) => Some(image.tint),
172 })
173}
174
175pub fn resolve(draw: &Draw) -> Draw {
176 let mut out = *draw;
177 out.style = None;
178 let Some(style) = draw.style else {
179 return out;
180 };
181 if matches!(draw.shape, Shape::Text(_)) {
182 return out;
183 }
184 out.shadow = match draw.shadow {
185 Shadow::Glow(glow) => Shadow::Glow(glow),
186 _ => Shadow::None,
187 };
188 out.material = Material::default();
189 if let Style::Outline(outline) = style {
190 out.fill = Fill::None;
191 out.pattern = None;
192 let scale = mean_scale(&draw.transform);
193 let colour = outline.colour.or_else(|| paint_colour(draw));
194 out.stroke = match colour {
195 Some(colour)
196 if scale > 0.0
197 && scale.is_finite()
198 && outline.width > 0.0
199 && outline.width.is_finite() =>
200 {
201 Some(Stroke {
202 colour: colour.alpha(colour.0[3] * outline.alpha.clamp(0.0, 1.0)),
203 width: outline.width / scale,
204 dash: outline.dash.map(|dash| Dash {
205 on: dash.on / scale,
206 off: dash.off / scale,
207 phase: dash.phase / scale,
208 }),
209 })
210 }
211 _ => None,
212 };
213 }
214 out
215}
216
217pub(super) fn pack(
218 draw: &Draw,
219 style: Style,
220 fit: &Fit,
221 icons: Option<&Icons>,
222) -> Option<Instance> {
223 let resolved = resolve(draw);
224 let mut instance = pack_plain(&resolved, fit, icons)?;
225 let kind = instance.kind();
226 match style {
227 Style::Outline(_) => {
228 if let Some(dash) = resolved.stroke.and_then(|stroke| stroke.dash)
229 && matches!(kind, KIND_RING | KIND_ARC)
230 && instance.stroke[0] > 0.0
231 && dash.on > 0.0
232 && dash.off > 0.0
233 {
234 instance.info[0] |= DASHED;
235 instance.stroke[1] = dash.on;
236 instance.stroke[2] = dash.off;
237 instance.stroke[3] = dash.phase;
238 }
239 }
240 Style::Bevel(bevel) => {
241 if instance.info[0] & (FILL_SOLID | FILL_VERTICAL) != 0
242 && instance.info[0] & BLUR == 0
243 && matches!(kind, KIND_RECT | KIND_CIRCLE | KIND_RING | KIND_ARC)
244 && bevel.width > 0.0
245 && bevel.width.is_finite()
246 {
247 let [a, b, c, d] = axes(&draw.transform);
248 let det = a * d - c * b;
249 let unit = det.abs().sqrt() * fit.scale;
250 let pixels = (bevel.width * fit.scale).round().max(1.0);
251 let toward = unit_or(bevel.toward, [0.0, -1.0]);
252 let local = unit_or(
253 [
254 (d * toward[0] - c * toward[1]) / det,
255 (-b * toward[0] + a * toward[1]) / det,
256 ],
257 [0.0, -1.0],
258 );
259 instance.material = [pixels / unit, local[0], local[1], 0.0];
260 instance.frame[0] = bevel.light.0;
261 instance.frame[1] = bevel.dark.0;
262 instance.info[0] |= BEVEL;
263 }
264 }
265 Style::Flat => {}
266 }
267 Some(instance)
268}
269
270fn unit_or(v: [f32; 2], fallback: [f32; 2]) -> [f32; 2] {
271 let length = (v[0] * v[0] + v[1] * v[1]).sqrt();
272 if length > 0.0 && length.is_finite() {
273 [v[0] / length, v[1] / length]
274 } else {
275 fallback
276 }
277}
278
279fn sign(value: f32) -> f32 {
280 if value >= 0.0 { 1.0 } else { -1.0 }
281}
282
283fn bevel_light(instance: &Instance, p: [f32; 2], inverse: [[f32; 2]; 2]) -> f32 {
284 let toward = [instance.material[1], instance.material[2]];
285 let shape = instance.shape;
286 match instance.kind() {
287 KIND_RECT => {
288 let s = [sign(p[0]), sign(p[1])];
289 let radius = sdf::corner(p, instance.corners);
290 let q = [
291 p[0].abs() - (shape[0] - radius).max(0.0),
292 p[1].abs() - (shape[1] - radius).max(0.0),
293 ];
294 if q[0] > 0.0 && q[1] > 0.0 {
295 let lean = s[0] * q[0] * toward[0] + s[1] * q[1] * toward[1];
296 return sdf::coverage([-lean, -toward[0], -toward[1]], inverse);
297 }
298 let x_lit = if s[0] * toward[0] > 0.0 { 1.0 } else { 0.0 };
299 let y_lit = if s[1] * toward[1] > 0.0 { 1.0 } else { 0.0 };
300 let x_side = sdf::coverage([q[1] - q[0], -s[0], s[1]], inverse);
301 y_lit + (x_lit - y_lit) * x_side
302 }
303 kind => {
304 let side = if matches!(kind, KIND_RING | KIND_ARC) {
305 sign((p[0] * p[0] + p[1] * p[1]).sqrt() - shape[0])
306 } else {
307 1.0
308 };
309 let lean = side * (p[0] * toward[0] + p[1] * toward[1]);
310 sdf::coverage([-lean, -side * toward[0], -side * toward[1]], inverse)
311 }
312 }
313}
314
315pub fn bevel_over(
316 instance: &Instance,
317 p: [f32; 2],
318 field: Field,
319 inverse: [[f32; 2]; 2],
320 fill: [f32; 4],
321 fill_cover: f32,
322) -> [f32; 4] {
323 if instance.info[0] & BEVEL == 0 {
324 return fill;
325 }
326 let inner = sdf::coverage(
327 [field[0] + instance.material[0], field[1], field[2]],
328 inverse,
329 );
330 let band = (fill_cover - inner).max(0.0);
331 let towards = bevel_light(instance, p, inverse);
332 let dark = instance.frame[1];
333 let light = instance.frame[0];
334 let colour: [f32; 4] = std::array::from_fn(|i| dark[i] + (light[i] - dark[i]) * towards);
335 let paint = [
336 colour[0] * colour[3],
337 colour[1] * colour[3],
338 colour[2] * colour[3],
339 colour[3],
340 ];
341 let share = if fill_cover > 0.0 {
342 band / fill_cover
343 } else {
344 0.0
345 };
346 std::array::from_fn(|i| paint[i] * band + fill[i] * (1.0 - paint[3] * share))
347}