experimental SVG-based video rendering engine made for music videos. React to MIDI or arbitrary signals from your DAW through "probe" VSTs

✨ Implement shape generation and solid fills

+974 -207
+4
.gitignore
··· 1 1 /target 2 + shapemaker 3 + 4 + .vscode 5 + test.svg
+37 -68
Cargo.lock
··· 3 3 version = 3 4 4 5 5 [[package]] 6 - name = "ahash" 7 - version = "0.3.8" 8 - source = "registry+https://github.com/rust-lang/crates.io-index" 9 - checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" 10 - dependencies = [ 11 - "const-random", 12 - ] 13 - 14 - [[package]] 15 6 name = "cfg-if" 16 7 version = "1.0.0" 17 8 source = "registry+https://github.com/rust-lang/crates.io-index" 18 9 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 19 10 20 11 [[package]] 21 - name = "chumsky" 22 - version = "0.8.0" 23 - source = "registry+https://github.com/rust-lang/crates.io-index" 24 - checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" 25 - dependencies = [ 26 - "ahash", 27 - ] 28 - 29 - [[package]] 30 - name = "const-random" 31 - version = "0.1.15" 32 - source = "registry+https://github.com/rust-lang/crates.io-index" 33 - checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" 34 - dependencies = [ 35 - "const-random-macro", 36 - "proc-macro-hack", 37 - ] 38 - 39 - [[package]] 40 - name = "const-random-macro" 41 - version = "0.1.15" 42 - source = "registry+https://github.com/rust-lang/crates.io-index" 43 - checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" 44 - dependencies = [ 45 - "getrandom", 46 - "once_cell", 47 - "proc-macro-hack", 48 - "tiny-keccak", 49 - ] 50 - 51 - [[package]] 52 - name = "crunchy" 53 - version = "0.2.2" 54 - source = "registry+https://github.com/rust-lang/crates.io-index" 55 - checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 56 - 57 - [[package]] 58 12 name = "docopt" 59 13 version = "1.1.1" 60 14 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 90 44 checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 91 45 92 46 [[package]] 93 - name = "once_cell" 94 - version = "1.16.0" 47 + name = "ppv-lite86" 48 + version = "0.2.17" 95 49 source = "registry+https://github.com/rust-lang/crates.io-index" 96 - checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 97 - 98 - [[package]] 99 - name = "proc-macro-hack" 100 - version = "0.5.19" 101 - source = "registry+https://github.com/rust-lang/crates.io-index" 102 - checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 50 + checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 103 51 104 52 [[package]] 105 53 name = "proc-macro2" ··· 120 68 ] 121 69 122 70 [[package]] 71 + name = "rand" 72 + version = "0.8.5" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 75 + dependencies = [ 76 + "libc", 77 + "rand_chacha", 78 + "rand_core", 79 + ] 80 + 81 + [[package]] 82 + name = "rand_chacha" 83 + version = "0.3.1" 84 + source = "registry+https://github.com/rust-lang/crates.io-index" 85 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 86 + dependencies = [ 87 + "ppv-lite86", 88 + "rand_core", 89 + ] 90 + 91 + [[package]] 92 + name = "rand_core" 93 + version = "0.6.4" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 96 + dependencies = [ 97 + "getrandom", 98 + ] 99 + 100 + [[package]] 123 101 name = "regex" 124 102 version = "1.7.0" 125 103 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 156 134 157 135 [[package]] 158 136 name = "shapemaker" 159 - version = "0.1.0" 137 + version = "1.0.0" 160 138 dependencies = [ 161 - "chumsky", 162 139 "docopt", 140 + "rand", 163 141 "serde", 164 142 "svg", 165 143 ] ··· 172 150 173 151 [[package]] 174 152 name = "svg" 175 - version = "0.12.1" 153 + version = "0.13.0" 176 154 source = "registry+https://github.com/rust-lang/crates.io-index" 177 - checksum = "a6e6ff893392e6a1eb94a210562432c6380cebf09d30962a012a655f7dde2ff8" 155 + checksum = "e715e0c3fc987f4c435dc7189641fd9caa6919a74675ace605c38e201d278001" 178 156 179 157 [[package]] 180 158 name = "syn" ··· 185 163 "proc-macro2", 186 164 "quote", 187 165 "unicode-ident", 188 - ] 189 - 190 - [[package]] 191 - name = "tiny-keccak" 192 - version = "2.0.2" 193 - source = "registry+https://github.com/rust-lang/crates.io-index" 194 - checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 195 - dependencies = [ 196 - "crunchy", 197 166 ] 198 167 199 168 [[package]]
+3 -3
Cargo.toml
··· 1 1 [package] 2 2 name = "shapemaker" 3 - version = "0.1.0" 3 + version = "1.0.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 - chumsky = "0.8.0" 10 9 docopt = "1.1.1" 10 + rand = "0.8.5" 11 11 serde = "1.0.147" 12 - svg = "0.12.1" 12 + svg = "0.13.0"
+56
README.md
··· 1 + # shapemaker 2 + 3 + An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid. 4 + 5 + ## Gallery 6 + 7 + <div style="display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 1rem;"> 8 + 9 + **HAL 9000** 10 + ![HAL 9000](gallery/HAL-9000.svg) 11 + 12 + **Amogus** 13 + ![Amogus](gallery/amogus.svg) 14 + 15 + **Capitalism** 16 + ![Capitalism](gallery/capitalism.svg) 17 + 18 + **Cutting An Egg** 19 + ![Cutting An Egg](gallery/cutting-an-egg.svg) 20 + 21 + **Designing A Font** 22 + ![Designing A Font](gallery/designing-a-font.svg) 23 + 24 + **Drone Operating System** 25 + ![Drone Operating System](gallery/drone-operating-system.svg) 26 + 27 + **Iron Factory** 28 + ![Iron Factory](gallery/iron-factory.svg) 29 + 30 + **Japan Sledding Olympics** 31 + ![Japan Sledding Olympics](gallery/japan-sledding-olympics.svg) 32 + 33 + **Lunatic Green Energy** 34 + ![Lunatic Green Energy](gallery/lunatic-green-energy.svg) 35 + 36 + **Measuring Spirits** 37 + ![Measuring Spirits](gallery/measuring-spirits.svg) 38 + 39 + **Phone Cameras** 40 + ![Phone Cameras](gallery/phone-cameras.svg) 41 + 42 + **Reflections** 43 + ![Reflections](gallery/reflections.svg) 44 + 45 + **Spline Optimisation** 46 + ![Spline Optimisation](gallery/spline-optimisation.svg) 47 + 48 + **Tropical Fish** 49 + ![Tropical Fish](gallery/tropical-fish.svg) 50 + 51 + **Weaving** 52 + ![Weaving](gallery/weaving.svg) 53 + 54 + 55 + 56 + </div>
+11
README.md.in
··· 1 + # shapemaker 2 + 3 + An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid. 4 + 5 + ## Gallery 6 + 7 + <div style="display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 1rem;"> 8 + 9 + %gallery% 10 + 11 + </div>
+20
fill-gallery.rb
··· 1 + #!/usr/bin/env ruby 2 + 3 + gallery = "" 4 + 5 + Dir.glob("gallery/*.svg").each do |file| 6 + if file == "gallery/test.svg" then next end 7 + 8 + title = file 9 + .sub(/^gallery\//, "") 10 + .sub(/\.svg$/, "") 11 + .gsub(/-/, " ").split(" ") 12 + .map { |word| word.upcase == word ? word : word.capitalize } 13 + .join(" ") 14 + 15 + gallery += "**#{title}**\n![#{title}](#{file})\n\n" 16 + end 17 + 18 + File.open "README.md", "w" do |f| 19 + f.write File.read("README.md.in").gsub("%gallery%", gallery) 20 + end
+14
gallery/HAL-9000.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="50" cy="50" r="5" style="fill: red;"/> 4 + </g> 5 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 6 + <path d="M100,50 L0,0 L0,100 L100,0 L50,100 L100,100 z"/> 7 + </g> 8 + <g> 9 + <path d="M50,0 Q0,0,0,50" style="fill: none; stroke: black; stroke-width: 2px;"/> 10 + </g> 11 + <g> 12 + <circle cx="25" cy="25" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 13 + </g> 14 + </svg>
+20
gallery/amogus.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <path d="M0,100 Q50,100,50,50" style="fill: none; stroke: black; stroke-width: 2px;"/> 4 + </g> 5 + <g> 6 + <circle cx="50" cy="50" r="25" style="fill: cyan;"/> 7 + </g> 8 + <g> 9 + <path d="M50,100 Q100,100,100,50" style="fill: none; stroke: black; stroke-width: 2px;"/> 10 + </g> 11 + <g> 12 + <circle cx="0" cy="50" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 13 + </g> 14 + <g> 15 + <circle cx="50" cy="50" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 16 + </g> 17 + <g> 18 + <path d="M100,50 Q100,0,50,0" style="fill: none; stroke: white; stroke-width: 2px;"/> 19 + </g> 20 + </svg>
+11
gallery/capitalism.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 3 + <path d="M100,50 L0,50 L0,0 L50,50 z"/> 4 + </g> 5 + <g style="fill: brown;"> 6 + <path d="M100,50 L50,100 L100,0 L100,50 L0,0 L0,0 L50,100 z"/> 7 + </g> 8 + <g> 9 + <circle cx="0" cy="0" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 10 + </g> 11 + </svg>
+17
gallery/cutting-an-egg.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <path d="M50,100 Q0,100,0,50" style="fill: none; stroke: brown; stroke-width: 2px;"/> 4 + </g> 5 + <g> 6 + <circle cx="75" cy="25" r="25" style="fill: orange;"/> 7 + </g> 8 + <g> 9 + <line style="fill: none; stroke: gray; stroke-width: 2px;" x1="50" x2="50" y1="50" y2="100"/> 10 + </g> 11 + <g> 12 + <circle cx="100" cy="0" r="5" style="fill: brown;"/> 13 + </g> 14 + <g style="fill: gray;"> 15 + <path d="M0,50 L50,0 L100,0 L50,100 z"/> 16 + </g> 17 + </svg>
+14
gallery/designing-a-font.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <path d="M0,100 Q50,100,50,50" style="fill: none; stroke: black; stroke-width: 2px;"/> 4 + </g> 5 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 6 + <path d="M100,50 L100,0 L50,0 L0,0 L0,50 z"/> 7 + </g> 8 + <g> 9 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="50" x2="50" y1="50" y2="0"/> 10 + </g> 11 + <g> 12 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="50" x2="0" y1="50" y2="0"/> 13 + </g> 14 + </svg>
+17
gallery/drone-operating-system.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="75" cy="25" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 6 + <path d="M50,50 L0,50 L50,100 L0,0 L100,50 L0,50 L100,50 z"/> 7 + </g> 8 + <g> 9 + <circle cx="0" cy="100" r="5" style="fill: pink;"/> 10 + </g> 11 + <g> 12 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="0" x2="50" y1="0" y2="50"/> 13 + </g> 14 + <g> 15 + <path d="M0,100 Q0,0,100,0" style="fill: none; stroke: brown; stroke-width: 2px;"/> 16 + </g> 17 + </svg>
+11
gallery/iron-factory.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="50" cy="0" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g style="fill: gray;"> 6 + <path d="M100,100 L0,0 L50,0 L0,100 L50,50 z"/> 7 + </g> 8 + <g> 9 + <path d="M50,50 Q50,0,100,0" style="fill: none; stroke: orange; stroke-width: 2px;"/> 10 + </g> 11 + </svg>
+17
gallery/japan-sledding-olympics.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <path d="M0,100 Q100,100,100,0" style="fill: none; stroke: black; stroke-width: 2px;"/> 4 + </g> 5 + <g style="fill: blue;"> 6 + <path d="M0,50 L100,0 L50,0 L50,0 L50,0 L100,0 z"/> 7 + </g> 8 + <g> 9 + <circle cx="25" cy="25" r="25" style="fill: brown;"/> 10 + </g> 11 + <g> 12 + <circle cx="100" cy="0" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 13 + </g> 14 + <g> 15 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="100" x2="100" y1="100" y2="0"/> 16 + </g> 17 + </svg>
+17
gallery/lunatic-green-energy.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 3 + <path d="M100,0 L100,50 L50,100 L50,50 L0,50 L50,50 z"/> 4 + </g> 5 + <g> 6 + <circle cx="50" cy="0" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 7 + </g> 8 + <g> 9 + <circle cx="75" cy="25" r="25" style="fill: green;"/> 10 + </g> 11 + <g> 12 + <circle cx="0" cy="50" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 13 + </g> 14 + <g> 15 + <circle cx="50" cy="50" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 16 + </g> 17 + </svg>
+17
gallery/measuring-spirits.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="50" cy="50" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g> 6 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="100" x2="0" y1="50" y2="0"/> 7 + </g> 8 + <g> 9 + <circle cx="25" cy="75" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 10 + </g> 11 + <g> 12 + <line style="fill: none; stroke: gray; stroke-width: 2px;" x1="50" x2="100" y1="100" y2="0"/> 13 + </g> 14 + <g> 15 + <circle cx="100" cy="0" r="2" style="fill: purple;"/> 16 + </g> 17 + </svg>
+14
gallery/phone-cameras.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="100" cy="0" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g> 6 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="100" x2="50" y1="50" y2="100"/> 7 + </g> 8 + <g> 9 + <circle cx="75" cy="75" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 10 + </g> 11 + <g> 12 + <circle cx="75" cy="25" r="25" style="fill: black;"/> 13 + </g> 14 + </svg>
+20
gallery/reflections.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 3 + <path d="M100,100 L0,50 L100,0 L0,100 L0,50 L50,0 z"/> 4 + </g> 5 + <g> 6 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="0" x2="0" y1="100" y2="0"/> 7 + </g> 8 + <g> 9 + <circle cx="100" cy="100" r="2" style="fill: red;"/> 10 + </g> 11 + <g> 12 + <line style="fill: none; stroke: black; stroke-width: 2px;" x1="0" x2="100" y1="50" y2="100"/> 13 + </g> 14 + <g> 15 + <circle cx="100" cy="100" r="2" style="fill: black;"/> 16 + </g> 17 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 18 + <path d="M0,0 L0,0 L50,100 L0,50 L50,50 z"/> 19 + </g> 20 + </svg>
+11
gallery/spline-optimisation.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <path d="M0,100 Q0,0,100,0" style="fill: none; stroke: black; stroke-width: 2px;"/> 4 + </g> 5 + <g> 6 + <circle cx="50" cy="0" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 7 + </g> 8 + <g style="fill: none; stroke: black; stroke-width: 0.5px;"> 9 + <path d="M0,100 L50,50 L0,0 L100,0 L100,0 L100,0 L50,100 z"/> 10 + </g> 11 + </svg>
+20
gallery/test.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="75" cy="25" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g> 6 + <circle cx="100" cy="50" r="2" style="fill: cyan;"/> 7 + </g> 8 + <g> 9 + <circle cx="50" cy="0" r="5" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 10 + </g> 11 + <g> 12 + <path d="M0,50 Q50,50,50,100" style="fill: none; stroke: black; stroke-width: 2px;"/> 13 + </g> 14 + <g> 15 + <circle cx="0" cy="0" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 16 + </g> 17 + <g> 18 + <circle cx="25" cy="75" r="25" style="fill: orange;"/> 19 + </g> 20 + </svg>
+17
gallery/tropical-fish.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g style="fill: orange;"> 3 + <path d="M50,50 L50,50 L0,0 L0,100 z"/> 4 + </g> 5 + <g> 6 + <circle cx="0" cy="50" r="2" style="fill: pink;"/> 7 + </g> 8 + <g> 9 + <path d="M50,50 Q100,50,100,0" style="fill: none; stroke: pink; stroke-width: 2px;"/> 10 + </g> 11 + <g> 12 + <circle cx="50" cy="0" r="2" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 13 + </g> 14 + <g> 15 + <circle cx="25" cy="25" r="25" style="fill: cyan;"/> 16 + </g> 17 + </svg>
+11
gallery/weaving.svg
··· 1 + <svg viewBox="-10 -10 120 120" xmlns="http://www.w3.org/2000/svg"> 2 + <g> 3 + <circle cx="50" cy="50" r="25" style="fill: none; stroke: black; stroke-width: 0.5px;"/> 4 + </g> 5 + <g> 6 + <line style="fill: none; stroke: blue; stroke-width: 2px;" x1="0" x2="100" y1="50" y2="0"/> 7 + </g> 8 + <g> 9 + <path d="M50,100 Q50,50,100,50" style="fill: none; stroke: black; stroke-width: 2px;"/> 10 + </g> 11 + </svg>
+595 -136
src/main.rs
··· 1 - use std::collections::HashMap; 1 + use std::{collections::HashMap, borrow::Borrow}; 2 2 3 - use chumsky::prelude::*; 4 - use chumsky::text; 5 - use chumsky::text::{newline, whitespace}; 6 3 use docopt::Docopt; 4 + use rand::Rng; 7 5 use serde::Deserialize; 8 6 9 7 const USAGE: &'static str = " ··· 11 9 █░▄▄█░████░▄▄▀█▀▄▄▀█░▄▄█░▄▀▄░█░▄▄▀█░█▀█░▄▄█░▄▄▀█ 12 10 █▄▄▀█░▄▄░█░▀▀░█░▀▀░█░▄▄█░█▄█░█░▀▀░█░▄▀█░▄▄█░▀▀▄█ 13 11 █▄▄▄█▄██▄█▄██▄█░████▄▄▄█▄███▄█▄██▄█▄█▄█▄▄▄█▄█▄▄█ 14 - ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ 12 + ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀vVERSION▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ 15 13 16 14 Usage: shapemaker [options] <file> 17 - shapemaker (-h | --help) 15 + shapemaker --help 16 + shapemaker --version 18 17 "; 19 18 20 19 #[derive(Debug, Deserialize)] 21 20 struct Args { 22 21 arg_file: String, 23 - flag_verbose: bool, 22 + flag_version: bool, 24 23 } 25 24 26 25 fn main() { 27 - let args: Args = Docopt::new(USAGE) 26 + let args: Args = Docopt::new(USAGE.replace("VERSION", env!("CARGO_PKG_VERSION"))) 28 27 .and_then(|d| d.deserialize()) 29 28 .unwrap_or_else(|e| e.exit()); 30 29 31 - let file_contents = std::fs::read_to_string(&args.arg_file).unwrap(); 32 - let shapes: Vec<Shape> = match parser().parse(file_contents) { 33 - Ok(shapes) => { 34 - println!("Parsed shapes: {:#?}", shapes); 35 - shapes 36 - } 37 - Err(e) => { 38 - println!("Error: {:?}", e); 39 - std::process::exit(1); 40 - } 41 - }; 30 + if args.flag_version { 31 + println!("shapemaker {}", env!("CARGO_PKG_VERSION")); 32 + std::process::exit(0); 33 + } 34 + 35 + let shape = random_shape("test"); 36 + 37 + std::fs::write(shape.name.to_owned() + ".svg", shape.render()); 38 + 39 + // let file_contents = std::fs::read_to_string(&args.arg_file).unwrap(); 40 + // let shape: Vec<Object> = match parser().parse(file_contents) { 41 + // Ok(shape) => { 42 + // println!("Parsed shape: {:#?}", shape); 43 + // shape 44 + // } 45 + // Err(e) => { 46 + // println!("Error: {:?}", e); 47 + // std::process::exit(1); 48 + // } 49 + // }; 50 + } 51 + 52 + fn random_shape(name: &'static str) -> Shape { 53 + let mut objects: Vec<(Object, Option<Fill>)> = vec![]; 54 + let number_of_objects = rand::thread_rng().gen_range(3..7); 55 + for _ in 0..number_of_objects { 56 + let object = random_object(); 57 + objects.push(( 58 + object, 59 + if rand::thread_rng().gen_bool(0.5) { 60 + Some(random_fill()) 61 + } else { 62 + None 63 + }, 64 + )); 65 + } 66 + Shape { name, objects } 67 + } 68 + 69 + fn random_object() -> Object { 70 + let start = random_anchor(); 71 + match rand::thread_rng().gen_range(1..=7) { 72 + 1 => random_polygon(), 73 + 2 => Object::BigCircle(random_center_anchor()), 74 + 3 => Object::SmallCircle(start), 75 + 4 => Object::Dot(start), 76 + 5 => Object::CurveInward(start, random_end_anchor(start)), 77 + 6 => Object::CurveOutward(start, random_end_anchor(start)), 78 + 7 => Object::Line(random_anchor(), random_anchor()), 79 + _ => unreachable!(), 80 + } 81 + } 82 + 83 + fn random_end_anchor(start: Anchor) -> Anchor { 84 + match start { 85 + Anchor::TopLeft => match rand::thread_rng().gen_range(1..=2) { 86 + 1 => Anchor::Center, 87 + 2 => Anchor::BottomRight, 88 + _ => unreachable!(), 89 + }, 90 + Anchor::Top => match rand::thread_rng().gen_range(1..=2) { 91 + 1 => Anchor::Left, 92 + 2 => Anchor::Right, 93 + _ => unreachable!(), 94 + }, 95 + Anchor::TopRight => match rand::thread_rng().gen_range(1..=2) { 96 + 1 => Anchor::Center, 97 + 2 => Anchor::BottomLeft, 98 + _ => unreachable!(), 99 + }, 100 + Anchor::Left => match rand::thread_rng().gen_range(1..=2) { 101 + 1 => Anchor::Top, 102 + 2 => Anchor::Bottom, 103 + _ => unreachable!(), 104 + }, 105 + Anchor::Center => match rand::thread_rng().gen_range(1..=4) { 106 + 1 => Anchor::TopLeft, 107 + 2 => Anchor::TopRight, 108 + 3 => Anchor::BottomLeft, 109 + 4 => Anchor::BottomRight, 110 + _ => unreachable!(), 111 + }, 112 + Anchor::Right => match rand::thread_rng().gen_range(1..=2) { 113 + 1 => Anchor::Top, 114 + 2 => Anchor::Bottom, 115 + _ => unreachable!(), 116 + }, 117 + Anchor::BottomLeft => match rand::thread_rng().gen_range(1..=2) { 118 + 1 => Anchor::Center, 119 + 2 => Anchor::TopRight, 120 + _ => unreachable!(), 121 + }, 122 + Anchor::Bottom => match rand::thread_rng().gen_range(1..=2) { 123 + 1 => Anchor::Left, 124 + 2 => Anchor::Right, 125 + _ => unreachable!(), 126 + }, 127 + Anchor::BottomRight => match rand::thread_rng().gen_range(1..=2) { 128 + 1 => Anchor::Center, 129 + 2 => Anchor::TopLeft, 130 + _ => unreachable!(), 131 + }, 132 + } 133 + } 134 + 135 + fn random_polygon() -> Object { 136 + let number_of_anchors = rand::thread_rng().gen_range(2..7); 137 + let start = random_anchor(); 138 + let mut lines: Vec<Line> = vec![]; 139 + let mut last_anchor = start.clone(); 140 + for _ in 0..number_of_anchors { 141 + let next_anchor = random_anchor(); 142 + lines.push(random_line(next_anchor)); 143 + last_anchor = next_anchor.clone(); 144 + } 145 + Object::Polygon(start, lines) 146 + } 147 + 148 + fn random_line(end: Anchor) -> Line { 149 + match rand::thread_rng().gen_range(1..=3) { 150 + 1 => Line::Line(end), 151 + 2 => Line::InwardCurve(end), 152 + 3 => Line::OutwardCurve(end), 153 + _ => unreachable!(), 154 + } 155 + } 156 + 157 + fn random_anchor() -> Anchor { 158 + match rand::thread_rng().gen_range(1..=9) { 159 + 1 => Anchor::TopLeft, 160 + 2 => Anchor::Top, 161 + 3 => Anchor::TopRight, 162 + 4 => Anchor::Left, 163 + 5 => Anchor::Center, 164 + 6 => Anchor::Right, 165 + 7 => Anchor::BottomLeft, 166 + 8 => Anchor::Bottom, 167 + 9 => Anchor::BottomRight, 168 + _ => unreachable!(), 169 + } 170 + } 171 + 172 + fn random_center_anchor() -> CenterAnchor { 173 + match rand::thread_rng().gen_range(1..=5) { 174 + 1 => CenterAnchor::TopLeft, 175 + 2 => CenterAnchor::TopRight, 176 + 3 => CenterAnchor::Center, 177 + 4 => CenterAnchor::BottomLeft, 178 + 5 => CenterAnchor::BottomRight, 179 + _ => unreachable!(), 180 + } 181 + } 182 + 183 + fn random_fill() -> Fill { 184 + Fill::Solid(random_color()) 185 + // match rand::thread_rng().gen_range(1..=3) { 186 + // 1 => Fill::Solid(random_color()), 187 + // 2 => Fill::Hatched, 188 + // 3 => Fill::Dotted, 189 + // _ => unreachable!(), 190 + // } 191 + } 192 + 193 + fn random_color() -> Color { 194 + match rand::thread_rng().gen_range(1..=12) { 195 + 1 => Color::Black, 196 + 2 => Color::White, 197 + 3 => Color::Red, 198 + 4 => Color::Green, 199 + 5 => Color::Blue, 200 + 6 => Color::Yellow, 201 + 7 => Color::Orange, 202 + 8 => Color::Purple, 203 + 9 => Color::Brown, 204 + 10 => Color::Pink, 205 + 11 => Color::Gray, 206 + 12 => Color::Cyan, 207 + _ => unreachable!(), 208 + } 42 209 } 43 210 44 211 #[derive(Debug)] 45 212 struct Shape { 46 - name: String, 213 + name: &'static str, 47 214 objects: Vec<(Object, Option<Fill>)>, 48 215 } 49 216 50 217 #[derive(Debug)] 51 218 enum Object { 52 - Polygon(Vec<Line>), 219 + Polygon(Anchor, Vec<Line>), 220 + Line(Anchor, Anchor), 221 + CurveOutward(Anchor, Anchor), 222 + CurveInward(Anchor, Anchor), 53 223 SmallCircle(Anchor), 54 224 Dot(Anchor), 55 225 BigCircle(CenterAnchor), 56 226 } 57 227 58 - #[derive(Debug, Clone)] 228 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 59 229 enum Anchor { 60 230 Top, 61 231 TopRight, ··· 68 238 Center, 69 239 } 70 240 241 + impl Anchor { 242 + fn x(&self) -> f32 { 243 + match self { 244 + Anchor::TopLeft | Anchor::Left | Anchor::BottomLeft => 0.0, 245 + Anchor::Top | Anchor::Center | Anchor::Bottom => 50.0, 246 + Anchor::TopRight | Anchor::Right | Anchor::BottomRight => 100.0, 247 + } 248 + } 249 + fn y(&self) -> f32 { 250 + match self { 251 + Anchor::TopLeft | Anchor::Top | Anchor::TopRight => 0.0, 252 + Anchor::Left | Anchor::Center | Anchor::Right => 50.0, 253 + Anchor::BottomLeft | Anchor::Bottom | Anchor::BottomRight => 100.0, 254 + } 255 + } 256 + } 257 + 71 258 #[derive(Debug, Clone)] 72 259 enum CenterAnchor { 73 260 TopLeft, ··· 77 264 Center, 78 265 } 79 266 80 - #[derive(Debug, Clone)] 267 + impl CenterAnchor { 268 + fn x(&self) -> f32 { 269 + match self { 270 + CenterAnchor::TopLeft | CenterAnchor::BottomLeft => 25.0, 271 + CenterAnchor::TopRight | CenterAnchor::BottomRight => 75.0, 272 + CenterAnchor::Center => 50.0, 273 + } 274 + } 275 + 276 + fn y(&self) -> f32 { 277 + match self { 278 + CenterAnchor::TopLeft | CenterAnchor::TopRight => 25.0, 279 + CenterAnchor::BottomLeft | CenterAnchor::BottomRight => 75.0, 280 + CenterAnchor::Center => 50.0, 281 + } 282 + } 283 + } 284 + 285 + #[derive(Debug, Clone, PartialEq, Eq)] 81 286 enum Line { 82 - Line(Anchor, Anchor), 83 - InwardCurve(Anchor, Anchor), 84 - OutwardCurve(Anchor, Anchor), 287 + Line(Anchor), 288 + InwardCurve(Anchor), 289 + OutwardCurve(Anchor), 85 290 } 86 291 87 - #[derive(Debug, Clone)] 292 + #[derive(Debug, Clone, Copy)] 88 293 enum Fill { 89 294 Solid(Color), 90 295 Hatched, 91 296 Dotted, 92 297 } 93 298 94 - #[derive(Debug, Clone)] 299 + #[derive(Debug, Clone, Copy)] 95 300 enum Color { 96 - Named(ColorName), 97 - RGBA(u8, u8, u8, u8), 98 - } 99 - 100 - #[derive(Debug, Clone)] 101 - enum ColorName { 102 301 Black, 103 302 White, 104 - Grey, 105 303 Red, 106 304 Green, 107 305 Blue, 108 306 Yellow, 307 + Orange, 308 + Purple, 309 + Brown, 109 310 Cyan, 110 - Magenta, 111 - Orange, 311 + Pink, 312 + Gray, 313 + } 314 + 315 + impl Color { 316 + fn to_string(self) -> String { 317 + match self { 318 + Color::Black => "black", 319 + Color::White => "white", 320 + Color::Red => "red", 321 + Color::Green => "green", 322 + Color::Blue => "blue", 323 + Color::Yellow => "yellow", 324 + Color::Orange => "orange", 325 + Color::Purple => "purple", 326 + Color::Brown => "brown", 327 + Color::Cyan => "cyan", 328 + Color::Pink => "pink", 329 + Color::Gray => "gray", 330 + } 331 + .to_string() 332 + } 112 333 } 113 334 114 - fn parser() -> impl Parser<char, Vec<Shape>, Error = Simple<char>> { 115 - let anchor = choice(( 116 - just("top").to(Anchor::Top), 117 - just("top right").to(Anchor::TopRight), 118 - just("right").to(Anchor::Right), 119 - just("bottom right").to(Anchor::BottomRight), 120 - just("bottom").to(Anchor::Bottom), 121 - just("bottom left").to(Anchor::BottomLeft), 122 - just("left").to(Anchor::Left), 123 - just("top left").to(Anchor::TopLeft), 124 - just("center").to(Anchor::Center), 125 - )); 126 - let center_anchor = choice(( 127 - just("top left").to(CenterAnchor::TopLeft), 128 - just("top right").to(CenterAnchor::TopRight), 129 - just("bottom left").to(CenterAnchor::BottomLeft), 130 - just("bottom right").to(CenterAnchor::BottomRight), 131 - just("center").to(CenterAnchor::Center), 132 - )); 335 + impl Shape { 336 + fn render(self) -> String { 337 + let default_color = "black"; 338 + let mut output = String::new(); 339 + let mut svg = svg::Document::new(); 340 + for (object, maybe_fill) in self.objects { 341 + let mut group = svg::node::element::Group::new(); 342 + match object { 343 + Object::Polygon(start, lines) => { 344 + println!("render: polygon({:?}, {:?})", start, lines); 345 + let mut path = svg::node::element::path::Data::new(); 346 + path = path.move_to((start.x(), start.y())); 347 + for line in lines { 348 + path = match line { 349 + Line::Line(end) | Line::InwardCurve(end) | Line::OutwardCurve(end) => { 350 + path.line_to((end.x(), end.y())) 351 + } 352 + }; 353 + // path = match line { 354 + // Line::Line(end) => path.line_to((end.x(), end.y())), 355 + // Line::InwardCurve(end) => path.quadratic_curve_to(( 356 + // (end.x(), end.y()), 357 + // ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0), 358 + // )), 359 + // Line::OutwardCurve(end) => path.quadratic_curve_to(( 360 + // (end.x(), end.y()), 361 + // ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0), 362 + // )), 363 + // }; 364 + } 365 + path = path.close(); 366 + group = group 367 + .add(svg::node::element::Path::new().set("d", path)) 368 + .set( 369 + "style", 370 + match maybe_fill { 371 + // TODO 372 + Some(Fill::Solid(color)) => { 373 + format!("fill: {};", color.to_string()) 374 + } 375 + _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color), 376 + }, 377 + ); 378 + } 379 + Object::Line(start, end) => { 380 + println!("render: line({:?}, {:?})", start, end); 381 + group = group.add( 382 + svg::node::element::Line::new() 383 + .set("x1", start.x()) 384 + .set("y1", start.y()) 385 + .set("x2", end.x()) 386 + .set("y2", end.y()) 387 + .set( 388 + "style", 389 + match maybe_fill { 390 + // TODO 391 + Some(Fill::Solid(color)) => { 392 + format!("fill: none; stroke: {}; stroke-width: 2px;", color.to_string()) 393 + } 394 + _ => format!("fill: none; stroke: {}; stroke-width: 2px;", default_color), 395 + }, 396 + ), 397 + ); 398 + } 399 + Object::CurveInward(start, end) | Object::CurveOutward(start, end) => { 400 + let inward = if matches!(object, Object::CurveInward(_, _)) { 401 + println!("render: curve_inward({:?}, {:?})", start, end); 402 + true 403 + } else { 404 + println!("render: curve_outward({:?}, {:?})", start, end); 405 + false 406 + }; 407 + 408 + let midpoint = ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0); 409 + let start_from_midpoint = (start.x() - midpoint.0, start.y() - midpoint.1); 410 + let end_from_midpoint = (end.x() - midpoint.0, end.y() - midpoint.1); 411 + println!(" midpoint: {:?}", midpoint); 412 + println!( 413 + " from midpoint: {:?} -> {:?}", 414 + start_from_midpoint, end_from_midpoint 415 + ); 416 + let control = { 417 + let relative = (end.x() - start.x(), end.y() - start.y()); 418 + println!(" relative: {:?}", relative); 419 + // diagonal line is going like this: \ 420 + if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 421 + && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 422 + { 423 + println!(" diagonal \\"); 424 + if inward { 425 + ( 426 + midpoint.0 + relative.0.abs() / 2.0, 427 + midpoint.1 - relative.1.abs() / 2.0, 428 + ) 429 + } else { 430 + ( 431 + midpoint.0 - relative.0.abs() / 2.0, 432 + midpoint.1 + relative.1.abs() / 2.0, 433 + ) 434 + } 435 + // diagonal line is going like this: / 436 + } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 437 + && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 438 + { 439 + println!(" diagonal /"); 440 + if inward { 441 + ( 442 + midpoint.0 - relative.0.abs() / 2.0, 443 + midpoint.1 - relative.1.abs() / 2.0, 444 + ) 445 + } else { 446 + ( 447 + midpoint.0 + relative.0.abs() / 2.0, 448 + midpoint.1 + relative.1.abs() / 2.0, 449 + ) 450 + } 451 + // line is horizontal 452 + } else if start.y() == end.y() { 453 + println!(" horizontal"); 454 + ( 455 + midpoint.0, 456 + midpoint.1 457 + + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0, 458 + ) 459 + // line is vertical 460 + } else if start.x() == end.x() { 461 + println!(" vertical"); 462 + ( 463 + midpoint.0 464 + + (if inward { -1.0 } else { 1.0 }) * relative.1.abs() / 2.0, 465 + midpoint.1, 466 + ) 467 + } else { 468 + unreachable!() 469 + } 470 + }; 471 + println!(" control: {:?}", control); 472 + group = group.add( 473 + svg::node::element::Path::new() 474 + .set( 475 + "d", 476 + svg::node::element::path::Data::new() 477 + .move_to((start.x(), start.y())) 478 + .quadratic_curve_to((control, (end.x(), end.y()))), 479 + ) 480 + .set( 481 + "style", 482 + match maybe_fill { 483 + // TODO 484 + Some(Fill::Solid(color)) => { 485 + format!("fill: none; stroke: {}; stroke-width: 2px;", color.to_string()) 486 + } 487 + _ => format!("fill: none; stroke: {}; stroke-width: 2px;", default_color), 488 + }, 489 + ), 490 + ); 491 + } 492 + Object::SmallCircle(center) => { 493 + println!("render: small_circle({:?})", center); 494 + group = group.add( 495 + svg::node::element::Circle::new() 496 + .set("cx", center.x()) 497 + .set("cy", center.y()) 498 + .set("r", 5) 499 + .set( 500 + "style", 501 + match maybe_fill { 502 + // TODO 503 + Some(Fill::Solid(color)) => { 504 + format!("fill: {};", color.to_string()) 505 + } 506 + _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color), 507 + }, 508 + ), 509 + ); 510 + } 511 + Object::Dot(center) => { 512 + println!("render: dot({:?})", center); 513 + group = group.add( 514 + svg::node::element::Circle::new() 515 + .set("cx", center.x()) 516 + .set("cy", center.y()) 517 + .set("r", 2) 518 + .set( 519 + "style", 520 + match maybe_fill { 521 + // TODO 522 + Some(Fill::Solid(color)) => { 523 + format!("fill: {};", color.to_string()) 524 + } 525 + _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color), 526 + }, 527 + ), 528 + ); 529 + } 530 + Object::BigCircle(center) => { 531 + println!("render: big_circle({:?})", center); 532 + group = group.add( 533 + svg::node::element::Circle::new() 534 + .set("cx", center.x()) 535 + .set("cy", center.y()) 536 + .set("r", 25) 537 + .set( 538 + "style", 539 + match maybe_fill { 540 + // TODO 541 + Some(Fill::Solid(color)) => { 542 + format!("fill: {};", color.to_string()) 543 + } 544 + _ => format!("fill: none; stroke: {}; stroke-width: 0.5px;", default_color), 545 + }, 546 + ), 547 + ); 548 + } 549 + } 550 + println!(" fill: {:?}", &maybe_fill); 551 + svg = svg.add(group); 552 + } 553 + // render a dotted grid 554 + if false { 555 + for x in vec![0, 25, 50, 75, 100] { 556 + for y in vec![0, 25, 50, 75, 100] { 557 + svg = svg.add( 558 + svg::node::element::Circle::new() 559 + .set("cx", x) 560 + .set("cy", y) 561 + .set("r", 0.5) 562 + .set("fill", "gray"), 563 + ); 564 + } 565 + } 566 + } 567 + svg.set("viewBox", "-10 -10 120 120").to_string() 568 + } 569 + } 133 570 134 - let straight_line = anchor 135 - .then_ignore(just("--").padded()) 136 - .then(anchor) 137 - .map(|(a, b)| Line::Line(a, b)); 138 - let inward_curve = anchor 139 - .then_ignore(one_of("n(").padded()) 140 - .then(anchor) 141 - .map(|(a, b)| Line::InwardCurve(a, b)); 142 - let outward_curve = anchor 143 - .then_ignore(one_of("u(").padded()) 144 - .then(anchor) 145 - .map(|(a, b)| Line::OutwardCurve(a, b)); 146 - let line = choice((straight_line, inward_curve, outward_curve)); 147 - let polygon = line.padded().repeated().boxed().map(Object::Polygon); 148 - let point = anchor 149 - .then_ignore(whitespace()) 150 - .then_ignore(just("point")) 151 - .map(Object::SmallCircle); 152 - let circle = center_anchor 153 - .then_ignore(whitespace()) 154 - .then_ignore(just("circle")) 155 - .map(Object::BigCircle); 156 - let dot = anchor 157 - .then_ignore(whitespace()) 158 - .then_ignore(just("dot")) 159 - .map(Object::Dot); 160 - let object = choice((polygon, point, dot, circle)); 161 - let color = choice(( 162 - just("black").to(Color::Named(ColorName::Black)), 163 - just("white").to(Color::Named(ColorName::White)), 164 - just("grey").to(Color::Named(ColorName::Grey)), 165 - just("red").to(Color::Named(ColorName::Red)), 166 - just("green").to(Color::Named(ColorName::Green)), 167 - just("blue").to(Color::Named(ColorName::Blue)), 168 - just("yellow").to(Color::Named(ColorName::Yellow)), 169 - just("cyan").to(Color::Named(ColorName::Cyan)), 170 - just("magenta").to(Color::Named(ColorName::Magenta)), 171 - just("orange").to(Color::Named(ColorName::Orange)), 172 - just("#").ignored().then(text::int(16)).map(|(_, i)| { 173 - Color::RGBA( 174 - ((i >> 24) & 0xFF) as u8, 175 - ((i >> 16) & 0xFF) as u8, 176 - ((i >> 8) & 0xFF) as u8, 177 - (i & 0xFF) as u8, 178 - ) 179 - }), 180 - )); 181 - let fill = choice(( 182 - just("filled with") 183 - .ignored() 184 - .then_ignore(whitespace()) 185 - .then(color) 186 - .map(|(_, c)| Fill::Solid(c)), 187 - just("hatched").to(Fill::Hatched), 188 - just("dotted").to(Fill::Dotted), 189 - )); 190 - let filled_object = just("[") 191 - .padded() 192 - .ignored() 193 - .then(object) 194 - .then_ignore(just("]").padded()) 195 - .then(fill) 196 - .map(|((_, o), f)| (o, Some(f))); 197 - let separator = newline().then_ignore(whitespace()).then_ignore(newline()); 198 - let header = take_until(just(":")) 199 - .then_ignore(whitespace()) 200 - .then_ignore(newline()) 201 - .map(|(i, _)| i); 202 - let shape = header 203 - .then_ignore(whitespace()) 204 - .then( 205 - filled_object 206 - .or(object.map(|o| (o, None))) 207 - .separated_by(newline()), 571 + impl Object { 572 + fn closed(self) -> bool { 573 + matches!( 574 + self, 575 + Object::Polygon(_, _) | Object::BigCircle(_) | Object::SmallCircle(_) | Object::Dot(_) 208 576 ) 209 - .map(|(name, objects)| Shape { 210 - name: name.into_iter().collect(), 211 - objects, 212 - }); 577 + } 578 + } 213 579 214 - shape.separated_by(separator) 215 - } 580 + // fn parser() -> impl Parser<char, Vec<Object>, Error = Simple<char>> { 581 + // let anchor = choice(( 582 + // just("top").to(Anchor::Top), 583 + // just("top right").to(Anchor::TopRight), 584 + // just("right").to(Anchor::Right), 585 + // just("bottom right").to(Anchor::BottomRight), 586 + // just("bottom").to(Anchor::Bottom), 587 + // just("bottom left").to(Anchor::BottomLeft), 588 + // just("left").to(Anchor::Left), 589 + // just("top left").to(Anchor::TopLeft), 590 + // just("center").to(Anchor::Center), 591 + // )); 592 + // let center_anchor = choice(( 593 + // just("top left").to(CenterAnchor::TopLeft), 594 + // just("top right").to(CenterAnchor::TopRight), 595 + // just("bottom left").to(CenterAnchor::BottomLeft), 596 + // just("bottom right").to(CenterAnchor::BottomRight), 597 + // just("center").to(CenterAnchor::Center), 598 + // )); 599 + 600 + // let straight_line = anchor.clone() 601 + // .then_ignore(just("--").padded()) 602 + // .then(anchor) 603 + // .map(|(a, b)| Line::Line(a, b)); 604 + // let inward_curve = &anchor 605 + // .then_ignore(one_of("n(").padded()) 606 + // .then(&anchor) 607 + // .map(|(a, b)| Line::InwardCurve(a, b)); 608 + // let outward_curve = &anchor 609 + // .then_ignore(one_of("u(").padded()) 610 + // .then(&anchor) 611 + // .map(|(a, b)| Line::OutwardCurve(a, b)); 612 + // let line = choice((straight_line, inward_curve, outward_curve)); 613 + // let polygon = line.padded().repeated().boxed().map(Object::Polygon); 614 + // let point = &anchor 615 + // .then_ignore(whitespace()) 616 + // .then_ignore(just("point")) 617 + // .map(Object::SmallCircle); 618 + // let circle = center_anchor 619 + // .then_ignore(whitespace()) 620 + // .then_ignore(just("circle")) 621 + // .map(Object::BigCircle); 622 + // let dot = &anchor 623 + // .then_ignore(whitespace()) 624 + // .then_ignore(just("dot")) 625 + // .map(Object::Dot); 626 + // let object = choice((polygon, point, dot, circle)); 627 + // // let color = choice(( 628 + // // text::ident().map(Color::Named), 629 + // // just("#").ignored().then(text::int(16)).map(|(_, i)| { 630 + // // Color::RGBA( 631 + // // ((i >> 24) & 0xFF) as u8, 632 + // // ((i >> 16) & 0xFF) as u8, 633 + // // ((i >> 8) & 0xFF) as u8, 634 + // // (i & 0xFF) as u8, 635 + // // ) 636 + // // }), 637 + // // )); 638 + // // let fill = choice(( 639 + // // just("filled with") 640 + // // .ignored() 641 + // // .then_ignore(whitespace()) 642 + // // .then(color) 643 + // // .map(|(_, c)| Fill::Solid(c)), 644 + // // just("hatched").to(Fill::Hatched), 645 + // // just("dotted").to(Fill::Dotted), 646 + // // )); 647 + // // let filled_object = just("[") 648 + // // .padded() 649 + // // .ignored() 650 + // // .then(object) 651 + // // .then_ignore(just("]").padded()) 652 + // // .then(fill) 653 + // // .map(|((_, o), f)| (o, Some(f))); 654 + // // let separator = newline().then_ignore(whitespace()).then_ignore(newline()); 655 + // // let header = take_until(just(":")) 656 + // // .then_ignore(whitespace()) 657 + // // .then_ignore(newline()) 658 + // // .map(|(i, _)| i); 659 + // // let shape = header 660 + // // .then_ignore(whitespace()) 661 + // // .then( 662 + // // filled_object 663 + // // .or(object.map(|o| (o, None))) 664 + // // .separated_by(newline()), 665 + // // ) 666 + // // .map(|(name, objects)| Shape { 667 + // // name: name.into_iter().collect(), 668 + // // objects, 669 + // // }); 670 + // // filled_object 671 + // // .or(object.map(|o| (o, None))) 672 + // // .separated_by(newline()) 673 + // object.separated_by(newline()) 674 + // }