1#[cfg(test)]
2mod corner_tests;
3pub mod cpu;
4pub mod cpu_text;
5#[cfg(test)]
6mod cpu_text_tests;
7pub mod effects;
8pub mod grid;
9pub mod icons;
10pub mod look;
11mod pass;
12mod roles;
13pub mod sdf;
14pub mod shader;
15pub mod shadow;
16#[cfg(test)]
17mod sprite_tests;
18pub mod sprites;
19pub mod style;
20#[cfg(test)]
21mod style_tests;
22pub mod svg;
23#[cfg(test)]
24mod tests;
25pub mod viewport;
26
27use crate::text::{GpuAtlas, RichQuad};
28use bytemuck::{Pod, Zeroable};
29pub use icons::{Icon, IconKind, IconShape, Icons};
30pub use pass::{
31 Environment, FlatIcons, FlatPass, FlatTarget, finish_entries, layer_entries, text_entries,
32 view_entries,
33};
34use pfx_gpu::window::{Letterbox, Size, letterbox_nearest};
35pub use pfx_post::look::{Gradient, GradientShape, Interpolation};
36use pfx_text::Paragraph;
37pub use roles::{Roles, TokenKey};
38pub use shadow::{CurvePoint, ShadowCurve};
39pub use sprites::{FlatSprites, SpriteFilter, SpriteImage};
40pub use style::{Bevel, Outline, Style};
41
42pub type Matrix = [[f32; 4]; 4];
43
44pub const IDENTITY: Matrix = [
45 [1.0, 0.0, 0.0, 0.0],
46 [0.0, 1.0, 0.0, 0.0],
47 [0.0, 0.0, 1.0, 0.0],
48 [0.0, 0.0, 0.0, 1.0],
49];
50
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct Srgba(pub [f32; 4]);
53
54impl Srgba {
55 pub const TRANSPARENT: Self = Self([0.0; 4]);
56 pub const WHITE: Self = Self([1.0; 4]);
57
58 pub const fn hex(rgb: u32) -> Self {
59 Self([
60 ((rgb >> 16) & 255) as f32 / 255.0,
61 ((rgb >> 8) & 255) as f32 / 255.0,
62 (rgb & 255) as f32 / 255.0,
63 1.0,
64 ])
65 }
66
67 pub const fn alpha(self, alpha: f32) -> Self {
68 Self([self.0[0], self.0[1], self.0[2], alpha])
69 }
70
71 pub fn premultiplied(self) -> [f32; 4] {
72 let [r, g, b, a] = self.0;
73 [r * a, g * a, b * a, a]
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq)]
78pub struct Fit {
79 pub layout: [f32; 2],
80 pub target: [u32; 2],
81 pub scale: f32,
82 pub offset: [f32; 2],
83 pub letterbox: Letterbox,
84}
85
86impl Fit {
87 pub fn new(layout: [f32; 2], target: [u32; 2]) -> Result<Self, String> {
88 if !(layout[0] > 0.0 && layout[1] > 0.0 && layout[0].is_finite() && layout[1].is_finite()) {
89 return Err("the layout size must be finite and positive".into());
90 }
91 let letterbox = letterbox_nearest(
92 layout[0],
93 layout[1],
94 Size {
95 width: target[0],
96 height: target[1],
97 },
98 )
99 .ok_or("the target is empty")?;
100 Ok(Self {
101 layout,
102 target,
103 scale: letterbox.matrix[0],
104 offset: [letterbox.matrix[12], letterbox.matrix[13]],
105 letterbox,
106 })
107 }
108
109 pub fn to_target(&self, point: [f32; 2]) -> [f32; 2] {
110 self.letterbox.map(point[0], point[1])
111 }
112
113 pub fn to_layout(&self, pixel: [f32; 2]) -> [f32; 2] {
114 self.letterbox.unmap(pixel[0], pixel[1])
115 }
116
117 pub fn viewport(&self) -> [f32; 4] {
118 [
119 self.offset[0],
120 self.offset[1],
121 self.offset[0] + self.layout[0] * self.scale,
122 self.offset[1] + self.layout[1] * self.scale,
123 ]
124 }
125
126 pub fn clip_from_layout(&self) -> Matrix {
127 let sx = 2.0 * self.scale / self.target[0] as f32;
128 let sy = -2.0 * self.scale / self.target[1] as f32;
129 [
130 [sx, 0.0, 0.0, 0.0],
131 [0.0, sy, 0.0, 0.0],
132 [0.0, 0.0, 0.0, 0.0],
133 [
134 2.0 * self.offset[0] / self.target[0] as f32 - 1.0,
135 1.0 - 2.0 * self.offset[1] / self.target[1] as f32,
136 0.5,
137 1.0,
138 ],
139 ]
140 }
141}
142
143pub fn multiply(a: Matrix, b: Matrix) -> Matrix {
144 crate::frame::multiply(a, b)
145}
146
147pub fn translation(x: f32, y: f32) -> Matrix {
148 let mut m = IDENTITY;
149 m[3][0] = x;
150 m[3][1] = y;
151 m
152}
153
154pub fn rotation(radians: f32) -> Matrix {
155 let (s, c) = radians.sin_cos();
156 let mut m = IDENTITY;
157 m[0][0] = c;
158 m[0][1] = s;
159 m[1][0] = -s;
160 m[1][1] = c;
161 m
162}
163
164pub fn scaling(x: f32, y: f32) -> Matrix {
165 let mut m = IDENTITY;
166 m[0][0] = x;
167 m[1][1] = y;
168 m[2][2] = x.abs().max(y.abs());
169 m
170}
171
172pub fn tip_x(radians: f32) -> Matrix {
173 let (s, c) = radians.sin_cos();
174 let mut m = IDENTITY;
175 m[1][1] = c;
176 m[1][2] = s;
177 m[2][1] = -s;
178 m[2][2] = c;
179 m
180}
181
182pub fn tip_y(radians: f32) -> Matrix {
183 let (s, c) = radians.sin_cos();
184 let mut m = IDENTITY;
185 m[0][0] = c;
186 m[0][2] = -s;
187 m[2][0] = s;
188 m[2][2] = c;
189 m
190}
191
192pub fn place(centre: [f32; 2], radians: f32) -> Matrix {
193 multiply(translation(centre[0], centre[1]), rotation(radians))
194}
195
196pub fn clamp_radii(half: [f32; 2], radii: [f32; 4]) -> [f32; 4] {
197 let limit = half[0].min(half[1]);
198 radii.map(|radius| radius.min(limit).max(0.0))
199}
200
201#[derive(Clone, Copy, Debug, PartialEq)]
202pub enum Shape {
203 Rect {
204 half: [f32; 2],
205 radii: [f32; 4],
206 },
207 Circle {
208 radius: f32,
209 },
210 Ring {
211 radius: f32,
212 width: f32,
213 },
214 Arc {
215 radius: f32,
216 width: f32,
217 start: f32,
218 sweep: f32,
219 },
220 Icon(Icon),
221 Text(usize),
222}
223
224#[derive(Clone, Copy, Debug, PartialEq)]
225pub enum Fill {
226 None,
227 Solid(Srgba),
228 Vertical(Srgba, Srgba),
229 Gradient(Gradient),
230 Image(SpriteFill),
231}
232
233#[derive(Clone, Copy, Debug, PartialEq)]
234pub struct SpriteFill {
235 pub frame: [f32; 4],
236 pub tint: Srgba,
237}
238
239impl SpriteFill {
240 pub fn new(frame: [f32; 4]) -> Self {
241 Self {
242 frame,
243 tint: Srgba::WHITE,
244 }
245 }
246}
247
248#[derive(Clone, Copy, Debug, PartialEq)]
249pub struct Dash {
250 pub on: f32,
251 pub off: f32,
252 pub phase: f32,
253}
254
255#[derive(Clone, Copy, Debug, PartialEq)]
256pub struct Stroke {
257 pub colour: Srgba,
258 pub width: f32,
259 pub dash: Option<Dash>,
260}
261
262impl Stroke {
263 pub fn solid(colour: Srgba, width: f32) -> Self {
264 Self {
265 colour,
266 width,
267 dash: None,
268 }
269 }
270
271 pub fn dashed(colour: Srgba, width: f32, on: f32, off: f32) -> Self {
272 Self {
273 colour,
274 width,
275 dash: Some(Dash {
276 on,
277 off,
278 phase: 0.0,
279 }),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq)]
285pub struct Glow {
286 pub colour: Srgba,
287 pub sigma: f32,
288}
289
290#[derive(Clone, Copy, Debug, Default, PartialEq)]
291pub enum Shadow {
292 None,
293 #[default]
294 Cast,
295 Turned,
296 Glow(Glow),
297}
298
299#[derive(Clone, Copy, Debug, Default, PartialEq)]
300pub struct Material {
301 pub thickness: f32,
302 pub bevel: f32,
303 pub gloss: f32,
304 pub roughness: f32,
305 pub reflection: f32,
306}
307
308impl Material {
309 pub fn is_flat(&self) -> bool {
310 self.thickness <= 0.0 && self.bevel <= 0.0 && self.gloss <= 0.0 && self.reflection <= 0.0
311 }
312}
313
314#[derive(Clone, Copy, Debug, PartialEq)]
315pub struct Draw {
316 pub transform: Matrix,
317 pub elevation: f32,
318 pub shape: Shape,
319 pub fill: Fill,
320 pub stroke: Option<Stroke>,
321 pub opacity: f32,
322 pub shadow: Shadow,
323 pub material: Material,
324 pub pattern: Option<Pattern>,
325 pub id: u32,
326 pub group: Option<u16>,
327 pub style: Option<Style>,
328 pub fx: Fx,
329 pub roles: Roles,
330}
331
332#[derive(Clone, Copy, Debug, Default, PartialEq)]
333pub struct Fx {
334 pub blur: [f32; 2],
335 pub additive: bool,
336 pub unpicked: bool,
337}
338
339impl Fx {
340 pub fn is_none(&self) -> bool {
341 *self == Self::default()
342 }
343}
344
345#[derive(Clone, Copy, Debug, PartialEq, Eq)]
346pub enum PatternKind {
347 Stripes,
348 Dots,
349 CrossHatch,
350 Chevrons,
351}
352
353#[derive(Clone, Copy, Debug, PartialEq)]
354pub struct Pattern {
355 pub kind: PatternKind,
356 pub scale: f32,
357 pub angle: f32,
358 pub width: f32,
359 pub colour: Srgba,
360}
361
362impl Pattern {
363 pub fn new(kind: PatternKind, scale: f32, angle: f32, width: f32, colour: Srgba) -> Self {
364 Self {
365 kind,
366 scale,
367 angle,
368 width,
369 colour,
370 }
371 }
372
373 fn bits(&self) -> u32 {
374 let code = match self.kind {
375 PatternKind::Stripes => 1,
376 PatternKind::Dots => 2,
377 PatternKind::CrossHatch => 3,
378 PatternKind::Chevrons => 4,
379 };
380 code << PATTERN_SHIFT
381 }
382
383 fn packed_colour(&self) -> u32 {
384 let byte = |value: f32| (value.clamp(0.0, 1.0) * 255.0).round() as u32;
385 let [r, g, b, a] = self.colour.0;
386 byte(r) | byte(g) << 8 | byte(b) << 16 | byte(a) << 24
387 }
388}
389
390impl Shape {
391 pub fn rounded(half: [f32; 2], radius: f32) -> Self {
392 Self::Rect {
393 half,
394 radii: [radius; 4],
395 }
396 }
397
398 pub fn corners(half: [f32; 2], radii: [f32; 4]) -> Self {
399 Self::Rect { half, radii }
400 }
401}
402
403impl Draw {
404 pub fn new(shape: Shape) -> Self {
405 Self {
406 transform: IDENTITY,
407 elevation: 0.0,
408 shape,
409 fill: Fill::None,
410 stroke: None,
411 opacity: 1.0,
412 shadow: Shadow::Cast,
413 material: Material::default(),
414 pattern: None,
415 id: 0,
416 group: None,
417 style: None,
418 fx: Fx::default(),
419 roles: Roles::default(),
420 }
421 }
422
423 pub fn rect(centre: [f32; 2], size: [f32; 2], radius: f32) -> Self {
424 Self::rect_corners(centre, size, [radius; 4])
425 }
426
427 pub fn rect_corners(centre: [f32; 2], size: [f32; 2], radii: [f32; 4]) -> Self {
428 Self::new(Shape::Rect {
429 half: [size[0] * 0.5, size[1] * 0.5],
430 radii,
431 })
432 .at(centre)
433 }
434
435 pub fn sprite(centre: [f32; 2], size: [f32; 2], frame: [f32; 4]) -> Self {
436 let mut draw = Self::rect(centre, size, 0.0);
437 draw.fill = Fill::Image(SpriteFill::new(frame));
438 draw
439 }
440
441 pub fn circle(centre: [f32; 2], radius: f32) -> Self {
442 Self::new(Shape::Circle { radius }).at(centre)
443 }
444
445 pub fn at(mut self, centre: [f32; 2]) -> Self {
446 self.transform = translation(centre[0], centre[1]);
447 self
448 }
449
450 pub fn transform(mut self, transform: Matrix) -> Self {
451 self.transform = transform;
452 self
453 }
454
455 pub fn elevation(mut self, elevation: f32) -> Self {
456 self.elevation = elevation;
457 self
458 }
459
460 pub fn fill(mut self, colour: Srgba) -> Self {
461 self.fill = Fill::Solid(colour);
462 self
463 }
464
465 pub fn vertical(mut self, top: Srgba, bottom: Srgba) -> Self {
466 self.fill = Fill::Vertical(top, bottom);
467 self
468 }
469
470 pub fn stroke(mut self, stroke: Stroke) -> Self {
471 self.stroke = Some(stroke);
472 self
473 }
474
475 pub fn opacity(mut self, opacity: f32) -> Self {
476 self.opacity = opacity;
477 self
478 }
479
480 pub fn shadow(mut self, shadow: Shadow) -> Self {
481 self.shadow = shadow;
482 self
483 }
484
485 pub fn material(mut self, material: Material) -> Self {
486 self.material = material;
487 self
488 }
489
490 pub fn pattern(mut self, pattern: Pattern) -> Self {
491 self.pattern = Some(pattern);
492 self
493 }
494
495 pub fn id(mut self, id: u32) -> Self {
496 self.id = id;
497 self
498 }
499
500 pub fn group(mut self, group: u16) -> Self {
501 self.group = Some(group);
502 self
503 }
504
505 pub fn style(mut self, style: Style) -> Self {
506 self.style = Some(style);
507 self
508 }
509
510 pub fn fill_token(mut self, key: impl Into<TokenKey>) -> Self {
511 self.roles.fill = Some(key.into());
512 self
513 }
514
515 pub fn fill_end_token(mut self, key: impl Into<TokenKey>) -> Self {
516 self.roles.fill_end = Some(key.into());
517 self
518 }
519
520 pub fn stroke_token(mut self, key: impl Into<TokenKey>) -> Self {
521 self.roles.stroke = Some(key.into());
522 self
523 }
524
525 pub fn pattern_token(mut self, key: impl Into<TokenKey>) -> Self {
526 self.roles.pattern = Some(key.into());
527 self
528 }
529
530 pub fn glow_token(mut self, key: impl Into<TokenKey>) -> Self {
531 self.roles.glow = Some(key.into());
532 self
533 }
534
535 pub fn blur(mut self, displacement: [f32; 2]) -> Self {
536 self.fx.blur = displacement;
537 self
538 }
539
540 pub fn additive(mut self) -> Self {
541 self.fx.additive = true;
542 self
543 }
544
545 pub fn unpicked(mut self) -> Self {
546 self.fx.unpicked = true;
547 self
548 }
549}
550
551#[derive(Clone, Copy, Debug, PartialEq)]
552pub struct Group {
553 pub opacity: f32,
554}
555
556#[derive(Clone, Copy, Debug, PartialEq)]
557pub struct Light {
558 pub shadow: [f32; 2],
559 pub key: [f32; 3],
560 pub ambient: f32,
561}
562
563impl Default for Light {
564 fn default() -> Self {
565 Self {
566 shadow: [0.0, 1.0],
567 key: normalize3([0.0, -0.5, 1.0]),
568 ambient: 0.55,
569 }
570 }
571}
572
573#[derive(Clone, Copy)]
574pub enum Glyphs<'a> {
575 Paragraph(&'a Paragraph),
576 Quads(&'a [RichQuad]),
577}
578
579#[derive(Clone, Copy)]
580pub struct FlatText<'a> {
581 pub atlas: &'a GpuAtlas,
582 pub glyphs: Glyphs<'a>,
583 pub colour: Srgba,
584}
585
586#[derive(Clone, Copy)]
587pub struct FlatScene<'a> {
588 pub layout: [f32; 2],
589 pub clear: Option<Srgba>,
590 pub curve: &'a ShadowCurve,
591 pub light: Light,
592 pub draws: &'a [Draw],
593 pub groups: &'a [Group],
594 pub text: &'a [FlatText<'a>],
595 pub icons: Option<&'a FlatIcons>,
596 pub sprites: Option<&'a FlatSprites>,
597 pub environment: Option<&'a Environment>,
598 pub post: bool,
599 pub frame: u32,
600 pub seed: u32,
601}
602
603pub(crate) fn normalize3(v: [f32; 3]) -> [f32; 3] {
604 let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
605 if l > 0.0 {
606 [v[0] / l, v[1] / l, v[2] / l]
607 } else {
608 [0.0, 0.0, 1.0]
609 }
610}
611
612pub const KIND_RECT: u32 = 0;
613pub const KIND_CIRCLE: u32 = 1;
614pub const KIND_RING: u32 = 2;
615pub const KIND_ARC: u32 = 3;
616pub const KIND_ICON_FILL: u32 = 4;
617pub const KIND_ICON_LINE: u32 = 5;
618pub const KIND_SHADOW: u32 = 6;
619pub const FILL_SOLID: u32 = 1 << 4;
620pub const FILL_VERTICAL: u32 = 2 << 4;
621pub const FILL_GRADIENT: u32 = 3 << 4;
622pub const FILL_IMAGE: u32 = 4 << 4;
623pub const FILL_MASK: u32 = 15 << 4;
624pub const DASHED: u32 = 1 << 8;
625pub const SLAB: u32 = 1 << 9;
626pub const LIT: u32 = 1 << 10;
627pub const BLUR: u32 = 1 << 11;
628pub const PATTERN_SHIFT: u32 = 12;
629pub const PATTERN_MASK: u32 = 7 << PATTERN_SHIFT;
630pub const ADDITIVE: u32 = 1 << 15;
631pub const UNPICKED: u32 = 1 << 16;
632pub const FX: u32 = BLUR | ADDITIVE | UNPICKED;
633pub const BLUR_MIN_PIXELS: f32 = 0.25;
634
635#[repr(C)]
636#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
637pub struct Instance {
638 pub axes: [f32; 4],
639 pub origin: [f32; 4],
640 pub bounds: [f32; 4],
641 pub shape: [f32; 4],
642 pub stroke: [f32; 4],
643 pub fill_top: [f32; 4],
644 pub fill_bottom: [f32; 4],
645 pub stroke_colour: [f32; 4],
646 pub icon: [f32; 4],
647 pub info: [u32; 4],
648 pub material: [f32; 4],
649 pub frame: [[f32; 4]; 3],
650 pub corners: [f32; 4],
651}
652
653pub const INSTANCE_ATTRIBUTES: u32 = 15;
654
655impl Instance {
656 pub fn kind(&self) -> u32 {
657 self.info[0] & 15
658 }
659
660 pub fn opacity(&self) -> f32 {
661 f32::from_bits(self.info[3])
662 }
663
664 pub fn inverse(&self) -> [[f32; 2]; 2] {
665 let [a, b, c, d] = self.axes;
666 let det = a * d - c * b;
667 [[d / det, -b / det], [-c / det, a / det]]
668 }
669
670 pub fn margin(&self) -> [f32; 2] {
671 let inverse = self.inverse();
672 [
673 1.5 * (inverse[0][0] * inverse[0][0] + inverse[1][0] * inverse[1][0]).sqrt(),
674 1.5 * (inverse[0][1] * inverse[0][1] + inverse[1][1] * inverse[1][1]).sqrt(),
675 ]
676 }
677
678 pub fn pixel_bounds(&self) -> [f32; 4] {
679 let margin = self.margin();
680 let [cx, cy, hx, hy] = self.bounds;
681 let mut out = [f32::MAX, f32::MAX, f32::MIN, f32::MIN];
682 for corner in [[-1.0, -1.0], [1.0, -1.0], [-1.0, 1.0], [1.0, 1.0]] {
683 let local = [
684 cx + corner[0] * (hx + margin[0]),
685 cy + corner[1] * (hy + margin[1]),
686 ];
687 let pixel = [
688 self.axes[0] * local[0] + self.axes[2] * local[1] + self.origin[0],
689 self.axes[1] * local[0] + self.axes[3] * local[1] + self.origin[1],
690 ];
691 out = [
692 out[0].min(pixel[0]),
693 out[1].min(pixel[1]),
694 out[2].max(pixel[0]),
695 out[3].max(pixel[1]),
696 ];
697 }
698 out
699 }
700
701 pub fn to_local(&self, pixel: [f32; 2]) -> [f32; 2] {
702 let inverse = self.inverse();
703 let d = [pixel[0] - self.origin[0], pixel[1] - self.origin[1]];
704 [
705 inverse[0][0] * d[0] + inverse[1][0] * d[1],
706 inverse[0][1] * d[0] + inverse[1][1] * d[1],
707 ]
708 }
709}
710
711pub(crate) fn positive(value: f32) -> bool {
712 value > 0.0
713}
714
715fn length2(v: [f32; 2]) -> f32 {
716 (v[0] * v[0] + v[1] * v[1]).sqrt()
717}
718
719#[derive(Clone, Copy, Debug)]
720pub(crate) struct Placed {
721 pub axes: [f32; 4],
722 pub origin: [f32; 2],
723 pub layout_axes: [f32; 4],
724}
725
726pub(crate) fn placed(transform: &Matrix, fit: &Fit) -> Option<Placed> {
727 let layout_axes = [
728 transform[0][0],
729 transform[0][1],
730 transform[1][0],
731 transform[1][1],
732 ];
733 let det = layout_axes[0] * layout_axes[3] - layout_axes[2] * layout_axes[1];
734 if !det.is_finite() || det.abs() < 1e-8 {
735 return None;
736 }
737 if transform.iter().flatten().any(|value| !value.is_finite()) {
738 return None;
739 }
740 Some(Placed {
741 axes: layout_axes.map(|value| value * fit.scale),
742 origin: fit.to_target([transform[3][0], transform[3][1]]),
743 layout_axes,
744 })
745}
746
747fn rotation_rows(transform: &Matrix) -> [[f32; 4]; 3] {
748 let columns =
749 [0, 1, 2].map(|c| normalize3([transform[c][0], transform[c][1], transform[c][2]]));
750 [0, 1, 2].map(|r| [columns[0][r], columns[1][r], columns[2][r], 0.0])
751}
752
753pub(crate) fn caster(draw: &Draw) -> Option<([f32; 2], [f32; 4])> {
754 let grow = draw
755 .stroke
756 .map_or(0.0, |stroke| stroke.width.max(0.0) * 0.5);
757 match draw.shape {
758 Shape::Rect { half, radii } => Some((
759 [half[0] + grow, half[1] + grow],
760 clamp_radii(half, radii).map(|radius| radius + grow),
761 )),
762 Shape::Circle { radius } => Some(([radius + grow; 2], [radius + grow; 4])),
763 Shape::Ring { radius, width } | Shape::Arc { radius, width, .. } => {
764 let outer = radius + width * 0.5 + grow;
765 Some(([outer; 2], [outer; 4]))
766 }
767 Shape::Icon(_) | Shape::Text(_) => None,
768 }
769}
770
771fn paint_alpha(draw: &Draw) -> f32 {
772 let fill = match draw.fill {
773 Fill::None => 0.0,
774 Fill::Solid(colour) => colour.0[3],
775 Fill::Vertical(top, bottom) => top.0[3].max(bottom.0[3]),
776 Fill::Gradient(gradient) => gradient.max_alpha(),
777 Fill::Image(image) => image.tint.0[3],
778 };
779 let stroke = draw
780 .stroke
781 .filter(|stroke| stroke.width > 0.0)
782 .map_or(0.0, |stroke| stroke.colour.0[3]);
783 fill.max(stroke).clamp(0.0, 1.0)
784}
785
786pub fn slab_size(draw: &Draw, icons: Option<&Icons>) -> Option<(f32, f32)> {
787 let material = draw.material;
788 if !(positive(material.thickness) || positive(material.bevel)) {
789 return None;
790 }
791 let m = draw.transform;
792 let scale = (m[0][0] * m[0][0] + m[0][1] * m[0][1] + m[0][2] * m[0][2]).sqrt();
793 if !positive(scale) || !scale.is_finite() {
794 return None;
795 }
796 let mut thickness = material.thickness.max(material.bevel).max(0.0);
797 let bevel_limit = match draw.shape {
798 Shape::Rect { half, .. } => half[0].min(half[1]) * scale,
799 Shape::Circle { radius } => radius * scale,
800 Shape::Icon(handle) => {
801 let entry = icons?.entry(handle)?;
802 let side = (entry.bounds[2] - entry.bounds[0]).min(entry.bounds[3] - entry.bounds[1]);
803 thickness = thickness.min(side * scale / 8.0);
804 match entry.kind {
805 IconKind::Line => draw.stroke.map_or(0.0, |stroke| stroke.width * scale * 0.5),
806 IconKind::Fill => f32::MAX,
807 }
808 }
809 _ => return None,
810 };
811 let bevel = material
812 .bevel
813 .clamp(0.0, (thickness * 0.5).min(bevel_limit));
814 Some((thickness / scale, bevel / scale))
815}
816
817pub fn pack_shape(draw: &Draw, fit: &Fit, icons: Option<&Icons>) -> Option<Instance> {
818 match (draw.style, draw.fill) {
819 (None, _) => pack_plain(draw, fit, icons),
820 (Some(style), Fill::Image(image)) => {
821 let tinted = Draw {
822 fill: Fill::Solid(image.tint),
823 ..*draw
824 };
825 style::pack(&tinted, style, fit, icons)
826 }
827 (Some(style), _) => style::pack(draw, style, fit, icons),
828 }
829}
830
831pub(crate) fn pack_plain(draw: &Draw, fit: &Fit, icons: Option<&Icons>) -> Option<Instance> {
832 let placed = placed(&draw.transform, fit)?;
833 if !positive(draw.opacity)
834 || (draw.fill == Fill::None && draw.stroke.is_none() && draw.pattern.is_none())
835 {
836 return None;
837 }
838 let stroke = draw
839 .stroke
840 .filter(|stroke| stroke.width > 0.0 && stroke.width.is_finite());
841 let grow = stroke.map_or(0.0, |stroke| stroke.width * 0.5);
842 let mut kind;
843 let mut shape = [0.0; 4];
844 let mut corners = [0.0; 4];
845 let mut icon = [0.0; 4];
846 let bounds;
847 let gradient;
848 match draw.shape {
849 Shape::Rect { half, radii } => {
850 kind = KIND_RECT;
851 corners = clamp_radii(half, radii);
852 shape = [half[0], half[1], 0.0, 0.0];
853 bounds = [0.0, 0.0, half[0] + grow, half[1] + grow];
854 gradient = [-half[1], half[1]];
855 }
856 Shape::Circle { radius } => {
857 kind = KIND_CIRCLE;
858 shape[0] = radius;
859 bounds = [0.0, 0.0, radius + grow, radius + grow];
860 gradient = [-radius, radius];
861 }
862 Shape::Ring { radius, width } => {
863 kind = KIND_RING;
864 shape = [radius, width, 0.0, 0.0];
865 let outer = radius + width * 0.5 + grow;
866 bounds = [0.0, 0.0, outer, outer];
867 gradient = [-outer, outer];
868 }
869 Shape::Arc {
870 radius,
871 width,
872 start,
873 sweep,
874 } => {
875 kind = KIND_ARC;
876 shape = [radius, width, start, sweep];
877 let outer = radius + width * 0.5 + grow;
878 bounds = [0.0, 0.0, outer, outer];
879 gradient = [-outer, outer];
880 }
881 Shape::Icon(handle) => {
882 let entry = icons?.entry(handle)?;
883 kind = match entry.kind {
884 IconKind::Fill => KIND_ICON_FILL,
885 IconKind::Line => KIND_ICON_LINE,
886 };
887 let [x0, y0, x1, y1] = entry.bounds;
888 bounds = [
889 (x0 + x1) * 0.5,
890 (y0 + y1) * 0.5,
891 (x1 - x0) * 0.5,
892 (y1 - y0) * 0.5,
893 ];
894 icon = entry.uv;
895 gradient = [y0, y1];
896 }
897 Shape::Text(_) => return None,
898 }
899 let mut flags = 0;
900 let (top, bottom) = match draw.fill {
901 Fill::None => (Srgba::TRANSPARENT, Srgba::TRANSPARENT),
902 Fill::Solid(colour) => {
903 flags |= FILL_SOLID;
904 (colour, colour)
905 }
906 Fill::Vertical(top, bottom) => {
907 flags |= FILL_VERTICAL;
908 (top, bottom)
909 }
910 Fill::Image(image) => {
911 flags |= FILL_IMAGE;
912 let reach = match draw.shape {
913 Shape::Rect { half, .. } => Some(half),
914 Shape::Circle { radius } => Some([radius; 2]),
915 Shape::Ring { radius, width } | Shape::Arc { radius, width, .. } => {
916 Some([radius + width * 0.5; 2])
917 }
918 Shape::Icon(_) | Shape::Text(_) => None,
919 };
920 match reach.filter(|half| positive(half[0]) && positive(half[1])) {
921 Some(half) => {
922 let size = [
923 image.frame[2] - image.frame[0],
924 image.frame[3] - image.frame[1],
925 ];
926 icon = [
927 image.frame[0] + size[0] * 0.5,
928 image.frame[1] + size[1] * 0.5,
929 size[0] / (2.0 * half[0]),
930 size[1] / (2.0 * half[1]),
931 ];
932 }
933 None => flags = flags & !FILL_IMAGE | FILL_SOLID,
934 }
935 (image.tint, image.tint)
936 }
937 Fill::Gradient(gradient) => {
938 flags |= FILL_GRADIENT;
939 let header = gradient.header();
940 (
941 Srgba(gradient.geometry()),
942 Srgba([0.0, header[0], header[1] + 2.0 * header[2], header[3]]),
943 )
944 }
945 };
946 if kind == KIND_ICON_LINE {
947 flags &= !FILL_MASK;
948 }
949 let mut pattern_colour = 0;
950 if let Some(pattern) = draw.pattern
951 && matches!(kind, KIND_RECT | KIND_CIRCLE | KIND_RING | KIND_ARC)
952 && flags & FILL_MASK != FILL_IMAGE
953 && positive(pattern.scale)
954 && pattern.scale.is_finite()
955 && pattern.angle.is_finite()
956 && positive(pattern.colour.0[3])
957 {
958 flags |= pattern.bits();
959 if flags & (FILL_SOLID | FILL_VERTICAL) == 0 {
960 flags |= FILL_SOLID;
961 }
962 icon = [
963 pattern.scale,
964 pattern.angle,
965 pattern.width.clamp(0.0, 1.0),
966 0.0,
967 ];
968 pattern_colour = pattern.packed_colour();
969 }
970 let mut stroke_params = [0.0; 4];
971 let mut stroke_colour = [0.0; 4];
972 if let Some(stroke) = stroke {
973 stroke_params[0] = stroke.width;
974 stroke_colour = stroke.colour.0;
975 if let Some(dash) = stroke.dash
976 && dash.on > 0.0
977 && dash.off > 0.0
978 && matches!(kind, KIND_RECT | KIND_CIRCLE)
979 {
980 flags |= DASHED;
981 stroke_params[1] = dash.on;
982 stroke_params[2] = dash.off;
983 stroke_params[3] = dash.phase;
984 }
985 }
986 let material = draw.material;
987 let mut material_params = [0.0; 4];
988 let mut frame = [[0.0; 4]; 3];
989 let flat_bounds = bounds;
990 let mut bounds = bounds;
991 if !material.is_flat() {
992 flags |= LIT;
993 frame = rotation_rows(&draw.transform);
994 frame[0][3] = material.reflection.max(0.0);
995 frame[1][3] = material.roughness.clamp(0.02, 1.0);
996 material_params[2] = material.gloss.max(0.0);
997 if let Some((thickness, bevel)) = slab_size(draw, icons) {
998 flags |= SLAB;
999 material_params[0] = thickness;
1000 material_params[1] = bevel;
1001 let down = [frame[2][0], frame[2][1], frame[2][2]];
1002 if down[2].abs() > 1e-3 {
1003 let reach = [
1004 (thickness * down[0] / down[2]).abs(),
1005 (thickness * down[1] / down[2]).abs(),
1006 ];
1007 bounds[2] += reach[0];
1008 bounds[3] += reach[1];
1009 }
1010 }
1011 }
1012 if draw.fx.additive {
1013 flags |= ADDITIVE;
1014 }
1015 if draw.fx.unpicked {
1016 flags |= UNPICKED;
1017 }
1018 if let Some(shift) = blur_shift(draw, &placed, fit) {
1019 if flags & FILL_MASK == FILL_IMAGE {
1020 flags = flags & !FILL_MASK | FILL_SOLID;
1021 }
1022 flags = (flags & !(LIT | SLAB)) | BLUR;
1023 material_params = [shift[0], shift[1], 0.0, 0.0];
1024 frame = [[0.0; 4]; 3];
1025 if matches!(kind, KIND_ICON_FILL | KIND_ICON_LINE) {
1026 shape = flat_bounds;
1027 }
1028 bounds = [
1029 flat_bounds[0],
1030 flat_bounds[1],
1031 flat_bounds[2] + shift[0].abs() * 0.5,
1032 flat_bounds[3] + shift[1].abs() * 0.5,
1033 ];
1034 }
1035 kind |= flags;
1036 let opacity = draw.opacity.min(1.0);
1037 let mut instance = Instance {
1038 axes: placed.axes,
1039 origin: [placed.origin[0], placed.origin[1], gradient[0], gradient[1]],
1040 bounds,
1041 shape,
1042 stroke: stroke_params,
1043 fill_top: top.0,
1044 fill_bottom: bottom.0,
1045 stroke_colour,
1046 icon,
1047 info: [kind, draw.id, pattern_colour, opacity.to_bits()],
1048 material: material_params,
1049 frame,
1050 corners,
1051 };
1052 if matches!(kind & 15, KIND_ICON_FILL | KIND_ICON_LINE) {
1053 let margin = instance.margin();
1054 instance.bounds[2] = (instance.bounds[2] - margin[0]).max(0.0);
1055 instance.bounds[3] = (instance.bounds[3] - margin[1]).max(0.0);
1056 }
1057 Some(instance)
1058}
1059
1060fn blur_shift(draw: &Draw, placed: &Placed, fit: &Fit) -> Option<[f32; 2]> {
1061 let blur = draw.fx.blur;
1062 if !(blur[0].is_finite() && blur[1].is_finite()) {
1063 return None;
1064 }
1065 if length2(blur) * fit.scale < BLUR_MIN_PIXELS {
1066 return None;
1067 }
1068 let [a, b, c, d] = placed.layout_axes;
1069 let det = a * d - c * b;
1070 Some([
1071 (d * blur[0] - c * blur[1]) / det,
1072 (-b * blur[0] + a * blur[1]) / det,
1073 ])
1074}
1075
1076pub fn pack_shadow(draw: &Draw, fit: &Fit, curve: &ShadowCurve, light: &Light) -> Option<Instance> {
1077 if draw.style.is_some() {
1078 return pack_shadow(&style::resolve(draw), fit, curve, light);
1079 }
1080 let (half, radii) = caster(draw)?;
1081 let placed = placed(&draw.transform, fit)?;
1082 let opacity = draw.opacity.min(1.0);
1083 let alpha = match draw.shadow {
1084 Shadow::Glow(_) => opacity,
1085 _ => paint_alpha(draw) * opacity,
1086 };
1087 if !positive(alpha) {
1088 return None;
1089 }
1090 let (colour, sigma, offset_local) = match draw.shadow {
1091 Shadow::None => return None,
1092 Shadow::Glow(glow) => (glow.colour, glow.sigma, [0.0, 0.0]),
1093 Shadow::Cast | Shadow::Turned => {
1094 let point = curve.at(draw.elevation)?;
1095 let direction = light.shadow;
1096 let length = length2(direction);
1097 let direction = if length > 0.0 {
1098 [direction[0] / length, direction[1] / length]
1099 } else {
1100 [0.0, 1.0]
1101 };
1102 let offset = [direction[0] * point.offset, direction[1] * point.offset];
1103 let local = if draw.shadow == Shadow::Turned {
1104 offset
1105 } else {
1106 let [a, b, c, d] = placed.layout_axes;
1107 let det = a * d - c * b;
1108 [
1109 (d * offset[0] - c * offset[1]) / det,
1110 (-b * offset[0] + a * offset[1]) / det,
1111 ]
1112 };
1113 (
1114 curve.colour.alpha(curve.colour.0[3] * point.alpha),
1115 point.sigma,
1116 local,
1117 )
1118 }
1119 };
1120 let alpha = alpha * colour.0[3];
1121 if !(sigma >= 0.0 && sigma.is_finite() && alpha > 0.0) {
1122 return None;
1123 }
1124 let [a, b, c, d] = placed.layout_axes;
1125 let unit = [length2([a, b]), length2([c, d])];
1126 let sigma_local = [(sigma / unit[0]).max(1e-4), (sigma / unit[1]).max(1e-4)];
1127 let reach = [4.0 * sigma_local[0], 4.0 * sigma_local[1]];
1128 Some(Instance {
1129 axes: placed.axes,
1130 origin: [placed.origin[0], placed.origin[1], 0.0, 0.0],
1131 bounds: [
1132 offset_local[0],
1133 offset_local[1],
1134 half[0] + reach[0],
1135 half[1] + reach[1],
1136 ],
1137 shape: [half[0], half[1], 0.0, 0.0],
1138 stroke: [0.0; 4],
1139 fill_top: colour.alpha(alpha).0,
1140 fill_bottom: [0.0; 4],
1141 stroke_colour: [0.0; 4],
1142 icon: [
1143 sigma_local[0],
1144 sigma_local[1],
1145 offset_local[0],
1146 offset_local[1],
1147 ],
1148 info: [KIND_SHADOW, 0, 0, 1.0f32.to_bits()],
1149 material: [0.0; 4],
1150 frame: [[0.0; 4]; 3],
1151 corners: radii,
1152 })
1153}
1154
1155#[derive(Clone, Copy, Debug, PartialEq)]
1156pub enum Step {
1157 Draw(usize),
1158 Layer(u16),
1159 EndLayer(u16),
1160}
1161
1162#[derive(Clone, Debug, Default)]
1163pub struct Order {
1164 pub steps: Vec<Step>,
1165 keys: Vec<(f32, usize, f32, usize)>,
1166 bases: Vec<(f32, usize)>,
1167}
1168
1169impl Order {
1170 pub fn build(&mut self, draws: &[Draw], groups: usize) -> Result<(), String> {
1171 self.steps.clear();
1172 self.keys.clear();
1173 self.bases.clear();
1174 self.bases.resize(groups, (f32::MAX, usize::MAX));
1175 for (index, draw) in draws.iter().enumerate() {
1176 if !draw.elevation.is_finite() {
1177 return Err(format!("draw {index} has a non-finite elevation"));
1178 }
1179 if let Some(group) = draw.group {
1180 let base = self
1181 .bases
1182 .get_mut(usize::from(group))
1183 .ok_or_else(|| format!("draw {index} names group {group}, which is missing"))?;
1184 base.0 = base.0.min(draw.elevation);
1185 base.1 = base.1.min(index);
1186 }
1187 }
1188 for (index, draw) in draws.iter().enumerate() {
1189 self.keys.push(match draw.group {
1190 None => (draw.elevation, index, 0.0, index),
1191 Some(group) => {
1192 let (base, first) = self.bases[usize::from(group)];
1193 (base, first, draw.elevation, index)
1194 }
1195 });
1196 }
1197 self.keys.sort_unstable_by(|a, b| {
1198 a.0.total_cmp(&b.0)
1199 .then(a.1.cmp(&b.1))
1200 .then(a.2.total_cmp(&b.2))
1201 .then(a.3.cmp(&b.3))
1202 });
1203 let mut open: Option<u16> = None;
1204 for &(_, _, _, index) in &self.keys {
1205 let group = draws[index].group;
1206 if open != group {
1207 if let Some(previous) = open {
1208 self.steps.push(Step::EndLayer(previous));
1209 }
1210 if let Some(next) = group {
1211 self.steps.push(Step::Layer(next));
1212 }
1213 open = group;
1214 }
1215 self.steps.push(Step::Draw(index));
1216 }
1217 if let Some(previous) = open {
1218 self.steps.push(Step::EndLayer(previous));
1219 }
1220 Ok(())
1221 }
1222}