src
geometry
graphics
random
rendering
···
1
1
-
use crate::{Shape, Point};
1
1
+
use crate::{Point, Shape};
2
2
use anyhow::{Error, Result, anyhow, format_err};
3
3
#[cfg(feature = "web")]
4
4
use wasm_bindgen::prelude::*;
···
1
1
-
use self::Shape::*;
2
2
-
use anyhow::anyhow;
3
3
-
use crate::{Point, Region, Containable};
4
4
-
5
5
-
#[derive(Debug, Clone, PartialEq, Eq)]
6
6
-
pub enum LineSegment {
7
7
-
Straight(Point),
8
8
-
InwardCurve(Point),
9
9
-
OutwardCurve(Point),
10
10
-
}
11
11
-
12
12
-
#[derive(Debug, Clone)]
13
13
-
pub enum Shape {
14
14
-
Polygon(Point, Vec<LineSegment>),
15
15
-
Line(Point, Point, f32),
16
16
-
CurveOutward(Point, Point, f32),
17
17
-
CurveInward(Point, Point, f32),
18
18
-
SmallCircle(Point),
19
19
-
BigDot(Point),
20
20
-
Dot(Point),
21
21
-
BigCircle(Point),
22
22
-
Text(Point, String, f32),
23
23
-
CenteredText(Point, String, f32),
24
24
-
// FittedText(Region, String),
25
25
-
Rectangle(Point, Point),
26
26
-
Image(Region, String),
27
27
-
RawSVG(String),
28
28
-
// Tiling(Region, Box<Object>),
29
29
-
}
30
30
-
31
31
-
impl Shape {
32
32
-
pub fn translate(&mut self, dx: i32, dy: i32) {
33
33
-
match self {
34
34
-
Polygon(start, lines) => {
35
35
-
start.translate(dx, dy);
36
36
-
for line in lines {
37
37
-
match line {
38
38
-
LineSegment::InwardCurve(anchor)
39
39
-
| LineSegment::OutwardCurve(anchor)
40
40
-
| LineSegment::Straight(anchor) => {
41
41
-
anchor.translate(dx, dy)
42
42
-
}
43
43
-
}
44
44
-
}
45
45
-
}
46
46
-
Line(start, end, _)
47
47
-
| CurveInward(start, end, _)
48
48
-
| CurveOutward(start, end, _)
49
49
-
| Rectangle(start, end) => {
50
50
-
start.translate(dx, dy);
51
51
-
end.translate(dx, dy);
52
52
-
}
53
53
-
Text(anchor, _, _)
54
54
-
| CenteredText(anchor, ..)
55
55
-
| Dot(anchor)
56
56
-
| BigDot(anchor) => anchor.translate(dx, dy),
57
57
-
BigCircle(center) | SmallCircle(center) => center.translate(dx, dy),
58
58
-
Image(region, ..) => region.translate(dx, dy),
59
59
-
RawSVG(_) => {
60
60
-
unimplemented!()
61
61
-
}
62
62
-
}
63
63
-
}
64
64
-
65
65
-
pub fn translate_with(&mut self, delta: (i32, i32)) {
66
66
-
self.translate(delta.0, delta.1)
67
67
-
}
68
68
-
69
69
-
pub fn teleport(&mut self, x: i32, y: i32) {
70
70
-
let (current_x, current_y) = self.region().start.xy::<i32>();
71
71
-
let delta_x = x - current_x;
72
72
-
let delta_y = y - current_y;
73
73
-
self.translate(delta_x, delta_y);
74
74
-
}
75
75
-
76
76
-
pub fn teleport_with(&mut self, position: (i32, i32)) {
77
77
-
self.teleport(position.0, position.1)
78
78
-
}
79
79
-
80
80
-
pub fn region(&self) -> Region {
81
81
-
match self {
82
82
-
Polygon(start, lines) => {
83
83
-
let mut region: Region = (start, start).into();
84
84
-
for line in lines {
85
85
-
match line {
86
86
-
LineSegment::InwardCurve(anchor)
87
87
-
| LineSegment::OutwardCurve(anchor)
88
88
-
| LineSegment::Straight(anchor) => {
89
89
-
// println!(
90
90
-
// "extending region {} with {}",
91
91
-
// region,
92
92
-
// Region::from((start, anchor))
93
93
-
// );
94
94
-
region = *region.max(&(start, anchor).into())
95
95
-
}
96
96
-
}
97
97
-
}
98
98
-
// println!("region for {:?} -> {}", self, region);
99
99
-
region
100
100
-
}
101
101
-
Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => {
102
102
-
let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x());
103
103
-
104
104
-
let region = Region::new(
105
105
-
(x1.min(x2), y1.min(y2)),
106
106
-
(x1.max(x2), y1.max(y2)),
107
107
-
)
108
108
-
.map_err(|e| {
109
109
-
anyhow!("Could not construct region of {self:?}: {e:?}")
110
110
-
})
111
111
-
.unwrap();
112
112
-
113
113
-
region.enlarged(
114
114
-
if region.width() > 1 { -1 } else { 0 },
115
115
-
if region.height() > 1 { -1 } else { 0 },
116
116
-
)
117
117
-
}
118
118
-
Rectangle(start, end) => {
119
119
-
Region::new(*start, *end).unwrap().enlarged(-1, -1)
120
120
-
}
121
121
-
Text(anchor, _, _)
122
122
-
| CenteredText(anchor, ..)
123
123
-
| Dot(anchor)
124
124
-
| BigDot(anchor)
125
125
-
| SmallCircle(anchor) => anchor.region(),
126
126
-
BigCircle(center) => center.region(),
127
127
-
Image(region, ..) => *region,
128
128
-
RawSVG(_) => {
129
129
-
unimplemented!()
130
130
-
}
131
131
-
}
132
132
-
}
133
133
-
134
134
-
pub fn fillable(&self) -> bool {
135
135
-
!matches!(self, Line(..) | CurveInward(..) | CurveOutward(..))
136
136
-
}
137
137
-
138
138
-
pub fn hatchable(&self) -> bool {
139
139
-
self.fillable() && !matches!(self, Dot(..))
140
140
-
}
141
141
-
142
142
-
pub fn point_is_on_line(self, point: Point) -> bool {
143
143
-
match (&self, point) {
144
144
-
(Line(s, e, _), Point::Corner(x, y)) => {
145
145
-
if !self.region().contains(&point) {
146
146
-
return false;
147
147
-
}
148
148
-
149
149
-
let (sx, sy) = s.xy::<f32>();
150
150
-
let (ex, ey) = e.xy::<f32>();
151
151
-
152
152
-
let m = (ey - sy) / (ex - sx);
153
153
-
let p = sy - m * sx;
154
154
-
155
155
-
(m * x as f32 + p) as usize == y
156
156
-
}
157
157
-
(Line(..), _) => panic!("Point type not supported"),
158
158
-
_ => panic!("{self:?} is not a line object"),
159
159
-
}
160
160
-
}
161
161
-
162
162
-
//
163
163
-
// ```rs
164
164
-
// use shapemaker::{Line, Point::Corner};
165
165
-
// let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0);
166
166
-
// assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1)));
167
167
-
// assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4)));
168
168
-
// ```
169
169
-
pub fn intersects_with(&self, line: Shape) -> bool {
170
170
-
match (self, &line) {
171
171
-
(&Line(s1, e1, _), &Line(s2, e2, _)) => {
172
172
-
let (dx1, dy1) = e1 - s1;
173
173
-
let (dx2, dy2) = e2 - s2;
174
174
-
let (dx3, dy3) = s2 - s1;
175
175
-
176
176
-
let det = dx1 * dy2 - dy1 * dx2;
177
177
-
178
178
-
let det1 = dx1 * dy3 - dx3 * dy1;
179
179
-
let det2 = dx2 * dy3 - dx3 * dy2;
180
180
-
181
181
-
if det == 0 {
182
182
-
if det1 != 0 || det2 != 0 {
183
183
-
return false;
184
184
-
}
185
185
-
186
186
-
let (x1, y1) = s1.xy::<isize>();
187
187
-
let (x2, y2) = e1.xy::<isize>();
188
188
-
let (x3, y3) = s2.xy::<isize>();
189
189
-
190
190
-
if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) {
191
191
-
return true;
192
192
-
}
193
193
-
194
194
-
if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) {
195
195
-
return true;
196
196
-
}
197
197
-
198
198
-
return false;
199
199
-
}
200
200
-
201
201
-
let frac_less_than_one = |num: isize, den: isize| {
202
202
-
if num.signum() != den.signum() {
203
203
-
return false;
204
204
-
}
205
205
-
206
206
-
num.abs() <= den.abs()
207
207
-
};
208
208
-
209
209
-
frac_less_than_one(det1, det) && frac_less_than_one(det2, det)
210
210
-
}
211
211
-
_ => {
212
212
-
unimplemented!(
213
213
-
"Intersection not implemented for {self:?} and {:?}",
214
214
-
line.clone()
215
215
-
)
216
216
-
}
217
217
-
}
218
218
-
}
219
219
-
}
1
1
+
use self::Shape::*;
2
2
+
use crate::{Containable, Point, Region};
3
3
+
use anyhow::anyhow;
4
4
+
5
5
+
#[derive(Debug, Clone, PartialEq, Eq)]
6
6
+
pub enum LineSegment {
7
7
+
Straight(Point),
8
8
+
InwardCurve(Point),
9
9
+
OutwardCurve(Point),
10
10
+
}
11
11
+
12
12
+
#[derive(Debug, Clone)]
13
13
+
pub enum Shape {
14
14
+
Polygon(Point, Vec<LineSegment>),
15
15
+
Line(Point, Point, f32),
16
16
+
CurveOutward(Point, Point, f32),
17
17
+
CurveInward(Point, Point, f32),
18
18
+
SmallCircle(Point),
19
19
+
BigDot(Point),
20
20
+
Dot(Point),
21
21
+
BigCircle(Point),
22
22
+
Text(Point, String, f32),
23
23
+
CenteredText(Point, String, f32),
24
24
+
// FittedText(Region, String),
25
25
+
Rectangle(Point, Point),
26
26
+
Image(Region, String),
27
27
+
RawSVG(String),
28
28
+
// Tiling(Region, Box<Object>),
29
29
+
}
30
30
+
31
31
+
impl Shape {
32
32
+
pub fn translate(&mut self, dx: i32, dy: i32) {
33
33
+
match self {
34
34
+
Polygon(start, lines) => {
35
35
+
start.translate(dx, dy);
36
36
+
for line in lines {
37
37
+
match line {
38
38
+
LineSegment::InwardCurve(anchor)
39
39
+
| LineSegment::OutwardCurve(anchor)
40
40
+
| LineSegment::Straight(anchor) => {
41
41
+
anchor.translate(dx, dy)
42
42
+
}
43
43
+
}
44
44
+
}
45
45
+
}
46
46
+
Line(start, end, _)
47
47
+
| CurveInward(start, end, _)
48
48
+
| CurveOutward(start, end, _)
49
49
+
| Rectangle(start, end) => {
50
50
+
start.translate(dx, dy);
51
51
+
end.translate(dx, dy);
52
52
+
}
53
53
+
Text(anchor, _, _)
54
54
+
| CenteredText(anchor, ..)
55
55
+
| Dot(anchor)
56
56
+
| BigDot(anchor) => anchor.translate(dx, dy),
57
57
+
BigCircle(center) | SmallCircle(center) => center.translate(dx, dy),
58
58
+
Image(region, ..) => region.translate(dx, dy),
59
59
+
RawSVG(_) => {
60
60
+
unimplemented!()
61
61
+
}
62
62
+
}
63
63
+
}
64
64
+
65
65
+
pub fn translate_with(&mut self, delta: (i32, i32)) {
66
66
+
self.translate(delta.0, delta.1)
67
67
+
}
68
68
+
69
69
+
pub fn teleport(&mut self, x: i32, y: i32) {
70
70
+
let (current_x, current_y) = self.region().start.xy::<i32>();
71
71
+
let delta_x = x - current_x;
72
72
+
let delta_y = y - current_y;
73
73
+
self.translate(delta_x, delta_y);
74
74
+
}
75
75
+
76
76
+
pub fn teleport_with(&mut self, position: (i32, i32)) {
77
77
+
self.teleport(position.0, position.1)
78
78
+
}
79
79
+
80
80
+
pub fn region(&self) -> Region {
81
81
+
match self {
82
82
+
Polygon(start, lines) => {
83
83
+
let mut region: Region = (start, start).into();
84
84
+
for line in lines {
85
85
+
match line {
86
86
+
LineSegment::InwardCurve(anchor)
87
87
+
| LineSegment::OutwardCurve(anchor)
88
88
+
| LineSegment::Straight(anchor) => {
89
89
+
// println!(
90
90
+
// "extending region {} with {}",
91
91
+
// region,
92
92
+
// Region::from((start, anchor))
93
93
+
// );
94
94
+
region = *region.max(&(start, anchor).into())
95
95
+
}
96
96
+
}
97
97
+
}
98
98
+
// println!("region for {:?} -> {}", self, region);
99
99
+
region
100
100
+
}
101
101
+
Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => {
102
102
+
let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x());
103
103
+
104
104
+
let region = Region::new(
105
105
+
(x1.min(x2), y1.min(y2)),
106
106
+
(x1.max(x2), y1.max(y2)),
107
107
+
)
108
108
+
.map_err(|e| {
109
109
+
anyhow!("Could not construct region of {self:?}: {e:?}")
110
110
+
})
111
111
+
.unwrap();
112
112
+
113
113
+
region.enlarged(
114
114
+
if region.width() > 1 { -1 } else { 0 },
115
115
+
if region.height() > 1 { -1 } else { 0 },
116
116
+
)
117
117
+
}
118
118
+
Rectangle(start, end) => {
119
119
+
Region::new(*start, *end).unwrap().enlarged(-1, -1)
120
120
+
}
121
121
+
Text(anchor, _, _)
122
122
+
| CenteredText(anchor, ..)
123
123
+
| Dot(anchor)
124
124
+
| BigDot(anchor)
125
125
+
| SmallCircle(anchor) => anchor.region(),
126
126
+
BigCircle(center) => center.region(),
127
127
+
Image(region, ..) => *region,
128
128
+
RawSVG(_) => {
129
129
+
unimplemented!()
130
130
+
}
131
131
+
}
132
132
+
}
133
133
+
134
134
+
pub fn fillable(&self) -> bool {
135
135
+
!matches!(self, Line(..) | CurveInward(..) | CurveOutward(..))
136
136
+
}
137
137
+
138
138
+
pub fn hatchable(&self) -> bool {
139
139
+
self.fillable() && !matches!(self, Dot(..))
140
140
+
}
141
141
+
142
142
+
pub fn point_is_on_line(self, point: Point) -> bool {
143
143
+
match (&self, point) {
144
144
+
(Line(s, e, _), Point::Corner(x, y)) => {
145
145
+
if !self.region().contains(&point) {
146
146
+
return false;
147
147
+
}
148
148
+
149
149
+
let (sx, sy) = s.xy::<f32>();
150
150
+
let (ex, ey) = e.xy::<f32>();
151
151
+
152
152
+
let m = (ey - sy) / (ex - sx);
153
153
+
let p = sy - m * sx;
154
154
+
155
155
+
(m * x as f32 + p) as usize == y
156
156
+
}
157
157
+
(Line(..), _) => panic!("Point type not supported"),
158
158
+
_ => panic!("{self:?} is not a line object"),
159
159
+
}
160
160
+
}
161
161
+
162
162
+
//
163
163
+
// ```rs
164
164
+
// use shapemaker::{Line, Point::Corner};
165
165
+
// let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0);
166
166
+
// assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1)));
167
167
+
// assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4)));
168
168
+
// ```
169
169
+
pub fn intersects_with(&self, line: Shape) -> bool {
170
170
+
match (self, &line) {
171
171
+
(&Line(s1, e1, _), &Line(s2, e2, _)) => {
172
172
+
let (dx1, dy1) = e1 - s1;
173
173
+
let (dx2, dy2) = e2 - s2;
174
174
+
let (dx3, dy3) = s2 - s1;
175
175
+
176
176
+
let det = dx1 * dy2 - dy1 * dx2;
177
177
+
178
178
+
let det1 = dx1 * dy3 - dx3 * dy1;
179
179
+
let det2 = dx2 * dy3 - dx3 * dy2;
180
180
+
181
181
+
if det == 0 {
182
182
+
if det1 != 0 || det2 != 0 {
183
183
+
return false;
184
184
+
}
185
185
+
186
186
+
let (x1, y1) = s1.xy::<isize>();
187
187
+
let (x2, y2) = e1.xy::<isize>();
188
188
+
let (x3, y3) = s2.xy::<isize>();
189
189
+
190
190
+
if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) {
191
191
+
return true;
192
192
+
}
193
193
+
194
194
+
if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) {
195
195
+
return true;
196
196
+
}
197
197
+
198
198
+
return false;
199
199
+
}
200
200
+
201
201
+
let frac_less_than_one = |num: isize, den: isize| {
202
202
+
if num.signum() != den.signum() {
203
203
+
return false;
204
204
+
}
205
205
+
206
206
+
num.abs() <= den.abs()
207
207
+
};
208
208
+
209
209
+
frac_less_than_one(det1, det) && frac_less_than_one(det2, det)
210
210
+
}
211
211
+
_ => {
212
212
+
unimplemented!(
213
213
+
"Intersection not implemented for {self:?} and {:?}",
214
214
+
line.clone()
215
215
+
)
216
216
+
}
217
217
+
}
218
218
+
}
219
219
+
}
···
1
1
-
use crate::{Canvas, Object, Fill, Layer, Shape, Region};
1
1
+
use crate::{Canvas, Fill, Layer, Object, Region, Shape};
2
2
use rand::{Rng, distr::uniform::SampleRange};
3
3
use std::collections::HashMap;
4
4
···
1
1
use rand::{Rng, distr::uniform::SampleRange};
2
2
3
3
-
use crate::{Object, LineSegment, Point, Region, Shape};
3
3
+
use crate::{LineSegment, Object, Point, Region, Shape};
4
4
5
5
impl Shape {
6
6
pub fn random_starting_at<R: rand::Rng>(
···
3
3
pub mod filter;
4
4
pub mod fonts;
5
5
pub mod layer;
6
6
-
pub mod shapes;
7
6
pub mod objects;
8
7
pub mod rasterization;
9
8
pub mod renderable;
9
9
+
pub mod shapes;
10
10
pub mod svg;
11
11
pub mod transform;
12
12
···
1
1
-
use measure_time::debug_time;
2
2
-
3
3
-
use crate::{LineSegment, Shape, graphics::objects::ObjectSizes};
4
4
-
5
5
-
use super::{
6
6
-
renderable::SVGRenderable, svg,
7
7
-
};
8
8
-
9
9
-
impl SVGRenderable for Shape {
10
10
-
fn render_to_svg(
11
11
-
&self,
12
12
-
_colormap: crate::ColorMapping,
13
13
-
cell_size: usize,
14
14
-
object_sizes: crate::graphics::objects::ObjectSizes,
15
15
-
id: &str,
16
16
-
) -> anyhow::Result<svg::Node> {
17
17
-
debug_time!("render_to_svg/object");
18
18
-
let rendered = match self {
19
19
-
Shape::Text(..) | Shape::CenteredText(..) => {
20
20
-
self.render_text(cell_size)
21
21
-
}
22
22
-
Shape::Rectangle(..) => self.render_rectangle(cell_size),
23
23
-
Shape::Polygon(..) => self.render_polygon(cell_size),
24
24
-
Shape::Line(..) => self.render_line(cell_size),
25
25
-
Shape::CurveInward(..) | Shape::CurveOutward(..) => {
26
26
-
self.render_curve(cell_size)
27
27
-
}
28
28
-
Shape::BigDot(..)
29
29
-
| Shape::Dot(..)
30
30
-
| Shape::BigCircle(..)
31
31
-
| Shape::SmallCircle(..) => {
32
32
-
self.render_circle(cell_size, object_sizes)
33
33
-
}
34
34
-
Shape::Image(..) => self.render_image(cell_size),
35
35
-
Shape::RawSVG(..) => self.render_raw_svg(),
36
36
-
};
37
37
-
38
38
-
Ok(match rendered {
39
39
-
svg::Node::Element(el) => el.dataset("object", id).into(),
40
40
-
svg::Node::SVG(svg) => {
41
41
-
if svg.trim().starts_with("<") {
42
42
-
let (before, after) =
43
43
-
svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >"));
44
44
-
svg::Node::SVG(format!(
45
45
-
r#"{before} data-object="{id}" {after}"#
46
46
-
))
47
47
-
} else {
48
48
-
eprintln!("Malformed raw SVG, {svg} doesn't start with <");
49
49
-
svg::Node::SVG(svg)
50
50
-
}
51
51
-
}
52
52
-
_ => {
53
53
-
panic!("Expected Element or SVG, got {:?}", rendered);
54
54
-
}
55
55
-
})
56
56
-
}
57
57
-
}
58
58
-
59
59
-
impl Shape {
60
60
-
fn render_image(&self, cell_size: usize) -> svg::Node {
61
61
-
if let Shape::Image(region, path) = self {
62
62
-
return svg::tag("image")
63
63
-
.coords(region.start.coords(cell_size))
64
64
-
.attr("width", region.width() * cell_size)
65
65
-
.attr("height", region.height() * cell_size)
66
66
-
.attr("href", path.clone())
67
67
-
.into();
68
68
-
}
69
69
-
70
70
-
panic!("Expected Image, got {:?}", self);
71
71
-
}
72
72
-
73
73
-
fn render_raw_svg(&self) -> svg::Node {
74
74
-
if let Shape::RawSVG(svg) = self {
75
75
-
return svg::Node::SVG(svg.clone());
76
76
-
}
77
77
-
78
78
-
panic!("Expected RawSVG, got {:?}", self);
79
79
-
}
80
80
-
81
81
-
fn render_text(&self, cell_size: usize) -> svg::Node {
82
82
-
match self {
83
83
-
Shape::Text(position, content, font_size)
84
84
-
| Shape::CenteredText(position, content, font_size) => {
85
85
-
let centered = matches!(self, Shape::CenteredText(..));
86
86
-
87
87
-
svg::tag("text")
88
88
-
.coords(
89
89
-
if centered {
90
90
-
position.as_centered()
91
91
-
} else {
92
92
-
position.as_corner()
93
93
-
}
94
94
-
.coords(cell_size),
95
95
-
)
96
96
-
.attr("font-size", format!("{}pt", font_size))
97
97
-
.attr("font-family", "Inconsolata")
98
98
-
.attr(
99
99
-
"dominant-baseline",
100
100
-
if centered { "middle" } else { "hanging" },
101
101
-
)
102
102
-
.attr(
103
103
-
"text-anchor",
104
104
-
if centered { "middle" } else { "start" },
105
105
-
)
106
106
-
.wrapping(vec![svg::Node::Text(content.to_string())])
107
107
-
.into()
108
108
-
}
109
109
-
_ => panic!("Expected Text, got {:?}", self),
110
110
-
}
111
111
-
}
112
112
-
113
113
-
// fn render_fitted_text(&self, cell_size: usize) -> svg::Node {
114
114
-
// if let Object::FittedText(region, content) = self {
115
115
-
// let (x, y) = region.start.coords(cell_size);
116
116
-
// let width = region.width() * cell_size as f32;
117
117
-
// let height = region.height() * cell_size as f32;
118
118
-
119
119
-
// return Box::new(
120
120
-
// svg::node::element::Text::new(content.clone())
121
121
-
// .set("x", x)
122
122
-
// .set("y", y)
123
123
-
// .set("")
124
124
-
// .set("font-size", format!("{}pt", 10.0))
125
125
-
// .set("font-family", "sans-serif"),
126
126
-
// );
127
127
-
// }
128
128
-
129
129
-
// panic!("Expected FittedText, got {:?}", self);
130
130
-
// }
131
131
-
132
132
-
fn render_rectangle(&self, cell_size: usize) -> svg::Node {
133
133
-
if let Shape::Rectangle(start, end) = self {
134
134
-
return svg::tag("rect").region((start, end), cell_size).into();
135
135
-
}
136
136
-
137
137
-
panic!("Expected Rectangle, got {:?}", self);
138
138
-
}
139
139
-
140
140
-
fn render_polygon(&self, cell_size: usize) -> svg::Node {
141
141
-
if let Shape::Polygon(start, lines) = self {
142
142
-
let mut path = svg::Path::new();
143
143
-
path.move_to(*start, cell_size);
144
144
-
for line in lines {
145
145
-
match line {
146
146
-
LineSegment::Straight(end)
147
147
-
| LineSegment::InwardCurve(end)
148
148
-
| LineSegment::OutwardCurve(end) => {
149
149
-
path.line_to(*end, cell_size);
150
150
-
}
151
151
-
};
152
152
-
}
153
153
-
path.close();
154
154
-
return path.node();
155
155
-
}
156
156
-
157
157
-
panic!("Expected Polygon, got {:?}", self);
158
158
-
}
159
159
-
160
160
-
fn render_line(&self, cell_size: usize) -> svg::Node {
161
161
-
if let Shape::Line(start, end, width) = self {
162
162
-
return svg::tag("line")
163
163
-
.position_pair(*start, *end, cell_size)
164
164
-
.attr("stroke-width", *width)
165
165
-
.into();
166
166
-
}
167
167
-
168
168
-
panic!("Expected Line, got {:?}", self);
169
169
-
}
170
170
-
171
171
-
fn render_curve(&self, cell_size: usize) -> svg::Node {
172
172
-
if let Shape::CurveOutward(start, end, stroke_width)
173
173
-
| Shape::CurveInward(start, end, stroke_width) = self
174
174
-
{
175
175
-
let inward = matches!(self, Shape::CurveInward(..));
176
176
-
177
177
-
let (start_x, start_y) = start.coords(cell_size);
178
178
-
let (end_x, end_y) = end.coords(cell_size);
179
179
-
180
180
-
let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0);
181
181
-
let start_from_midpoint =
182
182
-
(start_x - midpoint.0, start_y - midpoint.1);
183
183
-
let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1);
184
184
-
185
185
-
let control = {
186
186
-
let relative = (end_x - start_x, end_y - start_y);
187
187
-
if start_from_midpoint.0 * start_from_midpoint.1 > 0.0
188
188
-
&& end_from_midpoint.0 * end_from_midpoint.1 > 0.0
189
189
-
{
190
190
-
if inward {
191
191
-
(
192
192
-
midpoint.0 + relative.0.abs() / 2.0,
193
193
-
midpoint.1 - relative.1.abs() / 2.0,
194
194
-
)
195
195
-
} else {
196
196
-
(
197
197
-
midpoint.0 - relative.0.abs() / 2.0,
198
198
-
midpoint.1 + relative.1.abs() / 2.0,
199
199
-
)
200
200
-
}
201
201
-
// diagonal line is going like this: /
202
202
-
} else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0
203
203
-
&& end_from_midpoint.0 * end_from_midpoint.1 < 0.0
204
204
-
{
205
205
-
if inward {
206
206
-
(
207
207
-
midpoint.0 - relative.0.abs() / 2.0,
208
208
-
midpoint.1 - relative.1.abs() / 2.0,
209
209
-
)
210
210
-
} else {
211
211
-
(
212
212
-
midpoint.0 + relative.0.abs() / 2.0,
213
213
-
midpoint.1 + relative.1.abs() / 2.0,
214
214
-
)
215
215
-
}
216
216
-
// line is horizontal
217
217
-
} else if start_y == end_y {
218
218
-
(
219
219
-
midpoint.0,
220
220
-
midpoint.1
221
221
-
+ (if inward { -1.0 } else { 1.0 })
222
222
-
* relative.0.abs()
223
223
-
/ 2.0,
224
224
-
)
225
225
-
// line is vertical
226
226
-
} else if start_x == end_x {
227
227
-
(
228
228
-
midpoint.0
229
229
-
+ (if inward { -1.0 } else { 1.0 })
230
230
-
* relative.1.abs()
231
231
-
/ 2.0,
232
232
-
midpoint.1,
233
233
-
)
234
234
-
} else {
235
235
-
unreachable!()
236
236
-
}
237
237
-
};
238
238
-
239
239
-
let mut path = svg::Path::new();
240
240
-
path.move_to(*start, cell_size);
241
241
-
path.quadratic_curve_to(control, *end, cell_size);
242
242
-
return path
243
243
-
.element()
244
244
-
.attr("stroke-width", format!("{stroke_width}"))
245
245
-
.into();
246
246
-
}
247
247
-
248
248
-
panic!("Expected Curve, got {:?}", self);
249
249
-
}
250
250
-
251
251
-
fn render_circle(
252
252
-
&self,
253
253
-
cell_size: usize,
254
254
-
object_sizes: ObjectSizes,
255
255
-
) -> svg::Node {
256
256
-
let center = match self {
257
257
-
Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size),
258
258
-
Shape::BigCircle(at) | Shape::SmallCircle(at) => {
259
259
-
at.as_centered().coords(cell_size)
260
260
-
}
261
261
-
262
262
-
_ => panic!(
263
263
-
"Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}",
264
264
-
self
265
265
-
),
266
266
-
};
267
267
-
268
268
-
let radius = match self {
269
269
-
Shape::BigDot(_) => object_sizes.small_circle_radius,
270
270
-
Shape::Dot(_) => object_sizes.dot_radius,
271
271
-
Shape::BigCircle(_) => cell_size as f32 / 2.0,
272
272
-
Shape::SmallCircle(_) => object_sizes.small_circle_radius,
273
273
-
_ => unreachable!(),
274
274
-
};
275
275
-
276
276
-
return svg::tag("circle")
277
277
-
.attr("cx", center.0)
278
278
-
.attr("cy", center.1)
279
279
-
.attr("r", radius)
280
280
-
.into();
281
281
-
}
282
282
-
}
1
1
+
use measure_time::debug_time;
2
2
+
3
3
+
use crate::{LineSegment, Shape, graphics::objects::ObjectSizes};
4
4
+
5
5
+
use super::{renderable::SVGRenderable, svg};
6
6
+
7
7
+
impl SVGRenderable for Shape {
8
8
+
fn render_to_svg(
9
9
+
&self,
10
10
+
_colormap: crate::ColorMapping,
11
11
+
cell_size: usize,
12
12
+
object_sizes: crate::graphics::objects::ObjectSizes,
13
13
+
id: &str,
14
14
+
) -> anyhow::Result<svg::Node> {
15
15
+
debug_time!("render_to_svg/object");
16
16
+
let rendered = match self {
17
17
+
Shape::Text(..) | Shape::CenteredText(..) => {
18
18
+
self.render_text(cell_size)
19
19
+
}
20
20
+
Shape::Rectangle(..) => self.render_rectangle(cell_size),
21
21
+
Shape::Polygon(..) => self.render_polygon(cell_size),
22
22
+
Shape::Line(..) => self.render_line(cell_size),
23
23
+
Shape::CurveInward(..) | Shape::CurveOutward(..) => {
24
24
+
self.render_curve(cell_size)
25
25
+
}
26
26
+
Shape::BigDot(..)
27
27
+
| Shape::Dot(..)
28
28
+
| Shape::BigCircle(..)
29
29
+
| Shape::SmallCircle(..) => {
30
30
+
self.render_circle(cell_size, object_sizes)
31
31
+
}
32
32
+
Shape::Image(..) => self.render_image(cell_size),
33
33
+
Shape::RawSVG(..) => self.render_raw_svg(),
34
34
+
};
35
35
+
36
36
+
Ok(match rendered {
37
37
+
svg::Node::Element(el) => el.dataset("object", id).into(),
38
38
+
svg::Node::SVG(svg) => {
39
39
+
if svg.trim().starts_with("<") {
40
40
+
let (before, after) =
41
41
+
svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >"));
42
42
+
svg::Node::SVG(format!(
43
43
+
r#"{before} data-object="{id}" {after}"#
44
44
+
))
45
45
+
} else {
46
46
+
eprintln!("Malformed raw SVG, {svg} doesn't start with <");
47
47
+
svg::Node::SVG(svg)
48
48
+
}
49
49
+
}
50
50
+
_ => {
51
51
+
panic!("Expected Element or SVG, got {:?}", rendered);
52
52
+
}
53
53
+
})
54
54
+
}
55
55
+
}
56
56
+
57
57
+
impl Shape {
58
58
+
fn render_image(&self, cell_size: usize) -> svg::Node {
59
59
+
if let Shape::Image(region, path) = self {
60
60
+
return svg::tag("image")
61
61
+
.coords(region.start.coords(cell_size))
62
62
+
.attr("width", region.width() * cell_size)
63
63
+
.attr("height", region.height() * cell_size)
64
64
+
.attr("href", path.clone())
65
65
+
.into();
66
66
+
}
67
67
+
68
68
+
panic!("Expected Image, got {:?}", self);
69
69
+
}
70
70
+
71
71
+
fn render_raw_svg(&self) -> svg::Node {
72
72
+
if let Shape::RawSVG(svg) = self {
73
73
+
return svg::Node::SVG(svg.clone());
74
74
+
}
75
75
+
76
76
+
panic!("Expected RawSVG, got {:?}", self);
77
77
+
}
78
78
+
79
79
+
fn render_text(&self, cell_size: usize) -> svg::Node {
80
80
+
match self {
81
81
+
Shape::Text(position, content, font_size)
82
82
+
| Shape::CenteredText(position, content, font_size) => {
83
83
+
let centered = matches!(self, Shape::CenteredText(..));
84
84
+
85
85
+
svg::tag("text")
86
86
+
.coords(
87
87
+
if centered {
88
88
+
position.as_centered()
89
89
+
} else {
90
90
+
position.as_corner()
91
91
+
}
92
92
+
.coords(cell_size),
93
93
+
)
94
94
+
.attr("font-size", format!("{}pt", font_size))
95
95
+
.attr("font-family", "Inconsolata")
96
96
+
.attr(
97
97
+
"dominant-baseline",
98
98
+
if centered { "middle" } else { "hanging" },
99
99
+
)
100
100
+
.attr(
101
101
+
"text-anchor",
102
102
+
if centered { "middle" } else { "start" },
103
103
+
)
104
104
+
.wrapping(vec![svg::Node::Text(content.to_string())])
105
105
+
.into()
106
106
+
}
107
107
+
_ => panic!("Expected Text, got {:?}", self),
108
108
+
}
109
109
+
}
110
110
+
111
111
+
// fn render_fitted_text(&self, cell_size: usize) -> svg::Node {
112
112
+
// if let Object::FittedText(region, content) = self {
113
113
+
// let (x, y) = region.start.coords(cell_size);
114
114
+
// let width = region.width() * cell_size as f32;
115
115
+
// let height = region.height() * cell_size as f32;
116
116
+
117
117
+
// return Box::new(
118
118
+
// svg::node::element::Text::new(content.clone())
119
119
+
// .set("x", x)
120
120
+
// .set("y", y)
121
121
+
// .set("")
122
122
+
// .set("font-size", format!("{}pt", 10.0))
123
123
+
// .set("font-family", "sans-serif"),
124
124
+
// );
125
125
+
// }
126
126
+
127
127
+
// panic!("Expected FittedText, got {:?}", self);
128
128
+
// }
129
129
+
130
130
+
fn render_rectangle(&self, cell_size: usize) -> svg::Node {
131
131
+
if let Shape::Rectangle(start, end) = self {
132
132
+
return svg::tag("rect").region((start, end), cell_size).into();
133
133
+
}
134
134
+
135
135
+
panic!("Expected Rectangle, got {:?}", self);
136
136
+
}
137
137
+
138
138
+
fn render_polygon(&self, cell_size: usize) -> svg::Node {
139
139
+
if let Shape::Polygon(start, lines) = self {
140
140
+
let mut path = svg::Path::new();
141
141
+
path.move_to(*start, cell_size);
142
142
+
for line in lines {
143
143
+
match line {
144
144
+
LineSegment::Straight(end)
145
145
+
| LineSegment::InwardCurve(end)
146
146
+
| LineSegment::OutwardCurve(end) => {
147
147
+
path.line_to(*end, cell_size);
148
148
+
}
149
149
+
};
150
150
+
}
151
151
+
path.close();
152
152
+
return path.node();
153
153
+
}
154
154
+
155
155
+
panic!("Expected Polygon, got {:?}", self);
156
156
+
}
157
157
+
158
158
+
fn render_line(&self, cell_size: usize) -> svg::Node {
159
159
+
if let Shape::Line(start, end, width) = self {
160
160
+
return svg::tag("line")
161
161
+
.position_pair(*start, *end, cell_size)
162
162
+
.attr("stroke-width", *width)
163
163
+
.into();
164
164
+
}
165
165
+
166
166
+
panic!("Expected Line, got {:?}", self);
167
167
+
}
168
168
+
169
169
+
fn render_curve(&self, cell_size: usize) -> svg::Node {
170
170
+
if let Shape::CurveOutward(start, end, stroke_width)
171
171
+
| Shape::CurveInward(start, end, stroke_width) = self
172
172
+
{
173
173
+
let inward = matches!(self, Shape::CurveInward(..));
174
174
+
175
175
+
let (start_x, start_y) = start.coords(cell_size);
176
176
+
let (end_x, end_y) = end.coords(cell_size);
177
177
+
178
178
+
let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0);
179
179
+
let start_from_midpoint =
180
180
+
(start_x - midpoint.0, start_y - midpoint.1);
181
181
+
let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1);
182
182
+
183
183
+
let control = {
184
184
+
let relative = (end_x - start_x, end_y - start_y);
185
185
+
if start_from_midpoint.0 * start_from_midpoint.1 > 0.0
186
186
+
&& end_from_midpoint.0 * end_from_midpoint.1 > 0.0
187
187
+
{
188
188
+
if inward {
189
189
+
(
190
190
+
midpoint.0 + relative.0.abs() / 2.0,
191
191
+
midpoint.1 - relative.1.abs() / 2.0,
192
192
+
)
193
193
+
} else {
194
194
+
(
195
195
+
midpoint.0 - relative.0.abs() / 2.0,
196
196
+
midpoint.1 + relative.1.abs() / 2.0,
197
197
+
)
198
198
+
}
199
199
+
// diagonal line is going like this: /
200
200
+
} else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0
201
201
+
&& end_from_midpoint.0 * end_from_midpoint.1 < 0.0
202
202
+
{
203
203
+
if inward {
204
204
+
(
205
205
+
midpoint.0 - relative.0.abs() / 2.0,
206
206
+
midpoint.1 - relative.1.abs() / 2.0,
207
207
+
)
208
208
+
} else {
209
209
+
(
210
210
+
midpoint.0 + relative.0.abs() / 2.0,
211
211
+
midpoint.1 + relative.1.abs() / 2.0,
212
212
+
)
213
213
+
}
214
214
+
// line is horizontal
215
215
+
} else if start_y == end_y {
216
216
+
(
217
217
+
midpoint.0,
218
218
+
midpoint.1
219
219
+
+ (if inward { -1.0 } else { 1.0 })
220
220
+
* relative.0.abs()
221
221
+
/ 2.0,
222
222
+
)
223
223
+
// line is vertical
224
224
+
} else if start_x == end_x {
225
225
+
(
226
226
+
midpoint.0
227
227
+
+ (if inward { -1.0 } else { 1.0 })
228
228
+
* relative.1.abs()
229
229
+
/ 2.0,
230
230
+
midpoint.1,
231
231
+
)
232
232
+
} else {
233
233
+
unreachable!()
234
234
+
}
235
235
+
};
236
236
+
237
237
+
let mut path = svg::Path::new();
238
238
+
path.move_to(*start, cell_size);
239
239
+
path.quadratic_curve_to(control, *end, cell_size);
240
240
+
return path
241
241
+
.element()
242
242
+
.attr("stroke-width", format!("{stroke_width}"))
243
243
+
.into();
244
244
+
}
245
245
+
246
246
+
panic!("Expected Curve, got {:?}", self);
247
247
+
}
248
248
+
249
249
+
fn render_circle(
250
250
+
&self,
251
251
+
cell_size: usize,
252
252
+
object_sizes: ObjectSizes,
253
253
+
) -> svg::Node {
254
254
+
let center = match self {
255
255
+
Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size),
256
256
+
Shape::BigCircle(at) | Shape::SmallCircle(at) => {
257
257
+
at.as_centered().coords(cell_size)
258
258
+
}
259
259
+
260
260
+
_ => panic!(
261
261
+
"Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}",
262
262
+
self
263
263
+
),
264
264
+
};
265
265
+
266
266
+
let radius = match self {
267
267
+
Shape::BigDot(_) => object_sizes.small_circle_radius,
268
268
+
Shape::Dot(_) => object_sizes.dot_radius,
269
269
+
Shape::BigCircle(_) => cell_size as f32 / 2.0,
270
270
+
Shape::SmallCircle(_) => object_sizes.small_circle_radius,
271
271
+
_ => unreachable!(),
272
272
+
};
273
273
+
274
274
+
return svg::tag("circle")
275
275
+
.attr("cx", center.0)
276
276
+
.attr("cy", center.1)
277
277
+
.attr("r", radius)
278
278
+
.into();
279
279
+
}
280
280
+
}