diff --git a/.gitignore b/.gitignore
index ea8c4bf..8ffdea6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
/target
+shapemaker
+
+.vscode
+test.svg
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 04dec81..f79c807 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,58 +2,12 @@
# It is not intended for manual editing.
version = 3
-[[package]]
-name = "ahash"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
-dependencies = [
- "const-random",
-]
-
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-[[package]]
-name = "chumsky"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
-dependencies = [
- "ahash",
-]
-
-[[package]]
-name = "const-random"
-version = "0.1.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
-dependencies = [
- "const-random-macro",
- "proc-macro-hack",
-]
-
-[[package]]
-name = "const-random-macro"
-version = "0.1.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
-dependencies = [
- "getrandom",
- "once_cell",
- "proc-macro-hack",
- "tiny-keccak",
-]
-
-[[package]]
-name = "crunchy"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-
[[package]]
name = "docopt"
version = "1.1.1"
@@ -90,16 +44,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
-name = "once_cell"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
-
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.19"
+name = "ppv-lite86"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
@@ -119,6 +67,36 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
[[package]]
name = "regex"
version = "1.7.0"
@@ -156,10 +134,10 @@ dependencies = [
[[package]]
name = "shapemaker"
-version = "0.1.0"
+version = "1.0.0"
dependencies = [
- "chumsky",
"docopt",
+ "rand",
"serde",
"svg",
]
@@ -172,9 +150,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "svg"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6e6ff893392e6a1eb94a210562432c6380cebf09d30962a012a655f7dde2ff8"
+checksum = "e715e0c3fc987f4c435dc7189641fd9caa6919a74675ace605c38e201d278001"
[[package]]
name = "syn"
@@ -187,15 +165,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "tiny-keccak"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
-dependencies = [
- "crunchy",
-]
-
[[package]]
name = "unicode-ident"
version = "1.0.5"
diff --git a/Cargo.toml b/Cargo.toml
index 04d9361..cf4761b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "shapemaker"
-version = "0.1.0"
+version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-chumsky = "0.8.0"
docopt = "1.1.1"
+rand = "0.8.5"
serde = "1.0.147"
-svg = "0.12.1"
+svg = "0.13.0"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8fe250c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# shapemaker
+
+An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid.
+
+## Gallery
+
+
+
+**HAL 9000**
+
+
+**Amogus**
+
+
+**Capitalism**
+
+
+**Cutting An Egg**
+
+
+**Designing A Font**
+
+
+**Drone Operating System**
+
+
+**Iron Factory**
+
+
+**Japan Sledding Olympics**
+
+
+**Lunatic Green Energy**
+
+
+**Measuring Spirits**
+
+
+**Phone Cameras**
+
+
+**Reflections**
+
+
+**Spline Optimisation**
+
+
+**Tropical Fish**
+
+
+**Weaving**
+
+
+
+
+
diff --git a/README.md.in b/README.md.in
new file mode 100644
index 0000000..bcf1614
--- /dev/null
+++ b/README.md.in
@@ -0,0 +1,11 @@
+# shapemaker
+
+An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid.
+
+## Gallery
+
+
+
+%gallery%
+
+
diff --git a/fill-gallery.rb b/fill-gallery.rb
new file mode 100755
index 0000000..551343b
--- /dev/null
+++ b/fill-gallery.rb
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+
+gallery = ""
+
+Dir.glob("gallery/*.svg").each do |file|
+ if file == "gallery/test.svg" then next end
+
+ title = file
+ .sub(/^gallery\//, "")
+ .sub(/\.svg$/, "")
+ .gsub(/-/, " ").split(" ")
+ .map { |word| word.upcase == word ? word : word.capitalize }
+ .join(" ")
+
+ gallery += "**#{title}**\n\n\n"
+end
+
+File.open "README.md", "w" do |f|
+ f.write File.read("README.md.in").gsub("%gallery%", gallery)
+end
diff --git a/gallery/HAL-9000.svg b/gallery/HAL-9000.svg
new file mode 100644
index 0000000..941e6b9
--- /dev/null
+++ b/gallery/HAL-9000.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/amogus.svg b/gallery/amogus.svg
new file mode 100644
index 0000000..509afdf
--- /dev/null
+++ b/gallery/amogus.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/capitalism.svg b/gallery/capitalism.svg
new file mode 100644
index 0000000..ce05e26
--- /dev/null
+++ b/gallery/capitalism.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/cutting-an-egg.svg b/gallery/cutting-an-egg.svg
new file mode 100644
index 0000000..a4450b2
--- /dev/null
+++ b/gallery/cutting-an-egg.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/designing-a-font.svg b/gallery/designing-a-font.svg
new file mode 100644
index 0000000..3f76a7b
--- /dev/null
+++ b/gallery/designing-a-font.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/drone-operating-system.svg b/gallery/drone-operating-system.svg
new file mode 100644
index 0000000..8b63b4d
--- /dev/null
+++ b/gallery/drone-operating-system.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/iron-factory.svg b/gallery/iron-factory.svg
new file mode 100644
index 0000000..58dee5a
--- /dev/null
+++ b/gallery/iron-factory.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/japan-sledding-olympics.svg b/gallery/japan-sledding-olympics.svg
new file mode 100644
index 0000000..ac42183
--- /dev/null
+++ b/gallery/japan-sledding-olympics.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/lunatic-green-energy.svg b/gallery/lunatic-green-energy.svg
new file mode 100644
index 0000000..496c059
--- /dev/null
+++ b/gallery/lunatic-green-energy.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/measuring-spirits.svg b/gallery/measuring-spirits.svg
new file mode 100644
index 0000000..3f85061
--- /dev/null
+++ b/gallery/measuring-spirits.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/phone-cameras.svg b/gallery/phone-cameras.svg
new file mode 100644
index 0000000..ea21c15
--- /dev/null
+++ b/gallery/phone-cameras.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/reflections.svg b/gallery/reflections.svg
new file mode 100644
index 0000000..d3caeee
--- /dev/null
+++ b/gallery/reflections.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/spline-optimisation.svg b/gallery/spline-optimisation.svg
new file mode 100644
index 0000000..02b0b9d
--- /dev/null
+++ b/gallery/spline-optimisation.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/test.svg b/gallery/test.svg
new file mode 100644
index 0000000..aaf96c5
--- /dev/null
+++ b/gallery/test.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/tropical-fish.svg b/gallery/tropical-fish.svg
new file mode 100644
index 0000000..c11f39f
--- /dev/null
+++ b/gallery/tropical-fish.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gallery/weaving.svg b/gallery/weaving.svg
new file mode 100644
index 0000000..2f60a2d
--- /dev/null
+++ b/gallery/weaving.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 0342476..182aa5f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,7 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, borrow::Borrow};
-use chumsky::prelude::*;
-use chumsky::text;
-use chumsky::text::{newline, whitespace};
use docopt::Docopt;
+use rand::Rng;
use serde::Deserialize;
const USAGE: &'static str = "
@@ -11,51 +9,223 @@ const USAGE: &'static str = "
█░▄▄█░████░▄▄▀█▀▄▄▀█░▄▄█░▄▀▄░█░▄▄▀█░█▀█░▄▄█░▄▄▀█
█▄▄▀█░▄▄░█░▀▀░█░▀▀░█░▄▄█░█▄█░█░▀▀░█░▄▀█░▄▄█░▀▀▄█
█▄▄▄█▄██▄█▄██▄█░████▄▄▄█▄███▄█▄██▄█▄█▄█▄▄▄█▄█▄▄█
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀vVERSION▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
Usage: shapemaker [options]
- shapemaker (-h | --help)
+ shapemaker --help
+ shapemaker --version
";
#[derive(Debug, Deserialize)]
struct Args {
arg_file: String,
- flag_verbose: bool,
+ flag_version: bool,
}
fn main() {
- let args: Args = Docopt::new(USAGE)
+ let args: Args = Docopt::new(USAGE.replace("VERSION", env!("CARGO_PKG_VERSION")))
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
- let file_contents = std::fs::read_to_string(&args.arg_file).unwrap();
- let shapes: Vec = match parser().parse(file_contents) {
- Ok(shapes) => {
- println!("Parsed shapes: {:#?}", shapes);
- shapes
- }
- Err(e) => {
- println!("Error: {:?}", e);
- std::process::exit(1);
- }
- };
+ if args.flag_version {
+ println!("shapemaker {}", env!("CARGO_PKG_VERSION"));
+ std::process::exit(0);
+ }
+
+ let shape = random_shape("test");
+
+ std::fs::write(shape.name.to_owned() + ".svg", shape.render());
+
+ // let file_contents = std::fs::read_to_string(&args.arg_file).unwrap();
+ // let shape: Vec = match parser().parse(file_contents) {
+ // Ok(shape) => {
+ // println!("Parsed shape: {:#?}", shape);
+ // shape
+ // }
+ // Err(e) => {
+ // println!("Error: {:?}", e);
+ // std::process::exit(1);
+ // }
+ // };
+}
+
+fn random_shape(name: &'static str) -> Shape {
+ let mut objects: Vec<(Object, Option)> = vec![];
+ let number_of_objects = rand::thread_rng().gen_range(3..7);
+ for _ in 0..number_of_objects {
+ let object = random_object();
+ objects.push((
+ object,
+ if rand::thread_rng().gen_bool(0.5) {
+ Some(random_fill())
+ } else {
+ None
+ },
+ ));
+ }
+ Shape { name, objects }
+}
+
+fn random_object() -> Object {
+ let start = random_anchor();
+ match rand::thread_rng().gen_range(1..=7) {
+ 1 => random_polygon(),
+ 2 => Object::BigCircle(random_center_anchor()),
+ 3 => Object::SmallCircle(start),
+ 4 => Object::Dot(start),
+ 5 => Object::CurveInward(start, random_end_anchor(start)),
+ 6 => Object::CurveOutward(start, random_end_anchor(start)),
+ 7 => Object::Line(random_anchor(), random_anchor()),
+ _ => unreachable!(),
+ }
+}
+
+fn random_end_anchor(start: Anchor) -> Anchor {
+ match start {
+ Anchor::TopLeft => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Center,
+ 2 => Anchor::BottomRight,
+ _ => unreachable!(),
+ },
+ Anchor::Top => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Left,
+ 2 => Anchor::Right,
+ _ => unreachable!(),
+ },
+ Anchor::TopRight => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Center,
+ 2 => Anchor::BottomLeft,
+ _ => unreachable!(),
+ },
+ Anchor::Left => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Top,
+ 2 => Anchor::Bottom,
+ _ => unreachable!(),
+ },
+ Anchor::Center => match rand::thread_rng().gen_range(1..=4) {
+ 1 => Anchor::TopLeft,
+ 2 => Anchor::TopRight,
+ 3 => Anchor::BottomLeft,
+ 4 => Anchor::BottomRight,
+ _ => unreachable!(),
+ },
+ Anchor::Right => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Top,
+ 2 => Anchor::Bottom,
+ _ => unreachable!(),
+ },
+ Anchor::BottomLeft => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Center,
+ 2 => Anchor::TopRight,
+ _ => unreachable!(),
+ },
+ Anchor::Bottom => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Left,
+ 2 => Anchor::Right,
+ _ => unreachable!(),
+ },
+ Anchor::BottomRight => match rand::thread_rng().gen_range(1..=2) {
+ 1 => Anchor::Center,
+ 2 => Anchor::TopLeft,
+ _ => unreachable!(),
+ },
+ }
+}
+
+fn random_polygon() -> Object {
+ let number_of_anchors = rand::thread_rng().gen_range(2..7);
+ let start = random_anchor();
+ let mut lines: Vec = vec![];
+ let mut last_anchor = start.clone();
+ for _ in 0..number_of_anchors {
+ let next_anchor = random_anchor();
+ lines.push(random_line(next_anchor));
+ last_anchor = next_anchor.clone();
+ }
+ Object::Polygon(start, lines)
+}
+
+fn random_line(end: Anchor) -> Line {
+ match rand::thread_rng().gen_range(1..=3) {
+ 1 => Line::Line(end),
+ 2 => Line::InwardCurve(end),
+ 3 => Line::OutwardCurve(end),
+ _ => unreachable!(),
+ }
+}
+
+fn random_anchor() -> Anchor {
+ match rand::thread_rng().gen_range(1..=9) {
+ 1 => Anchor::TopLeft,
+ 2 => Anchor::Top,
+ 3 => Anchor::TopRight,
+ 4 => Anchor::Left,
+ 5 => Anchor::Center,
+ 6 => Anchor::Right,
+ 7 => Anchor::BottomLeft,
+ 8 => Anchor::Bottom,
+ 9 => Anchor::BottomRight,
+ _ => unreachable!(),
+ }
+}
+
+fn random_center_anchor() -> CenterAnchor {
+ match rand::thread_rng().gen_range(1..=5) {
+ 1 => CenterAnchor::TopLeft,
+ 2 => CenterAnchor::TopRight,
+ 3 => CenterAnchor::Center,
+ 4 => CenterAnchor::BottomLeft,
+ 5 => CenterAnchor::BottomRight,
+ _ => unreachable!(),
+ }
+}
+
+fn random_fill() -> Fill {
+ Fill::Solid(random_color())
+ // match rand::thread_rng().gen_range(1..=3) {
+ // 1 => Fill::Solid(random_color()),
+ // 2 => Fill::Hatched,
+ // 3 => Fill::Dotted,
+ // _ => unreachable!(),
+ // }
+}
+
+fn random_color() -> Color {
+ match rand::thread_rng().gen_range(1..=12) {
+ 1 => Color::Black,
+ 2 => Color::White,
+ 3 => Color::Red,
+ 4 => Color::Green,
+ 5 => Color::Blue,
+ 6 => Color::Yellow,
+ 7 => Color::Orange,
+ 8 => Color::Purple,
+ 9 => Color::Brown,
+ 10 => Color::Pink,
+ 11 => Color::Gray,
+ 12 => Color::Cyan,
+ _ => unreachable!(),
+ }
}
#[derive(Debug)]
struct Shape {
- name: String,
+ name: &'static str,
objects: Vec<(Object, Option)>,
}
#[derive(Debug)]
enum Object {
- Polygon(Vec),
+ Polygon(Anchor, Vec),
+ Line(Anchor, Anchor),
+ CurveOutward(Anchor, Anchor),
+ CurveInward(Anchor, Anchor),
SmallCircle(Anchor),
Dot(Anchor),
BigCircle(CenterAnchor),
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Anchor {
Top,
TopRight,
@@ -68,6 +238,23 @@ enum Anchor {
Center,
}
+impl Anchor {
+ fn x(&self) -> f32 {
+ match self {
+ Anchor::TopLeft | Anchor::Left | Anchor::BottomLeft => 0.0,
+ Anchor::Top | Anchor::Center | Anchor::Bottom => 50.0,
+ Anchor::TopRight | Anchor::Right | Anchor::BottomRight => 100.0,
+ }
+ }
+ fn y(&self) -> f32 {
+ match self {
+ Anchor::TopLeft | Anchor::Top | Anchor::TopRight => 0.0,
+ Anchor::Left | Anchor::Center | Anchor::Right => 50.0,
+ Anchor::BottomLeft | Anchor::Bottom | Anchor::BottomRight => 100.0,
+ }
+ }
+}
+
#[derive(Debug, Clone)]
enum CenterAnchor {
TopLeft,
@@ -77,139 +264,411 @@ enum CenterAnchor {
Center,
}
-#[derive(Debug, Clone)]
+impl CenterAnchor {
+ fn x(&self) -> f32 {
+ match self {
+ CenterAnchor::TopLeft | CenterAnchor::BottomLeft => 25.0,
+ CenterAnchor::TopRight | CenterAnchor::BottomRight => 75.0,
+ CenterAnchor::Center => 50.0,
+ }
+ }
+
+ fn y(&self) -> f32 {
+ match self {
+ CenterAnchor::TopLeft | CenterAnchor::TopRight => 25.0,
+ CenterAnchor::BottomLeft | CenterAnchor::BottomRight => 75.0,
+ CenterAnchor::Center => 50.0,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
enum Line {
- Line(Anchor, Anchor),
- InwardCurve(Anchor, Anchor),
- OutwardCurve(Anchor, Anchor),
+ Line(Anchor),
+ InwardCurve(Anchor),
+ OutwardCurve(Anchor),
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
enum Fill {
Solid(Color),
Hatched,
Dotted,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
enum Color {
- Named(ColorName),
- RGBA(u8, u8, u8, u8),
-}
-
-#[derive(Debug, Clone)]
-enum ColorName {
Black,
White,
- Grey,
Red,
Green,
Blue,
Yellow,
- Cyan,
- Magenta,
Orange,
+ Purple,
+ Brown,
+ Cyan,
+ Pink,
+ Gray,
}
-fn parser() -> impl Parser, Error = Simple> {
- let anchor = choice((
- just("top").to(Anchor::Top),
- just("top right").to(Anchor::TopRight),
- just("right").to(Anchor::Right),
- just("bottom right").to(Anchor::BottomRight),
- just("bottom").to(Anchor::Bottom),
- just("bottom left").to(Anchor::BottomLeft),
- just("left").to(Anchor::Left),
- just("top left").to(Anchor::TopLeft),
- just("center").to(Anchor::Center),
- ));
- let center_anchor = choice((
- just("top left").to(CenterAnchor::TopLeft),
- just("top right").to(CenterAnchor::TopRight),
- just("bottom left").to(CenterAnchor::BottomLeft),
- just("bottom right").to(CenterAnchor::BottomRight),
- just("center").to(CenterAnchor::Center),
- ));
-
- let straight_line = anchor
- .then_ignore(just("--").padded())
- .then(anchor)
- .map(|(a, b)| Line::Line(a, b));
- let inward_curve = anchor
- .then_ignore(one_of("n(").padded())
- .then(anchor)
- .map(|(a, b)| Line::InwardCurve(a, b));
- let outward_curve = anchor
- .then_ignore(one_of("u(").padded())
- .then(anchor)
- .map(|(a, b)| Line::OutwardCurve(a, b));
- let line = choice((straight_line, inward_curve, outward_curve));
- let polygon = line.padded().repeated().boxed().map(Object::Polygon);
- let point = anchor
- .then_ignore(whitespace())
- .then_ignore(just("point"))
- .map(Object::SmallCircle);
- let circle = center_anchor
- .then_ignore(whitespace())
- .then_ignore(just("circle"))
- .map(Object::BigCircle);
- let dot = anchor
- .then_ignore(whitespace())
- .then_ignore(just("dot"))
- .map(Object::Dot);
- let object = choice((polygon, point, dot, circle));
- let color = choice((
- just("black").to(Color::Named(ColorName::Black)),
- just("white").to(Color::Named(ColorName::White)),
- just("grey").to(Color::Named(ColorName::Grey)),
- just("red").to(Color::Named(ColorName::Red)),
- just("green").to(Color::Named(ColorName::Green)),
- just("blue").to(Color::Named(ColorName::Blue)),
- just("yellow").to(Color::Named(ColorName::Yellow)),
- just("cyan").to(Color::Named(ColorName::Cyan)),
- just("magenta").to(Color::Named(ColorName::Magenta)),
- just("orange").to(Color::Named(ColorName::Orange)),
- just("#").ignored().then(text::int(16)).map(|(_, i)| {
- Color::RGBA(
- ((i >> 24) & 0xFF) as u8,
- ((i >> 16) & 0xFF) as u8,
- ((i >> 8) & 0xFF) as u8,
- (i & 0xFF) as u8,
- )
- }),
- ));
- let fill = choice((
- just("filled with")
- .ignored()
- .then_ignore(whitespace())
- .then(color)
- .map(|(_, c)| Fill::Solid(c)),
- just("hatched").to(Fill::Hatched),
- just("dotted").to(Fill::Dotted),
- ));
- let filled_object = just("[")
- .padded()
- .ignored()
- .then(object)
- .then_ignore(just("]").padded())
- .then(fill)
- .map(|((_, o), f)| (o, Some(f)));
- let separator = newline().then_ignore(whitespace()).then_ignore(newline());
- let header = take_until(just(":"))
- .then_ignore(whitespace())
- .then_ignore(newline())
- .map(|(i, _)| i);
- let shape = header
- .then_ignore(whitespace())
- .then(
- filled_object
- .or(object.map(|o| (o, None)))
- .separated_by(newline()),
- )
- .map(|(name, objects)| Shape {
- name: name.into_iter().collect(),
- objects,
- });
+impl Color {
+ fn to_string(self) -> String {
+ match self {
+ Color::Black => "black",
+ Color::White => "white",
+ Color::Red => "red",
+ Color::Green => "green",
+ Color::Blue => "blue",
+ Color::Yellow => "yellow",
+ Color::Orange => "orange",
+ Color::Purple => "purple",
+ Color::Brown => "brown",
+ Color::Cyan => "cyan",
+ Color::Pink => "pink",
+ Color::Gray => "gray",
+ }
+ .to_string()
+ }
+}
+
+impl Shape {
+ fn render(self) -> String {
+ let default_color = "black";
+ let mut output = String::new();
+ let mut svg = svg::Document::new();
+ for (object, maybe_fill) in self.objects {
+ let mut group = svg::node::element::Group::new();
+ match object {
+ Object::Polygon(start, lines) => {
+ println!("render: polygon({:?}, {:?})", start, lines);
+ let mut path = svg::node::element::path::Data::new();
+ path = path.move_to((start.x(), start.y()));
+ for line in lines {
+ path = match line {
+ Line::Line(end) | Line::InwardCurve(end) | Line::OutwardCurve(end) => {
+ path.line_to((end.x(), end.y()))
+ }
+ };
+ // path = match line {
+ // Line::Line(end) => path.line_to((end.x(), end.y())),
+ // Line::InwardCurve(end) => path.quadratic_curve_to((
+ // (end.x(), end.y()),
+ // ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0),
+ // )),
+ // Line::OutwardCurve(end) => path.quadratic_curve_to((
+ // (end.x(), end.y()),
+ // ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0),
+ // )),
+ // };
+ }
+ path = path.close();
+ group = group
+ .add(svg::node::element::Path::new().set("d", path))
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: {};", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color),
+ },
+ );
+ }
+ Object::Line(start, end) => {
+ println!("render: line({:?}, {:?})", start, end);
+ group = group.add(
+ svg::node::element::Line::new()
+ .set("x1", start.x())
+ .set("y1", start.y())
+ .set("x2", end.x())
+ .set("y2", end.y())
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: none; stroke: {}; stroke-width: 2px;", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 2px;", default_color),
+ },
+ ),
+ );
+ }
+ Object::CurveInward(start, end) | Object::CurveOutward(start, end) => {
+ let inward = if matches!(object, Object::CurveInward(_, _)) {
+ println!("render: curve_inward({:?}, {:?})", start, end);
+ true
+ } else {
+ println!("render: curve_outward({:?}, {:?})", start, end);
+ false
+ };
- shape.separated_by(separator)
+ let midpoint = ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0);
+ let start_from_midpoint = (start.x() - midpoint.0, start.y() - midpoint.1);
+ let end_from_midpoint = (end.x() - midpoint.0, end.y() - midpoint.1);
+ println!(" midpoint: {:?}", midpoint);
+ println!(
+ " from midpoint: {:?} -> {:?}",
+ start_from_midpoint, end_from_midpoint
+ );
+ let control = {
+ let relative = (end.x() - start.x(), end.y() - start.y());
+ println!(" relative: {:?}", relative);
+ // diagonal line is going like this: \
+ if start_from_midpoint.0 * start_from_midpoint.1 > 0.0
+ && end_from_midpoint.0 * end_from_midpoint.1 > 0.0
+ {
+ println!(" diagonal \\");
+ if inward {
+ (
+ midpoint.0 + relative.0.abs() / 2.0,
+ midpoint.1 - relative.1.abs() / 2.0,
+ )
+ } else {
+ (
+ midpoint.0 - relative.0.abs() / 2.0,
+ midpoint.1 + relative.1.abs() / 2.0,
+ )
+ }
+ // diagonal line is going like this: /
+ } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0
+ && end_from_midpoint.0 * end_from_midpoint.1 < 0.0
+ {
+ println!(" diagonal /");
+ if inward {
+ (
+ midpoint.0 - relative.0.abs() / 2.0,
+ midpoint.1 - relative.1.abs() / 2.0,
+ )
+ } else {
+ (
+ midpoint.0 + relative.0.abs() / 2.0,
+ midpoint.1 + relative.1.abs() / 2.0,
+ )
+ }
+ // line is horizontal
+ } else if start.y() == end.y() {
+ println!(" horizontal");
+ (
+ midpoint.0,
+ midpoint.1
+ + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0,
+ )
+ // line is vertical
+ } else if start.x() == end.x() {
+ println!(" vertical");
+ (
+ midpoint.0
+ + (if inward { -1.0 } else { 1.0 }) * relative.1.abs() / 2.0,
+ midpoint.1,
+ )
+ } else {
+ unreachable!()
+ }
+ };
+ println!(" control: {:?}", control);
+ group = group.add(
+ svg::node::element::Path::new()
+ .set(
+ "d",
+ svg::node::element::path::Data::new()
+ .move_to((start.x(), start.y()))
+ .quadratic_curve_to((control, (end.x(), end.y()))),
+ )
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: none; stroke: {}; stroke-width: 2px;", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 2px;", default_color),
+ },
+ ),
+ );
+ }
+ Object::SmallCircle(center) => {
+ println!("render: small_circle({:?})", center);
+ group = group.add(
+ svg::node::element::Circle::new()
+ .set("cx", center.x())
+ .set("cy", center.y())
+ .set("r", 5)
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: {};", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color),
+ },
+ ),
+ );
+ }
+ Object::Dot(center) => {
+ println!("render: dot({:?})", center);
+ group = group.add(
+ svg::node::element::Circle::new()
+ .set("cx", center.x())
+ .set("cy", center.y())
+ .set("r", 2)
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: {};", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color),
+ },
+ ),
+ );
+ }
+ Object::BigCircle(center) => {
+ println!("render: big_circle({:?})", center);
+ group = group.add(
+ svg::node::element::Circle::new()
+ .set("cx", center.x())
+ .set("cy", center.y())
+ .set("r", 25)
+ .set(
+ "style",
+ match maybe_fill {
+ // TODO
+ Some(Fill::Solid(color)) => {
+ format!("fill: {};", color.to_string())
+ }
+ _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color),
+ },
+ ),
+ );
+ }
+ }
+ println!(" fill: {:?}", &maybe_fill);
+ svg = svg.add(group);
+ }
+ // render a dotted grid
+ if false {
+ for x in vec![0, 25, 50, 75, 100] {
+ for y in vec![0, 25, 50, 75, 100] {
+ svg = svg.add(
+ svg::node::element::Circle::new()
+ .set("cx", x)
+ .set("cy", y)
+ .set("r", 0.5)
+ .set("fill", "gray"),
+ );
+ }
+ }
+ }
+ svg.set("viewBox", "-10 -10 120 120").to_string()
+ }
+}
+
+impl Object {
+ fn closed(self) -> bool {
+ matches!(
+ self,
+ Object::Polygon(_, _) | Object::BigCircle(_) | Object::SmallCircle(_) | Object::Dot(_)
+ )
+ }
}
+
+// fn parser() -> impl Parser, Error = Simple> {
+// let anchor = choice((
+// just("top").to(Anchor::Top),
+// just("top right").to(Anchor::TopRight),
+// just("right").to(Anchor::Right),
+// just("bottom right").to(Anchor::BottomRight),
+// just("bottom").to(Anchor::Bottom),
+// just("bottom left").to(Anchor::BottomLeft),
+// just("left").to(Anchor::Left),
+// just("top left").to(Anchor::TopLeft),
+// just("center").to(Anchor::Center),
+// ));
+// let center_anchor = choice((
+// just("top left").to(CenterAnchor::TopLeft),
+// just("top right").to(CenterAnchor::TopRight),
+// just("bottom left").to(CenterAnchor::BottomLeft),
+// just("bottom right").to(CenterAnchor::BottomRight),
+// just("center").to(CenterAnchor::Center),
+// ));
+
+// let straight_line = anchor.clone()
+// .then_ignore(just("--").padded())
+// .then(anchor)
+// .map(|(a, b)| Line::Line(a, b));
+// let inward_curve = &anchor
+// .then_ignore(one_of("n(").padded())
+// .then(&anchor)
+// .map(|(a, b)| Line::InwardCurve(a, b));
+// let outward_curve = &anchor
+// .then_ignore(one_of("u(").padded())
+// .then(&anchor)
+// .map(|(a, b)| Line::OutwardCurve(a, b));
+// let line = choice((straight_line, inward_curve, outward_curve));
+// let polygon = line.padded().repeated().boxed().map(Object::Polygon);
+// let point = &anchor
+// .then_ignore(whitespace())
+// .then_ignore(just("point"))
+// .map(Object::SmallCircle);
+// let circle = center_anchor
+// .then_ignore(whitespace())
+// .then_ignore(just("circle"))
+// .map(Object::BigCircle);
+// let dot = &anchor
+// .then_ignore(whitespace())
+// .then_ignore(just("dot"))
+// .map(Object::Dot);
+// let object = choice((polygon, point, dot, circle));
+// // let color = choice((
+// // text::ident().map(Color::Named),
+// // just("#").ignored().then(text::int(16)).map(|(_, i)| {
+// // Color::RGBA(
+// // ((i >> 24) & 0xFF) as u8,
+// // ((i >> 16) & 0xFF) as u8,
+// // ((i >> 8) & 0xFF) as u8,
+// // (i & 0xFF) as u8,
+// // )
+// // }),
+// // ));
+// // let fill = choice((
+// // just("filled with")
+// // .ignored()
+// // .then_ignore(whitespace())
+// // .then(color)
+// // .map(|(_, c)| Fill::Solid(c)),
+// // just("hatched").to(Fill::Hatched),
+// // just("dotted").to(Fill::Dotted),
+// // ));
+// // let filled_object = just("[")
+// // .padded()
+// // .ignored()
+// // .then(object)
+// // .then_ignore(just("]").padded())
+// // .then(fill)
+// // .map(|((_, o), f)| (o, Some(f)));
+// // let separator = newline().then_ignore(whitespace()).then_ignore(newline());
+// // let header = take_until(just(":"))
+// // .then_ignore(whitespace())
+// // .then_ignore(newline())
+// // .map(|(i, _)| i);
+// // let shape = header
+// // .then_ignore(whitespace())
+// // .then(
+// // filled_object
+// // .or(object.map(|o| (o, None)))
+// // .separated_by(newline()),
+// // )
+// // .map(|(name, objects)| Shape {
+// // name: name.into_iter().collect(),
+// // objects,
+// // });
+// // filled_object
+// // .or(object.map(|o| (o, None)))
+// // .separated_by(newline())
+// object.separated_by(newline())
+// }