1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use sequoia_core::{Point, Rect, Vec2};
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 offset: u64,
87}
88
89#[derive(Debug)]
90struct Entry {
91 shape: Rc<Shape>,
92 used: u64,
93}
94
95pub const DEFAULT_CAPACITY: usize = 256;
96
97#[derive(Debug)]
98pub struct ShapeCache {
99 inner: RefCell<Inner>,
100}
101
102#[derive(Debug)]
103struct Inner {
104 entries: HashMap<Key, Entry>,
105 capacity: usize,
106 tick: u64,
107 stats: ShapeStats,
108}
109
110impl Default for ShapeCache {
111 fn default() -> Self {
112 Self::new(DEFAULT_CAPACITY)
113 }
114}
115
116impl ShapeCache {
117 pub fn new(capacity: usize) -> Self {
118 Self {
119 inner: RefCell::new(Inner {
120 entries: HashMap::new(),
121 capacity,
122 tick: 0,
123 stats: ShapeStats::default(),
124 }),
125 }
126 }
127
128 pub fn capacity(&self) -> usize {
129 self.inner.borrow().capacity
130 }
131
132 pub fn len(&self) -> usize {
133 self.inner.borrow().entries.len()
134 }
135
136 pub fn is_empty(&self) -> bool {
137 self.inner.borrow().entries.is_empty()
138 }
139
140 pub fn stats(&self) -> ShapeStats {
141 self.inner.borrow().stats
142 }
143
144 pub fn reset_stats(&self) {
145 self.inner.borrow_mut().stats = ShapeStats::default();
146 }
147
148 pub fn clear(&self) {
149 self.inner.borrow_mut().entries.clear();
150 }
151
152 pub fn fill(&self, path: &Path, rule: FillRule, opts: RasterOptions) -> Rc<Shape> {
153 self.fill_styled(path, FillStyle::new(rule, opts))
154 }
155
156 pub fn fill_styled(&self, path: &Path, style: FillStyle) -> Rc<Shape> {
157 let key = Key {
158 kind: 0,
159 fingerprint: path.fingerprint(),
160 style: style.rule as u64,
161 opts: opts_key(style.opts),
162 offset: 0,
163 };
164 self.get_or_build(key, |out| {
165 raster::fill(path, style.rule, style.opts, out);
166 })
167 }
168
169 pub fn fill_offset(
170 &self,
171 path: &Path,
172 rule: FillRule,
173 opts: RasterOptions,
174 offset: Vec2,
175 ) -> Rc<Shape> {
176 let key = Key {
177 kind: 0,
178 fingerprint: path.fingerprint(),
179 style: rule as u64,
180 opts: opts_key(opts),
181 offset: offset_key(offset),
182 };
183 self.get_or_build(key, |out| {
184 let translated = path.translated(offset.x, offset.y);
185 raster::fill(&translated, rule, opts, out);
186 })
187 }
188
189 pub fn stroke(&self, path: &Path, style: &StrokeStyle, opts: RasterOptions) -> Rc<Shape> {
190 let key = Key {
191 kind: 1,
192 fingerprint: path.fingerprint(),
193 style: style.fingerprint(),
194 opts: opts_key(opts),
195 offset: 0,
196 };
197 self.get_or_build(key, |out| {
198 stroke::stroke_path(path, style, opts, out);
199 })
200 }
201
202 pub fn stroke_offset(
203 &self,
204 path: &Path,
205 style: &StrokeStyle,
206 opts: RasterOptions,
207 offset: Vec2,
208 ) -> Rc<Shape> {
209 let key = Key {
210 kind: 1,
211 fingerprint: path.fingerprint(),
212 style: style.fingerprint(),
213 opts: opts_key(opts),
214 offset: offset_key(offset),
215 };
216 self.get_or_build(key, |out| {
217 let translated = path.translated(offset.x, offset.y);
218 stroke::stroke_path(&translated, style, opts, out);
219 })
220 }
221
222 pub fn fill_contours(
223 &self,
224 contours: &[crate::Contour],
225 rule: FillRule,
226 opts: RasterOptions,
227 ) -> Rc<Shape> {
228 let key = Key {
229 kind: 2,
230 fingerprint: contours_fingerprint(contours),
231 style: rule as u64,
232 opts: opts_key(opts),
233 offset: 0,
234 };
235 self.get_or_build(key, |out| {
236 raster::fill_contours(contours, rule, opts, out);
237 })
238 }
239
240 pub fn disk(&self, center: Point, radius: f32) -> Rc<Shape> {
241 if !(radius > 0.0) || !radius.is_finite() {
242 return Rc::new(Shape::empty());
243 }
244 Rc::new(Shape::new(vec![Span::rounded(
245 Rect::from_xywh(
246 center.x - radius,
247 center.y - radius,
248 radius * 2.0,
249 radius * 2.0,
250 ),
251 radius,
252 )]))
253 }
254
255 pub fn rounded_rect(&self, rect: Rect, radius: f32) -> Rc<Shape> {
256 let radius = radius
257 .clamp(0.0, rect.size.width.min(rect.size.height) * 0.5)
258 .max(0.0);
259 Rc::new(Shape::new(vec![Span::rounded(rect, radius)]))
260 }
261
262 pub fn rect(&self, rect: Rect) -> Rc<Shape> {
263 Rc::new(Shape::new(vec![Span::rect(rect)]))
264 }
265
266 fn get_or_build(&self, key: Key, build: impl FnOnce(&mut Vec<Span>)) -> Rc<Shape> {
267 let mut inner = self.inner.borrow_mut();
268 inner.tick += 1;
269 let tick = inner.tick;
270 if let Some(entry) = inner.entries.get_mut(&key) {
271 entry.used = tick;
272 let shape = entry.shape.clone();
273 inner.stats.hits += 1;
274 return shape;
275 }
276 inner.stats.misses += 1;
277 if inner.capacity == 0 {
278 drop(inner);
279 let mut spans = Vec::new();
280 build(&mut spans);
281 return Rc::new(Shape::new(spans));
282 }
283
284 let mut spans = Vec::new();
285 build(&mut spans);
286 let shape = Rc::new(Shape::new(spans));
287 if inner.entries.len() >= inner.capacity
288 && let Some(oldest) = inner
289 .entries
290 .iter()
291 .min_by_key(|(_, entry)| entry.used)
292 .map(|(key, _)| *key)
293 {
294 inner.entries.remove(&oldest);
295 inner.stats.evictions += 1;
296 }
297 inner.entries.insert(
298 key,
299 Entry {
300 shape: shape.clone(),
301 used: tick,
302 },
303 );
304 shape
305 }
306}
307
308fn offset_key(offset: Vec2) -> u64 {
309 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
310 for v in [offset.x, offset.y] {
311 let v = if v == 0.0 { 0.0 } else { v };
312 hash ^= u64::from(v.to_bits());
313 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
314 }
315 hash
316}
317
318fn opts_key(opts: RasterOptions) -> u64 {
319 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
320 for v in [opts.tolerance, opts.row_height, opts.merge_tol] {
321 hash ^= u64::from(v.to_bits());
322 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
323 }
324 hash ^= u64::from(opts.aa_samples);
325 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
326 hash ^= u64::from(opts.analytic);
327 hash.wrapping_mul(0x0000_0100_0000_01b3)
328}
329
330fn contours_fingerprint(contours: &[crate::Contour]) -> u64 {
331 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
332 let mut eat = |v: u64| {
333 hash ^= v;
334 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
335 };
336 for c in contours {
337 eat(c.points.len() as u64);
338 eat(u64::from(c.closed));
339 for p in &c.points {
340 eat(u64::from(p.x.to_bits()));
341 eat(u64::from(p.y.to_bits()));
342 }
343 }
344 hash
345}