This repository has no description
1use crate::{Angle, Color, ColorMapping, rendering::svg};
2
3#[derive(Debug, Clone, Copy)]
4pub enum Fill {
5 Solid(Color),
6 Translucent(Color, f32),
7 /// Hatches(color, angle, thickness_ratio, spacing)
8 Hatches(Color, Angle, f32, f32),
9 /// Dotted(color, diameter, spacing)
10 Dotted(Color, f32, f32),
11}
12
13impl Color {
14 pub fn solid(self) -> Fill {
15 Fill::Solid(self)
16 }
17
18 pub fn translucent(self, opacity: f32) -> Fill {
19 Fill::Translucent(self, opacity)
20 }
21
22 pub fn hatches(self, angle: Angle, thickness: f32, spacing: f32) -> Fill {
23 Fill::Hatches(self, angle, thickness, spacing)
24 }
25
26 pub fn dotted(self, diameter: f32, spacing: f32) -> Fill {
27 Fill::Dotted(self, diameter, spacing)
28 }
29}
30
31// Operations that can be applied on fills.
32pub trait FillOperations {
33 fn opacify(&self, opacity: f32) -> Self;
34}
35
36impl FillOperations for Fill {
37 fn opacify(&self, opacity: f32) -> Self {
38 match self {
39 Fill::Solid(color) => Fill::Translucent(*color, opacity),
40 Fill::Translucent(color, _) => Fill::Translucent(*color, opacity),
41 _ => *self,
42 }
43 }
44}
45
46impl FillOperations for Option<Fill> {
47 fn opacify(&self, opacity: f32) -> Self {
48 self.map(|fill| fill.opacify(opacity))
49 }
50}
51
52impl Fill {
53 pub fn bottom_up_hatches(color: Color, thickness: f32, spacing: f32) -> Self {
54 Fill::Hatches(color, Angle(45.0), thickness, spacing)
55 }
56
57 pub fn pattern_id(&self) -> String {
58 if let Fill::Hatches(color, angle, thickness, spacing) = self {
59 return format!(
60 "pattern-hatched-{}-{}-{}-{}",
61 angle,
62 color.name(),
63 thickness,
64 spacing
65 );
66 }
67 if let Fill::Dotted(color, diameter, spacing) = self {
68 return format!(
69 "pattern-dotted-{}-{}-{}",
70 color.name(),
71 diameter,
72 spacing
73 );
74 }
75 String::from("")
76 }
77
78 pub fn pattern_definition(
79 &self,
80 colormapping: &ColorMapping,
81 ) -> Option<svg::Node> {
82 match self {
83 Fill::Hatches(color, angle, size, thickness_ratio) => {
84 let thickness = size * (2.0 * thickness_ratio);
85
86 let pattern = svg::tag("pattern")
87 .attr("id", self.pattern_id())
88 .attr("patternUnits", "userSpaceOnUse")
89 .attr("height", size * 2.0)
90 .attr("width", size * 2.0)
91 .attr("viewBox", format!("0,0,{},{}", size, size))
92 .attr(
93 "patternTransform",
94 format!("rotate({})", (*angle - Angle(45.0)).degrees()),
95 )
96 // https://stackoverflow.com/a/55104220/9943464
97 .wrapping(vec![
98 svg::tag("polygon").fill(*color, colormapping).attr(
99 "points",
100 format!(
101 "0,0 {},0 0,{}",
102 thickness / 2.0,
103 thickness / 2.0
104 ),
105 ),
106 svg::tag("polygon").fill(*color, colormapping).attr(
107 "points",
108 format!(
109 "0,{} {},0 {},{} {},{}",
110 size,
111 size,
112 size,
113 thickness / 2.0,
114 thickness / 2.0,
115 size,
116 ),
117 ),
118 ])
119 .node();
120
121 Some(pattern)
122 }
123 Fill::Dotted(color, diameter, spacing) => {
124 let box_size = diameter + 2.0 * spacing;
125 let pattern = svg::tag("pattern")
126 .attr("id", self.pattern_id())
127 .attr("patternUnits", "userSpaceOnUse")
128 .attr("height", box_size)
129 .attr("width", box_size)
130 .attr("viewBox", format!("0,0,{},{}", box_size, box_size))
131 .wrapping(vec![
132 svg::tag("circle")
133 .fill(*color, colormapping)
134 .attr("cx", box_size / 2.0)
135 .attr("cy", box_size / 2.0)
136 .attr("r", diameter / 2.0),
137 ])
138 .node();
139
140 Some(pattern)
141 }
142 _ => None,
143 }
144 }
145}