1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect};
6
7use crate::path::{FillRule, Path};
8use crate::raster::{self, FillStyle, RasterOptions};
9use crate::stroke::{self, StrokeStyle};
10use crate::{Span, union_rect};
11
12#[derive(Clone, Debug, PartialEq)]
13pub struct Shape {
14 pub spans: Rc<[Span]>,
15
16 pub bounds: Rect,
17}
18
19impl Shape {
20 pub fn new(spans: Vec<Span>) -> Self {
21 let mut bounds = Rect::ZERO;
22 for span in &spans {
23 bounds = union_rect(bounds, span.rect);
24 }
25 Self {
26 spans: spans.into(),
27 bounds,
28 }
29 }
30
31 pub fn empty() -> Self {
32 Self {
33 spans: Rc::from(Vec::new()),
34 bounds: Rect::ZERO,
35 }
36 }
37
38 pub fn len(&self) -> usize {
39 self.spans.len()
40 }
41
42 pub fn is_empty(&self) -> bool {
43 self.spans.is_empty()
44 }
45
46 pub fn iter(&self) -> std::slice::Iter<'_, Span> {
47 self.spans.iter()
48 }
49}
50
51impl<'a> IntoIterator for &'a Shape {
52 type Item = &'a Span;
53 type IntoIter = std::slice::Iter<'a, Span>;
54
55 fn into_iter(self) -> Self::IntoIter {
56 self.spans.iter()
57 }
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub struct ShapeStats {
62 pub hits: u64,
63
64 pub misses: u64,
65
66 pub evictions: u64,
67}
68
69impl ShapeStats {
70 pub fn hit_rate(&self) -> f32 {
71 let total = self.hits + self.misses;
72 if total == 0 {
73 0.0
74 } else {
75 self.hits as f32 / total as f32
76 }
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
81struct Key {
82 kind: u8,
83 fingerprint: u64,
84 style: u64,
85 opts: u64,
86}
87
88#[derive(Debug)]
89struct Entry {
90 shape: Rc<Shape>,
91 used: u64,
92}
93
94pub const DEFAULT_CAPACITY: usize = 256;
95
96#[derive(Debug)]
97pub struct ShapeCache {
98 inner: RefCell<Inner>,
99}
100
101#[derive(Debug)]
102struct Inner {
103 entries: HashMap<Key, Entry>,
104 capacity: usize,
105 tick: u64,
106 stats: ShapeStats,
107}
108
109impl Default for ShapeCache {
110 fn default() -> Self {
111 Self::new(DEFAULT_CAPACITY)
112 }
113}
114
115impl ShapeCache {
116 pub fn new(capacity: usize) -> Self {
117 Self {
118 inner: RefCell::new(Inner {
119 entries: HashMap::new(),
120 capacity,
121 tick: 0,
122 stats: ShapeStats::default(),
123 }),
124 }
125 }
126
127 pub fn capacity(&self) -> usize {
128 self.inner.borrow().capacity
129 }
130
131 pub fn len(&self) -> usize {
132 self.inner.borrow().entries.len()
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.inner.borrow().entries.is_empty()
137 }
138
139 pub fn stats(&self) -> ShapeStats {
140 self.inner.borrow().stats
141 }
142
143 pub fn reset_stats(&self) {
144 self.inner.borrow_mut().stats = ShapeStats::default();
145 }
146
147 pub fn clear(&self) {
148 self.inner.borrow_mut().entries.clear();
149 }
150
151 pub fn fill(&self, path: &Path, rule: FillRule, opts: RasterOptions) -> Rc<Shape> {
152 self.fill_styled(path, FillStyle::new(rule, opts))
153 }
154
155 pub fn fill_styled(&self, path: &Path, style: FillStyle) -> Rc<Shape> {
156 let key = Key {
157 kind: 0,
158 fingerprint: path.fingerprint(),
159 style: style.rule as u64,
160 opts: opts_key(style.opts),
161 };
162 self.get_or_build(key, |out| {
163 raster::fill(path, style.rule, style.opts, out);
164 })
165 }
166
167 pub fn stroke(&self, path: &Path, style: &StrokeStyle, opts: RasterOptions) -> Rc<Shape> {
168 let key = Key {
169 kind: 1,
170 fingerprint: path.fingerprint(),
171 style: style.fingerprint(),
172 opts: opts_key(opts),
173 };
174 self.get_or_build(key, |out| {
175 stroke::stroke_path(path, style, opts, out);
176 })
177 }
178
179 pub fn fill_contours(
180 &self,
181 contours: &[crate::Contour],
182 rule: FillRule,
183 opts: RasterOptions,
184 ) -> Rc<Shape> {
185 let key = Key {
186 kind: 2,
187 fingerprint: contours_fingerprint(contours),
188 style: rule as u64,
189 opts: opts_key(opts),
190 };
191 self.get_or_build(key, |out| {
192 raster::fill_contours(contours, rule, opts, out);
193 })
194 }
195
196 pub fn disk(&self, center: Point, radius: f32) -> Rc<Shape> {
197 if !(radius > 0.0) || !radius.is_finite() {
198 return Rc::new(Shape::empty());
199 }
200 Rc::new(Shape::new(vec![Span::rounded(
201 Rect::from_xywh(
202 center.x - radius,
203 center.y - radius,
204 radius * 2.0,
205 radius * 2.0,
206 ),
207 radius,
208 )]))
209 }
210
211 pub fn rounded_rect(&self, rect: Rect, radius: f32) -> Rc<Shape> {
212 let radius = radius
213 .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
214 .max(0.0);
215 Rc::new(Shape::new(vec![Span::rounded(rect, radius)]))
216 }
217
218 pub fn rect(&self, rect: Rect) -> Rc<Shape> {
219 Rc::new(Shape::new(vec![Span::rect(rect)]))
220 }
221
222 fn get_or_build(&self, key: Key, build: impl FnOnce(&mut Vec<Span>)) -> Rc<Shape> {
223 let mut inner = self.inner.borrow_mut();
224 inner.tick += 1;
225 let tick = inner.tick;
226 if let Some(entry) = inner.entries.get_mut(&key) {
227 entry.used = tick;
228 let shape = entry.shape.clone();
229 inner.stats.hits += 1;
230 return shape;
231 }
232 inner.stats.misses += 1;
233 if inner.capacity == 0 {
234 drop(inner);
235 let mut spans = Vec::new();
236 build(&mut spans);
237 return Rc::new(Shape::new(spans));
238 }
239
240 let mut spans = Vec::new();
241 build(&mut spans);
242 let shape = Rc::new(Shape::new(spans));
243 if inner.entries.len() >= inner.capacity
244 && let Some(oldest) = inner
245 .entries
246 .iter()
247 .min_by_key(|(_, entry)| entry.used)
248 .map(|(key, _)| *key)
249 {
250 inner.entries.remove(&oldest);
251 inner.stats.evictions += 1;
252 }
253 inner.entries.insert(
254 key,
255 Entry {
256 shape: shape.clone(),
257 used: tick,
258 },
259 );
260 shape
261 }
262}
263
264fn opts_key(opts: RasterOptions) -> u64 {
265 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
266 for v in [opts.tolerance, opts.row_height, opts.merge_tol] {
267 hash ^= u64::from(v.to_bits());
268 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
269 }
270 hash ^= u64::from(opts.aa_samples);
271 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
272 hash ^= u64::from(opts.analytic);
273 hash.wrapping_mul(0x0000_0100_0000_01b3)
274}
275
276fn contours_fingerprint(contours: &[crate::Contour]) -> u64 {
277 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
278 let mut eat = |v: u64| {
279 hash ^= v;
280 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
281 };
282 for c in contours {
283 eat(c.points.len() as u64);
284 eat(u64::from(c.closed));
285 for p in &c.points {
286 eat(u64::from(p.x.to_bits()));
287 eat(u64::from(p.y.to_bits()));
288 }
289 }
290 hash
291}