This repository has no description
1use crate::{Angle, Containable, Fill, Filter, Point, Region, Transformation};
2use anyhow::anyhow;
3use itertools::Itertools;
4use std::fmt::Display;
5#[cfg(feature = "web")]
6use wasm_bindgen::prelude::*;
7
8use super::{Color, fill::FillOperations};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum LineSegment {
12 Straight(Point),
13 InwardCurve(Point),
14 OutwardCurve(Point),
15}
16
17#[derive(Debug, Clone)]
18pub enum Object {
19 Polygon(Point, Vec<LineSegment>),
20 Line(Point, Point, f32),
21 CurveOutward(Point, Point, f32),
22 CurveInward(Point, Point, f32),
23 SmallCircle(Point),
24 BigDot(Point),
25 Dot(Point),
26 BigCircle(Point),
27 Text(Point, String, f32),
28 CenteredText(Point, String, f32),
29 // FittedText(Region, String),
30 Rectangle(Point, Point),
31 Image(Region, String),
32 RawSVG(String),
33 // Tiling(Region, Box<Object>),
34}
35
36impl Object {
37 pub fn filled(self, fill: Fill) -> ColoredObject {
38 ColoredObject::from((self, Some(fill)))
39 }
40
41 pub fn colored(self, color: Color) -> ColoredObject {
42 ColoredObject::from((self, None)).colored(color)
43 }
44
45 pub fn filtered(self, filter: Filter) -> ColoredObject {
46 ColoredObject::from((self, None)).filtered(filter)
47 }
48
49 pub fn transform(self, transformation: Transformation) -> ColoredObject {
50 ColoredObject::from((self, None)).transformed(transformation)
51 }
52}
53
54#[derive(Debug, Clone)]
55pub struct ColoredObject {
56 pub object: Object,
57 pub fill: Option<Fill>,
58 pub filters: Vec<Filter>,
59 pub transformations: Vec<Transformation>,
60 pub tags: Vec<String>,
61 pub clip_to: Option<Region>,
62}
63
64impl ColoredObject {
65 pub fn filtered(mut self, filter: Filter) -> Self {
66 self.filters.push(filter);
67 self
68 }
69
70 pub fn transformed(mut self, transformation: Transformation) -> Self {
71 self.transformations.push(transformation);
72 self
73 }
74
75 pub fn filled(mut self, fill: Fill) -> Self {
76 self.fill = Some(fill);
77 self
78 }
79
80 pub fn colored(mut self, color: Color) -> Self {
81 self.fill = Some(Fill::Solid(color));
82 self
83 }
84
85 pub fn opacified(mut self, opacity: f32) -> Self {
86 if let Some(fill) = &mut self.fill {
87 *fill = fill.opacify(opacity);
88 }
89 self
90 }
91
92 pub fn clipped_to(mut self, region: impl Into<Region>) -> Self {
93 self.clip_to = Some(region.into());
94 self
95 }
96
97 pub fn clear_filters(&mut self) {
98 self.filters.clear();
99 }
100
101 pub fn refill(&mut self, fill: Fill) {
102 self.fill = Some(fill);
103 }
104
105 pub fn recolor(&mut self, color: Color) {
106 self.fill = Some(Fill::Solid(color))
107 }
108
109 pub fn filter(&mut self, filter: Filter) {
110 self.filters.push(filter)
111 }
112
113 pub fn rotate(&mut self, angle: Angle) {
114 self.transformations
115 .push(Transformation::Rotate(angle.degrees()))
116 }
117
118 pub fn set_rotation(&mut self, angle: Angle) {
119 self.transformations
120 .retain(|t| !matches!(t, Transformation::Rotate(_)));
121 self.transformations
122 .push(Transformation::Rotate(angle.degrees()))
123 }
124
125 pub fn region(&self) -> Region {
126 self.object.region()
127 }
128
129 pub fn tag(&mut self, tag: impl Display) {
130 self.tags.push(format!("{tag}"));
131 }
132
133 pub fn remove_tag(&mut self, tag: impl Display) {
134 let tag_str = format!("{tag}");
135 self.tags.retain(|t| t != &tag_str);
136 }
137
138 pub fn tagged(mut self, tag: impl Display) -> Self {
139 self.tags.push(format!("{tag}"));
140 self
141 }
142
143 pub fn has_tag(&self, tag: impl Display) -> bool {
144 let tag_str = format!("{tag}");
145 self.tags.iter().any(|t| t == &tag_str)
146 }
147}
148
149impl std::fmt::Display for ColoredObject {
150 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
151 let ColoredObject {
152 object,
153 fill,
154 filters,
155 transformations,
156 tags,
157 clip_to,
158 } = self;
159
160 if fill.is_some() {
161 write!(f, "{:?} {:?}", fill.unwrap(), object)?;
162 } else {
163 write!(f, "transparent {:?}", object)?;
164 }
165
166 if !filters.is_empty() {
167 write!(f, " with filters {:?}", filters)?;
168 }
169
170 if !transformations.is_empty() {
171 write!(f, " with transformations {:?}", transformations)?;
172 }
173
174 if !tags.is_empty() {
175 write!(f, "{}", tags.iter().map(|t| format!("#{t}")).join(" "))?;
176 }
177
178 if let Some(clip_to) = clip_to {
179 write!(f, " (clipped to {:?})", clip_to)?;
180 }
181
182 Ok(())
183 }
184}
185
186impl From<Object> for ColoredObject {
187 fn from(value: Object) -> Self {
188 ColoredObject {
189 object: value,
190 fill: None,
191 filters: vec![],
192 transformations: vec![],
193 tags: vec![],
194 clip_to: None,
195 }
196 }
197}
198
199impl From<(Object, Option<Fill>)> for ColoredObject {
200 fn from((object, fill): (Object, Option<Fill>)) -> Self {
201 ColoredObject {
202 object,
203 fill,
204 filters: vec![],
205 transformations: vec![],
206 tags: vec![],
207 clip_to: None,
208 }
209 }
210}
211
212#[cfg_attr(feature = "web", wasm_bindgen)]
213#[derive(Debug, Clone, Copy)]
214pub struct ObjectSizes {
215 pub empty_shape_stroke_width: f32,
216 pub small_circle_radius: f32,
217 pub dot_radius: f32,
218 pub default_line_width: f32,
219}
220
221impl Default for ObjectSizes {
222 fn default() -> Self {
223 Self {
224 empty_shape_stroke_width: 0.5,
225 small_circle_radius: 5.0,
226 dot_radius: 2.0,
227 default_line_width: 2.0,
228 }
229 }
230}
231
232impl Object {
233 pub fn translate(&mut self, dx: i32, dy: i32) {
234 match self {
235 Object::Polygon(start, lines) => {
236 start.translate(dx, dy);
237 for line in lines {
238 match line {
239 LineSegment::InwardCurve(anchor)
240 | LineSegment::OutwardCurve(anchor)
241 | LineSegment::Straight(anchor) => {
242 anchor.translate(dx, dy)
243 }
244 }
245 }
246 }
247 Object::Line(start, end, _)
248 | Object::CurveInward(start, end, _)
249 | Object::CurveOutward(start, end, _)
250 | Object::Rectangle(start, end) => {
251 start.translate(dx, dy);
252 end.translate(dx, dy);
253 }
254 Object::Text(anchor, _, _)
255 | Object::CenteredText(anchor, ..)
256 | Object::Dot(anchor)
257 | Object::BigDot(anchor) => anchor.translate(dx, dy),
258 Object::BigCircle(center) | Object::SmallCircle(center) => {
259 center.translate(dx, dy)
260 }
261 Object::Image(region, ..) => region.translate(dx, dy),
262 Object::RawSVG(_) => {
263 unimplemented!()
264 }
265 }
266 }
267
268 pub fn translate_with(&mut self, delta: (i32, i32)) {
269 self.translate(delta.0, delta.1)
270 }
271
272 pub fn teleport(&mut self, x: i32, y: i32) {
273 let (current_x, current_y) = self.region().start.xy::<i32>();
274 let delta_x = x - current_x;
275 let delta_y = y - current_y;
276 self.translate(delta_x, delta_y);
277 }
278
279 pub fn teleport_with(&mut self, position: (i32, i32)) {
280 self.teleport(position.0, position.1)
281 }
282
283 pub fn region(&self) -> Region {
284 match self {
285 Object::Polygon(start, lines) => {
286 let mut region: Region = (start, start).into();
287 for line in lines {
288 match line {
289 LineSegment::InwardCurve(anchor)
290 | LineSegment::OutwardCurve(anchor)
291 | LineSegment::Straight(anchor) => {
292 // println!(
293 // "extending region {} with {}",
294 // region,
295 // Region::from((start, anchor))
296 // );
297 region = *region.max(&(start, anchor).into())
298 }
299 }
300 }
301 // println!("region for {:?} -> {}", self, region);
302 region
303 }
304 Object::Line(s, e, _)
305 | Object::CurveInward(s, e, _)
306 | Object::CurveOutward(s, e, _) => {
307 let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x());
308
309 let region = Region::new(
310 (x1.min(x2), y1.min(y2)),
311 (x1.max(x2), y1.max(y2)),
312 )
313 .map_err(|e| {
314 anyhow!("Could not construct region of {self:?}: {e:?}")
315 })
316 .unwrap();
317
318 region.enlarged(
319 if region.width() > 1 { -1 } else { 0 },
320 if region.height() > 1 { -1 } else { 0 },
321 )
322 }
323 Object::Rectangle(start, end) => {
324 Region::new(*start, *end).unwrap().enlarged(-1, -1)
325 }
326 Object::Text(anchor, _, _)
327 | Object::CenteredText(anchor, ..)
328 | Object::Dot(anchor)
329 | Object::BigDot(anchor)
330 | Object::SmallCircle(anchor) => anchor.region(),
331 Object::BigCircle(center) => center.region(),
332 Object::Image(region, ..) => *region,
333 Object::RawSVG(_) => {
334 unimplemented!()
335 }
336 }
337 }
338
339 pub fn fillable(&self) -> bool {
340 !matches!(
341 self,
342 Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..)
343 )
344 }
345
346 pub fn hatchable(&self) -> bool {
347 self.fillable() && !matches!(self, Object::Dot(..))
348 }
349
350 pub fn point_is_on_line(self, point: Point) -> bool {
351 match (&self, point) {
352 (Object::Line(s, e, _), Point::Corner(x, y)) => {
353 if !self.region().contains(&point) {
354 return false;
355 }
356
357 let (sx, sy) = s.xy::<f32>();
358 let (ex, ey) = e.xy::<f32>();
359
360 let m = (ey - sy) / (ex - sx);
361 let p = sy - m * sx;
362
363 (m * x as f32 + p) as usize == y
364 }
365 (Object::Line(..), _) => panic!("Point type not supported"),
366 _ => panic!("{self:?} is not a line object"),
367 }
368 }
369
370 //
371 // ```rs
372 // use shapemaker::{Object::Line, Point::Corner};
373 // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0);
374 // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1)));
375 // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4)));
376 // ```
377 pub fn intersects_with(&self, line: Object) -> bool {
378 match (self, &line) {
379 (&Object::Line(s1, e1, _), &Object::Line(s2, e2, _)) => {
380 let (dx1, dy1) = e1 - s1;
381 let (dx2, dy2) = e2 - s2;
382 let (dx3, dy3) = s2 - s1;
383
384 let det = dx1 * dy2 - dy1 * dx2;
385
386 let det1 = dx1 * dy3 - dx3 * dy1;
387 let det2 = dx2 * dy3 - dx3 * dy2;
388
389 if det == 0 {
390 if det1 != 0 || det2 != 0 {
391 return false;
392 }
393
394 let (x1, y1) = s1.xy::<isize>();
395 let (x2, y2) = e1.xy::<isize>();
396 let (x3, y3) = s2.xy::<isize>();
397
398 if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) {
399 return true;
400 }
401
402 if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) {
403 return true;
404 }
405
406 return false;
407 }
408
409 let frac_less_than_one = |num: isize, den: isize| {
410 if num.signum() != den.signum() {
411 return false;
412 }
413
414 num.abs() <= den.abs()
415 };
416
417 frac_less_than_one(det1, det) && frac_less_than_one(det2, det)
418 }
419 _ => {
420 unimplemented!(
421 "Intersection not implemented for {self:?} and {:?}",
422 line.clone()
423 )
424 }
425 }
426 }
427}