···
1
1
+
---
2
2
+
tags:
3
3
+
- graphism
4
4
+
- programming
5
5
+
- experiment
6
6
+
- language
7
7
+
- command line
8
8
+
made with:
9
9
+
- svg
10
10
+
- rust
11
11
+
---
12
12
+
13
13
+
# Shapemaker
14
14
+
15
15
+
:: en
16
16
+
17
17
+
I wanted to dabble in generative art, and, as a first project, create a way to generate small, icon-like shapes from a predefined set of possible lines, curves and circles
18
18
+
19
19
+
## A restricted set of shapes
20
20
+
21
21
+
![]()
22
22
+
23
23
+
Those shapes are described as sequences of elements, such as lines, either straight or curved, that start and end on fixed "anchor points": south-west, south, south-east, east, north-east, north, north-west, west and center.
24
24
+
25
25
+
Lines can be grouped to form polygons (with some edges possibly curved), which can be filled with patterns (hatched or dotted) or colors.
26
26
+
27
27
+
Additionally, dots, points (small circles) or circles can be added. The latter objects take up a full quarter of the block and can be placed in each quarter's center. These can be filled just like polygons.
28
28
+
29
29
+
## A language to describe these shapes
30
30
+
31
31
+
I needed a language to describe these shapes with text, so that a program could generate random shapes, while keeping them coherent by reducing the amount of variables needed (randomly generating SVG text directly, for example, is unfeasable).
32
32
+
33
33
+
```
34
34
+
percent:
35
35
+
top left circle
36
36
+
bottom right -- top left
37
37
+
bottom right circle
38
38
+
39
39
+
abstract1:
40
40
+
[ bottom left -- left ) top -- bottom -- bottom left ] filled with green
41
41
+
top -- right -- bottom
42
42
+
top left red dot
43
43
+
44
44
+
lowercase j:
45
45
+
center point
46
46
+
center -- bottom -- bottom left
47
47
+
```
48
48
+
49
49
+
50
50
+
The idea was to generate SVG from this representation.
···
1
1
+
<shape name="percent">
2
2
+
<big-circle at="top left">
3
3
+
<big-circle at="bottom right">
4
4
+
<line from="bottom left" to="top right">
5
5
+
</shape>
6
6
+
7
7
+
<shape name="abstract1">
8
8
+
<polygon hatched>
9
9
+
<line from="bottom left" to="left">
10
10
+
<curve down from="left" to="top">
11
11
+
<line points="top, right, bottom">
12
12
+
</polygon>
13
13
+
</shape>
···
1
1
+
shapes:
2
2
+
percent:
3
3
+
- top left: big circle
4
4
+
- bottom right: big circle
5
5
+
- bottom left to top right: line
6
6
+
abstract1:
7
7
+
- hatched polygon:
8
8
+
- bottom left to left: line
9
9
+
- left to top: curve down
10
10
+
- bottom to bottom left: line
11
11
+
- top to right: line
12
12
+
- right to bottom: line
···
1
1
+
# This file is automatically @generated by Cargo.
2
2
+
# It is not intended for manual editing.
3
3
+
version = 3
4
4
+
5
5
+
[[package]]
6
6
+
name = "ahash"
7
7
+
version = "0.3.8"
8
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
9
+
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
10
10
+
dependencies = [
11
11
+
"const-random",
12
12
+
]
13
13
+
14
14
+
[[package]]
15
15
+
name = "cfg-if"
16
16
+
version = "1.0.0"
17
17
+
source = "registry+https://github.com/rust-lang/crates.io-index"
18
18
+
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
19
19
+
20
20
+
[[package]]
21
21
+
name = "chumsky"
22
22
+
version = "0.8.0"
23
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
24
+
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
25
25
+
dependencies = [
26
26
+
"ahash",
27
27
+
]
28
28
+
29
29
+
[[package]]
30
30
+
name = "const-random"
31
31
+
version = "0.1.15"
32
32
+
source = "registry+https://github.com/rust-lang/crates.io-index"
33
33
+
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
34
34
+
dependencies = [
35
35
+
"const-random-macro",
36
36
+
"proc-macro-hack",
37
37
+
]
38
38
+
39
39
+
[[package]]
40
40
+
name = "const-random-macro"
41
41
+
version = "0.1.15"
42
42
+
source = "registry+https://github.com/rust-lang/crates.io-index"
43
43
+
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
44
44
+
dependencies = [
45
45
+
"getrandom",
46
46
+
"once_cell",
47
47
+
"proc-macro-hack",
48
48
+
"tiny-keccak",
49
49
+
]
50
50
+
51
51
+
[[package]]
52
52
+
name = "crunchy"
53
53
+
version = "0.2.2"
54
54
+
source = "registry+https://github.com/rust-lang/crates.io-index"
55
55
+
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
56
56
+
57
57
+
[[package]]
58
58
+
name = "docopt"
59
59
+
version = "1.1.1"
60
60
+
source = "registry+https://github.com/rust-lang/crates.io-index"
61
61
+
checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f"
62
62
+
dependencies = [
63
63
+
"lazy_static",
64
64
+
"regex",
65
65
+
"serde",
66
66
+
"strsim",
67
67
+
]
68
68
+
69
69
+
[[package]]
70
70
+
name = "getrandom"
71
71
+
version = "0.2.8"
72
72
+
source = "registry+https://github.com/rust-lang/crates.io-index"
73
73
+
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
74
74
+
dependencies = [
75
75
+
"cfg-if",
76
76
+
"libc",
77
77
+
"wasi",
78
78
+
]
79
79
+
80
80
+
[[package]]
81
81
+
name = "lazy_static"
82
82
+
version = "1.4.0"
83
83
+
source = "registry+https://github.com/rust-lang/crates.io-index"
84
84
+
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
85
85
+
86
86
+
[[package]]
87
87
+
name = "libc"
88
88
+
version = "0.2.137"
89
89
+
source = "registry+https://github.com/rust-lang/crates.io-index"
90
90
+
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
91
91
+
92
92
+
[[package]]
93
93
+
name = "once_cell"
94
94
+
version = "1.16.0"
95
95
+
source = "registry+https://github.com/rust-lang/crates.io-index"
96
96
+
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
97
97
+
98
98
+
[[package]]
99
99
+
name = "proc-macro-hack"
100
100
+
version = "0.5.19"
101
101
+
source = "registry+https://github.com/rust-lang/crates.io-index"
102
102
+
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
103
103
+
104
104
+
[[package]]
105
105
+
name = "proc-macro2"
106
106
+
version = "1.0.47"
107
107
+
source = "registry+https://github.com/rust-lang/crates.io-index"
108
108
+
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
109
109
+
dependencies = [
110
110
+
"unicode-ident",
111
111
+
]
112
112
+
113
113
+
[[package]]
114
114
+
name = "quote"
115
115
+
version = "1.0.21"
116
116
+
source = "registry+https://github.com/rust-lang/crates.io-index"
117
117
+
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
118
118
+
dependencies = [
119
119
+
"proc-macro2",
120
120
+
]
121
121
+
122
122
+
[[package]]
123
123
+
name = "regex"
124
124
+
version = "1.7.0"
125
125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
126
126
+
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
127
127
+
dependencies = [
128
128
+
"regex-syntax",
129
129
+
]
130
130
+
131
131
+
[[package]]
132
132
+
name = "regex-syntax"
133
133
+
version = "0.6.28"
134
134
+
source = "registry+https://github.com/rust-lang/crates.io-index"
135
135
+
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
136
136
+
137
137
+
[[package]]
138
138
+
name = "serde"
139
139
+
version = "1.0.147"
140
140
+
source = "registry+https://github.com/rust-lang/crates.io-index"
141
141
+
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
142
142
+
dependencies = [
143
143
+
"serde_derive",
144
144
+
]
145
145
+
146
146
+
[[package]]
147
147
+
name = "serde_derive"
148
148
+
version = "1.0.147"
149
149
+
source = "registry+https://github.com/rust-lang/crates.io-index"
150
150
+
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
151
151
+
dependencies = [
152
152
+
"proc-macro2",
153
153
+
"quote",
154
154
+
"syn",
155
155
+
]
156
156
+
157
157
+
[[package]]
158
158
+
name = "shapemaker"
159
159
+
version = "0.1.0"
160
160
+
dependencies = [
161
161
+
"chumsky",
162
162
+
"docopt",
163
163
+
"serde",
164
164
+
"svg",
165
165
+
]
166
166
+
167
167
+
[[package]]
168
168
+
name = "strsim"
169
169
+
version = "0.10.0"
170
170
+
source = "registry+https://github.com/rust-lang/crates.io-index"
171
171
+
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
172
172
+
173
173
+
[[package]]
174
174
+
name = "svg"
175
175
+
version = "0.12.1"
176
176
+
source = "registry+https://github.com/rust-lang/crates.io-index"
177
177
+
checksum = "a6e6ff893392e6a1eb94a210562432c6380cebf09d30962a012a655f7dde2ff8"
178
178
+
179
179
+
[[package]]
180
180
+
name = "syn"
181
181
+
version = "1.0.103"
182
182
+
source = "registry+https://github.com/rust-lang/crates.io-index"
183
183
+
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
184
184
+
dependencies = [
185
185
+
"proc-macro2",
186
186
+
"quote",
187
187
+
"unicode-ident",
188
188
+
]
189
189
+
190
190
+
[[package]]
191
191
+
name = "tiny-keccak"
192
192
+
version = "2.0.2"
193
193
+
source = "registry+https://github.com/rust-lang/crates.io-index"
194
194
+
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
195
195
+
dependencies = [
196
196
+
"crunchy",
197
197
+
]
198
198
+
199
199
+
[[package]]
200
200
+
name = "unicode-ident"
201
201
+
version = "1.0.5"
202
202
+
source = "registry+https://github.com/rust-lang/crates.io-index"
203
203
+
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
204
204
+
205
205
+
[[package]]
206
206
+
name = "wasi"
207
207
+
version = "0.11.0+wasi-snapshot-preview1"
208
208
+
source = "registry+https://github.com/rust-lang/crates.io-index"
209
209
+
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
···
1
1
+
[package]
2
2
+
name = "shapemaker"
3
3
+
version = "0.1.0"
4
4
+
edition = "2021"
5
5
+
6
6
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
7
+
8
8
+
[dependencies]
9
9
+
chumsky = "0.8.0"
10
10
+
docopt = "1.1.1"
11
11
+
serde = "1.0.147"
12
12
+
svg = "0.12.1"
···
1
1
+
use std::collections::HashMap;
2
2
+
3
3
+
use chumsky::prelude::*;
4
4
+
use chumsky::text;
5
5
+
use chumsky::text::{newline, whitespace};
6
6
+
use docopt::Docopt;
7
7
+
use serde::Deserialize;
8
8
+
9
9
+
const USAGE: &'static str = "
10
10
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
11
11
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
12
12
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
13
13
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
14
14
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
15
15
+
16
16
+
Usage: shapemaker [options] <file>
17
17
+
shapemaker (-h | --help)
18
18
+
";
19
19
+
20
20
+
#[derive(Debug, Deserialize)]
21
21
+
struct Args {
22
22
+
arg_file: String,
23
23
+
flag_verbose: bool,
24
24
+
}
25
25
+
26
26
+
fn main() {
27
27
+
let args: Args = Docopt::new(USAGE)
28
28
+
.and_then(|d| d.deserialize())
29
29
+
.unwrap_or_else(|e| e.exit());
30
30
+
31
31
+
let file_contents = std::fs::read_to_string(&args.arg_file).unwrap();
32
32
+
let shapes: Vec<Shape> = match parser().parse(file_contents) {
33
33
+
Ok(shapes) => {
34
34
+
println!("Parsed shapes: {:#?}", shapes);
35
35
+
shapes
36
36
+
}
37
37
+
Err(e) => {
38
38
+
println!("Error: {:?}", e);
39
39
+
std::process::exit(1);
40
40
+
}
41
41
+
};
42
42
+
}
43
43
+
44
44
+
#[derive(Debug)]
45
45
+
struct Shape {
46
46
+
name: String,
47
47
+
objects: Vec<(Object, Option<Fill>)>,
48
48
+
}
49
49
+
50
50
+
#[derive(Debug)]
51
51
+
enum Object {
52
52
+
Polygon(Vec<Line>),
53
53
+
SmallCircle(Anchor),
54
54
+
Dot(Anchor),
55
55
+
BigCircle(CenterAnchor),
56
56
+
}
57
57
+
58
58
+
#[derive(Debug, Clone)]
59
59
+
enum Anchor {
60
60
+
Top,
61
61
+
TopRight,
62
62
+
Right,
63
63
+
BottomRight,
64
64
+
Bottom,
65
65
+
BottomLeft,
66
66
+
Left,
67
67
+
TopLeft,
68
68
+
Center,
69
69
+
}
70
70
+
71
71
+
#[derive(Debug, Clone)]
72
72
+
enum CenterAnchor {
73
73
+
TopLeft,
74
74
+
TopRight,
75
75
+
BottomLeft,
76
76
+
BottomRight,
77
77
+
Center,
78
78
+
}
79
79
+
80
80
+
#[derive(Debug, Clone)]
81
81
+
enum Line {
82
82
+
Line(Anchor, Anchor),
83
83
+
InwardCurve(Anchor, Anchor),
84
84
+
OutwardCurve(Anchor, Anchor),
85
85
+
}
86
86
+
87
87
+
#[derive(Debug, Clone)]
88
88
+
enum Fill {
89
89
+
Solid(Color),
90
90
+
Hatched,
91
91
+
Dotted,
92
92
+
}
93
93
+
94
94
+
#[derive(Debug, Clone)]
95
95
+
enum Color {
96
96
+
Named(ColorName),
97
97
+
RGBA(u8, u8, u8, u8),
98
98
+
}
99
99
+
100
100
+
#[derive(Debug, Clone)]
101
101
+
enum ColorName {
102
102
+
Black,
103
103
+
White,
104
104
+
Grey,
105
105
+
Red,
106
106
+
Green,
107
107
+
Blue,
108
108
+
Yellow,
109
109
+
Cyan,
110
110
+
Magenta,
111
111
+
Orange,
112
112
+
}
113
113
+
114
114
+
fn parser() -> impl Parser<char, Vec<Shape>, Error = Simple<char>> {
115
115
+
let anchor = choice((
116
116
+
just("top").to(Anchor::Top),
117
117
+
just("top right").to(Anchor::TopRight),
118
118
+
just("right").to(Anchor::Right),
119
119
+
just("bottom right").to(Anchor::BottomRight),
120
120
+
just("bottom").to(Anchor::Bottom),
121
121
+
just("bottom left").to(Anchor::BottomLeft),
122
122
+
just("left").to(Anchor::Left),
123
123
+
just("top left").to(Anchor::TopLeft),
124
124
+
just("center").to(Anchor::Center),
125
125
+
));
126
126
+
let center_anchor = choice((
127
127
+
just("top left").to(CenterAnchor::TopLeft),
128
128
+
just("top right").to(CenterAnchor::TopRight),
129
129
+
just("bottom left").to(CenterAnchor::BottomLeft),
130
130
+
just("bottom right").to(CenterAnchor::BottomRight),
131
131
+
just("center").to(CenterAnchor::Center),
132
132
+
));
133
133
+
134
134
+
let straight_line = anchor
135
135
+
.then_ignore(just("--").padded())
136
136
+
.then(anchor)
137
137
+
.map(|(a, b)| Line::Line(a, b));
138
138
+
let inward_curve = anchor
139
139
+
.then_ignore(one_of("n(").padded())
140
140
+
.then(anchor)
141
141
+
.map(|(a, b)| Line::InwardCurve(a, b));
142
142
+
let outward_curve = anchor
143
143
+
.then_ignore(one_of("u(").padded())
144
144
+
.then(anchor)
145
145
+
.map(|(a, b)| Line::OutwardCurve(a, b));
146
146
+
let line = choice((straight_line, inward_curve, outward_curve));
147
147
+
let polygon = line.padded().repeated().boxed().map(Object::Polygon);
148
148
+
let point = anchor
149
149
+
.then_ignore(whitespace())
150
150
+
.then_ignore(just("point"))
151
151
+
.map(Object::SmallCircle);
152
152
+
let circle = center_anchor
153
153
+
.then_ignore(whitespace())
154
154
+
.then_ignore(just("circle"))
155
155
+
.map(Object::BigCircle);
156
156
+
let dot = anchor
157
157
+
.then_ignore(whitespace())
158
158
+
.then_ignore(just("dot"))
159
159
+
.map(Object::Dot);
160
160
+
let object = choice((polygon, point, dot, circle));
161
161
+
let color = choice((
162
162
+
just("black").to(Color::Named(ColorName::Black)),
163
163
+
just("white").to(Color::Named(ColorName::White)),
164
164
+
just("grey").to(Color::Named(ColorName::Grey)),
165
165
+
just("red").to(Color::Named(ColorName::Red)),
166
166
+
just("green").to(Color::Named(ColorName::Green)),
167
167
+
just("blue").to(Color::Named(ColorName::Blue)),
168
168
+
just("yellow").to(Color::Named(ColorName::Yellow)),
169
169
+
just("cyan").to(Color::Named(ColorName::Cyan)),
170
170
+
just("magenta").to(Color::Named(ColorName::Magenta)),
171
171
+
just("orange").to(Color::Named(ColorName::Orange)),
172
172
+
just("#").ignored().then(text::int(16)).map(|(_, i)| {
173
173
+
Color::RGBA(
174
174
+
((i >> 24) & 0xFF) as u8,
175
175
+
((i >> 16) & 0xFF) as u8,
176
176
+
((i >> 8) & 0xFF) as u8,
177
177
+
(i & 0xFF) as u8,
178
178
+
)
179
179
+
}),
180
180
+
));
181
181
+
let fill = choice((
182
182
+
just("filled with")
183
183
+
.ignored()
184
184
+
.then_ignore(whitespace())
185
185
+
.then(color)
186
186
+
.map(|(_, c)| Fill::Solid(c)),
187
187
+
just("hatched").to(Fill::Hatched),
188
188
+
just("dotted").to(Fill::Dotted),
189
189
+
));
190
190
+
let filled_object = just("[")
191
191
+
.padded()
192
192
+
.ignored()
193
193
+
.then(object)
194
194
+
.then_ignore(just("]").padded())
195
195
+
.then(fill)
196
196
+
.map(|((_, o), f)| (o, Some(f)));
197
197
+
let separator = newline().then_ignore(whitespace()).then_ignore(newline());
198
198
+
let header = take_until(just(":"))
199
199
+
.then_ignore(whitespace())
200
200
+
.then_ignore(newline())
201
201
+
.map(|(i, _)| i);
202
202
+
let shape = header
203
203
+
.then_ignore(whitespace())
204
204
+
.then(
205
205
+
filled_object
206
206
+
.or(object.map(|o| (o, None)))
207
207
+
.separated_by(newline()),
208
208
+
)
209
209
+
.map(|(name, objects)| Shape {
210
210
+
name: name.into_iter().collect(),
211
211
+
objects,
212
212
+
});
213
213
+
214
214
+
shape.separated_by(separator)
215
215
+
}