1use std::collections::hash_map::DefaultHasher;
2use std::hash::Hasher;
3use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, Window};
4
5use super::*;
6
7#[derive(Debug, Clone, Copy, PartialEq, Default)]
8pub struct CanvasFingerPrint {
9 pub winding: bool,
10 pub geometry_hash: u64,
11 pub text_hash: u64,
12}
13impl CanvasFingerPrint {
14 pub fn new(window: &Window) -> Option<Self> {
15 let canvas = window
16 .document()?
17 .create_element("canvas")
18 .ok()?
19 .dyn_into::<HtmlCanvasElement>()
20 .ok()?;
21 let ctx = canvas
22 .get_context("2d")
23 .ok()??
24 .dyn_into::<CanvasRenderingContext2d>()
25 .ok()?;
26 let winding = supports_winding(&ctx);
27 render_text_image(&canvas, &ctx);
28 let text = canvas.to_data_url().ok()?;
30 let text_2 = canvas.to_data_url().ok()?;
31 if text != text_2 {
32 return None;
33 }
34 render_geometry_image(&canvas, &ctx);
35 let geometry = canvas.to_data_url().ok()?;
36 let mut hasher = DefaultHasher::new();
37 hasher.write(geometry.as_bytes());
38 let geometry_hash = hasher.finish();
39 let mut hasher = std::collections::hash_map::DefaultHasher::new();
40 hasher.write(text.as_bytes());
41 let text_hash = hasher.finish();
42 Some(Self {
43 winding,
44 geometry_hash,
45 text_hash,
46 })
47 }
48}
49fn render_text_image(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d) {
50 canvas.set_width(240);
52 canvas.set_height(60);
53
54 ctx.set_text_baseline("alphabetic");
55 ctx.set_fill_style(&JsValue::from_str("#60"));
56 ctx.fill_rect(100., 1., 62., 20.);
57 ctx.set_fill_style(&JsValue::from_str("#069"));
58 ctx.set_font(r#"11pt "Times New Roman""#);
61 let printed_text = "Cwm fjordbank gly \u{1F60D}";
62 ctx.fill_text(printed_text, 2., 15.).unwrap();
63 ctx.set_fill_style(&JsValue::from_str("rgba(102, 204, 0, 0.2)"));
64 ctx.set_font("18pt Arial");
65 ctx.fill_text(&printed_text, 4., 45.).unwrap();
66}
67
68fn supports_winding(ctx: &CanvasRenderingContext2d) -> bool {
69 ctx.rect(0., 0., 10., 10.);
70 ctx.rect(2., 2., 6., 6.);
71 ctx.is_point_in_path_with_f64_and_canvas_winding_rule(
72 5.,
73 5.,
74 web_sys::CanvasWindingRule::Evenodd,
75 )
76}
77
78fn render_geometry_image(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d) {
79 canvas.set_width(122);
81 canvas.set_height(110);
82
83 ctx.set_global_composite_operation("multiply").unwrap();
85
86 let colors_and_positions = [
88 ("#f2f", 40.0, 40.0),
89 ("#2ff", 80.0, 40.0),
90 ("#ff2", 60.0, 80.0),
91 ];
92
93 for (color, x, y) in colors_and_positions.iter() {
95 ctx.set_fill_style(&JsValue::from_str(color));
96 ctx.begin_path();
97 ctx.arc(*x, *y, 40.0, 0.0, std::f64::consts::PI * 2.0)
98 .unwrap();
99 ctx.close_path();
100 ctx.fill();
101 }
102
103 ctx.set_fill_style(&JsValue::from_str("#f9c"));
105 ctx.begin_path();
106 ctx.arc(60.0, 60.0, 60.0, 0.0, std::f64::consts::PI * 2.0)
107 .unwrap();
108 ctx.arc(60.0, 60.0, 20.0, 0.0, std::f64::consts::PI * 2.0)
109 .unwrap();
110 ctx.fill_with_canvas_winding_rule(web_sys::CanvasWindingRule::Evenodd);
111}