use std::hash::Hasher;
use rustc_hash::FxHasher;
use crate::{
BorderRadius, BorderWidths, FillRule, Gradient, GradientKind, Paint, PathStyle, RectStyle,
Shadow, Stroke, TextStyle,
};
pub fn hash_rect_style(s: &RectStyle) -> u64 {
let mut h = FxHasher::default();
hash_opt_paint(s.fill.as_ref(), &mut h);
hash_opt_stroke(s.stroke.as_ref(), &mut h);
hash_opt_shadow(s.shadow.as_ref(), &mut h);
hash_border_radius(&s.radius, &mut h);
hash_border_widths(&s.border_widths, &mut h);
h.finish()
}
fn hash_border_widths(w: &BorderWidths, h: &mut FxHasher) {
match w {
BorderWidths::Uniform => h.write_u8(0),
BorderWidths::PerSide {
top,
right,
bottom,
left,
} => {
h.write_u8(1);
for v in [top, right, bottom, left] {
h.write_u32(v.to_bits());
}
}
}
}
pub fn hash_text_style(s: &TextStyle) -> u64 {
let mut h = FxHasher::default();
h.write_u32(s.font_size.to_bits());
hash_paint(&s.paint, &mut h);
hash_opt_shadow(s.shadow.as_ref(), &mut h);
h.finish()
}
pub fn hash_path_style(s: &PathStyle) -> u64 {
let mut h = FxHasher::default();
hash_opt_paint(s.fill.as_ref(), &mut h);
hash_opt_stroke(s.stroke.as_ref(), &mut h);
hash_opt_shadow(s.shadow.as_ref(), &mut h);
h.write_u8(match s.fill_rule {
FillRule::Winding => 0,
FillRule::EvenOdd => 1,
});
h.finish()
}
fn hash_opt_paint(p: Option<&Paint>, h: &mut FxHasher) {
match p {
None => h.write_u8(0),
Some(paint) => {
h.write_u8(1);
hash_paint(paint, h);
}
}
}
fn hash_paint(p: &Paint, h: &mut FxHasher) {
match p {
Paint::Solid(c) => {
h.write_u8(0);
h.write_u32(c.r.to_bits());
h.write_u32(c.g.to_bits());
h.write_u32(c.b.to_bits());
h.write_u32(c.a.to_bits());
}
Paint::Gradient(g) => {
h.write_u8(1);
hash_gradient(g, h);
}
}
}
fn hash_gradient(g: &Gradient, h: &mut FxHasher) {
match g.kind {
GradientKind::Linear { start, end } => {
h.write_u8(0);
h.write_u32(start.x.to_bits());
h.write_u32(start.y.to_bits());
h.write_u32(end.x.to_bits());
h.write_u32(end.y.to_bits());
}
GradientKind::Radial { center, radius } => {
h.write_u8(1);
h.write_u32(center.x.to_bits());
h.write_u32(center.y.to_bits());
h.write_u32(radius.to_bits());
}
}
let active = g.stops.active();
h.write_usize(active.len());
for stop in active {
h.write_u32(stop.position.to_bits());
h.write_u32(stop.color.r.to_bits());
h.write_u32(stop.color.g.to_bits());
h.write_u32(stop.color.b.to_bits());
h.write_u32(stop.color.a.to_bits());
}
}
fn hash_opt_stroke(s: Option<&Stroke>, h: &mut FxHasher) {
match s {
None => h.write_u8(0),
Some(stroke) => {
h.write_u8(1);
hash_paint(&stroke.paint, h);
h.write_u32(stroke.width.to_bits());
h.write_u8(stroke.cap as u8);
h.write_u8(stroke.join as u8);
}
}
}
fn hash_opt_shadow(s: Option<&Shadow>, h: &mut FxHasher) {
match s {
None => h.write_u8(0),
Some(shadow) => {
h.write_u8(1);
h.write_u32(shadow.offset_x.to_bits());
h.write_u32(shadow.offset_y.to_bits());
h.write_u32(shadow.blur_radius.to_bits());
h.write_u32(shadow.spread.to_bits());
h.write_u32(shadow.color.r.to_bits());
h.write_u32(shadow.color.g.to_bits());
h.write_u32(shadow.color.b.to_bits());
h.write_u32(shadow.color.a.to_bits());
}
}
}
fn hash_border_radius(r: &BorderRadius, h: &mut FxHasher) {
h.write_u32(r.top_left.to_bits());
h.write_u32(r.top_right.to_bits());
h.write_u32(r.bottom_right.to_bits());
h.write_u32(r.bottom_left.to_bits());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Color;
fn solid_path(shadow: Option<Shadow>) -> PathStyle {
PathStyle {
fill: Some(Paint::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0))),
stroke: None,
shadow,
fill_rule: FillRule::Winding,
}
}
#[test]
fn hash_path_style_distinguishes_a_shadow() {
let shadow = Shadow::new(1.0, 2.0, 3.0, Color::rgba(0.0, 0.0, 0.0, 0.5));
assert_ne!(
hash_path_style(&solid_path(None)),
hash_path_style(&solid_path(Some(shadow)))
);
}
#[test]
fn hash_path_style_distinguishes_two_shadows() {
let a = Shadow::new(1.0, 2.0, 3.0, Color::rgba(0.0, 0.0, 0.0, 0.5));
let b = Shadow::new(1.0, 2.0, 4.0, Color::rgba(0.0, 0.0, 0.0, 0.5));
assert_ne!(
hash_path_style(&solid_path(Some(a))),
hash_path_style(&solid_path(Some(b)))
);
}
#[test]
fn hash_path_style_agrees_with_itself() {
let shadow = Shadow::new(1.0, 2.0, 3.0, Color::rgba(0.0, 0.0, 0.0, 0.5));
assert_eq!(
hash_path_style(&solid_path(Some(shadow))),
hash_path_style(&solid_path(Some(shadow)))
);
}
}