aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2025-12-16 10:40:44 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2025-12-16 10:55:34 +0100
commitf08648e4b5a9f7855a2ed9068a386e2f3c596322 (patch)
tree1f1567ea609a1f74aaeab3cbbfdb249c5c47d723
Initial implementation
-rw-r--r--.gitignore4
-rw-r--r--Cargo.lock2052
-rw-r--r--Cargo.toml36
-rw-r--r--LICENSE661
-rw-r--r--Makefile38
-rw-r--r--README.md114
-rw-r--r--benches/huge.wmap1960
-rw-r--r--benches/rendering.rs25
-rw-r--r--examples/example.wmap122
-rw-r--r--examples/generate_map.rs38
-rw-r--r--examples/huge.wmap1960
-rw-r--r--src/configuration.rs195
-rw-r--r--src/geometry.rs142
-rw-r--r--src/lib.rs105
-rw-r--r--src/patterns.rs77
-rw-r--r--src/renderer.rs823
-rw-r--r--src/shapes.rs95
-rw-r--r--src/smart_positioning.rs316
-rw-r--r--src/stage_type.rs199
-rw-r--r--src/utils.rs53
-rw-r--r--tests/fixtures/simple.wmap13
-rw-r--r--tests/integration_test.rs63
22 files changed, 9091 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..41d198b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+example*.png
+example*.svg
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..4ea8935
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2052 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aligned"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
+dependencies = [
+ "as-slice",
+]
+
+[[package]]
+name = "aligned-vec"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
+dependencies = [
+ "equator",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anyhow"
+version = "1.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+
+[[package]]
+name = "arg_enum_proc_macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "as-slice"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "av-scenechange"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
+dependencies = [
+ "aligned",
+ "anyhow",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "log",
+ "num-rational",
+ "num-traits",
+ "pastey",
+ "rayon",
+ "thiserror 2.0.17",
+ "v_frame",
+ "y4m",
+]
+
+[[package]]
+name = "av1-grain"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
+dependencies = [
+ "anyhow",
+ "arrayvec",
+ "log",
+ "nom",
+ "num-rational",
+ "v_frame",
+]
+
+[[package]]
+name = "avif-serialize"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bit_field"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
+[[package]]
+name = "bitstream-io"
+version = "4.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
+dependencies = [
+ "core2",
+]
+
+[[package]]
+name = "built"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytemuck"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "cairo-rs"
+version = "0.20.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0"
+dependencies = [
+ "bitflags",
+ "cairo-sys-rs",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cc"
+version = "1.2.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "core2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools 0.10.5",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools 0.10.5",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "earcutr"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01"
+dependencies = [
+ "itertools 0.11.0",
+ "num-traits",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "equator"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
+dependencies = [
+ "equator-macro",
+]
+
+[[package]]
+name = "equator-macro"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "exr"
+version = "1.74.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
+dependencies = [
+ "bit_field",
+ "half",
+ "lebe",
+ "miniz_oxide",
+ "rayon-core",
+ "smallvec",
+ "zune-inflate",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fax"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
+dependencies = [
+ "fax_derive",
+]
+
+[[package]]
+name = "fax_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
+
+[[package]]
+name = "flate2"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "float_next_after"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "geo"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f811f663912a69249fa620dcd2a005db7254529da2d8a0b23942e81f47084501"
+dependencies = [
+ "earcutr",
+ "float_next_after",
+ "geo-types",
+ "geographiclib-rs",
+ "log",
+ "num-traits",
+ "robust",
+ "rstar",
+ "spade",
+]
+
+[[package]]
+name = "geo-types"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f8647af4005fa11da47cd56252c6ef030be8fa97bdbf355e7dfb6348f0a82c"
+dependencies = [
+ "approx",
+ "num-traits",
+ "rstar",
+ "serde",
+]
+
+[[package]]
+name = "geographiclib-rs"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "gif"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "glib"
+version = "0.20.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683"
+dependencies = [
+ "bitflags",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "memchr",
+ "smallvec",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.20.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.20.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "zerocopy",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "image"
+version = "0.25.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "color_quant",
+ "exr",
+ "gif",
+ "image-webp",
+ "moxcms",
+ "num-traits",
+ "png",
+ "qoi",
+ "ravif",
+ "rayon",
+ "rgb",
+ "tiff",
+ "zune-core 0.5.0",
+ "zune-jpeg 0.5.7",
+]
+
+[[package]]
+name = "image-webp"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
+dependencies = [
+ "byteorder-lite",
+ "quick-error 2.0.1",
+]
+
+[[package]]
+name = "imgref"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
+
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "interpolate_name"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lebe"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
+
+[[package]]
+name = "libc"
+version = "0.2.178"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
+dependencies = [
+ "arbitrary",
+ "cc",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "loop9"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
+dependencies = [
+ "imgref",
+]
+
+[[package]]
+name = "maybe-rayon"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
+dependencies = [
+ "cfg-if",
+ "rayon",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "noop_proc_macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pastey"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "png"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proptest"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
+dependencies = [
+ "bit-set",
+ "bit-vec",
+ "bitflags",
+ "num-traits",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "regex-syntax",
+ "rusty-fork",
+ "tempfile",
+ "unarray",
+]
+
+[[package]]
+name = "pxfm"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "qoi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rav1e"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
+dependencies = [
+ "aligned-vec",
+ "arbitrary",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "av-scenechange",
+ "av1-grain",
+ "bitstream-io",
+ "built",
+ "cfg-if",
+ "interpolate_name",
+ "itertools 0.14.0",
+ "libc",
+ "libfuzzer-sys",
+ "log",
+ "maybe-rayon",
+ "new_debug_unreachable",
+ "noop_proc_macro",
+ "num-derive",
+ "num-traits",
+ "paste",
+ "profiling",
+ "rand",
+ "rand_chacha",
+ "simd_helpers",
+ "thiserror 2.0.17",
+ "v_frame",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ravif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
+dependencies = [
+ "avif-serialize",
+ "imgref",
+ "loop9",
+ "quick-error 2.0.1",
+ "rav1e",
+ "rayon",
+ "rgb",
+]
+
+[[package]]
+name = "rayon"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "rgb"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
+
+[[package]]
+name = "robust"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
+
+[[package]]
+name = "rstar"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
+dependencies = [
+ "heapless",
+ "num-traits",
+ "smallvec",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "rusty-fork"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
+dependencies = [
+ "fnv",
+ "quick-error 1.2.3",
+ "tempfile",
+ "wait-timeout",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "simd_helpers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
+dependencies = [
+ "quote",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "spade"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990"
+dependencies = [
+ "hashbrown 0.15.5",
+ "num-traits",
+ "robust",
+ "smallvec",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "syn"
+version = "2.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "system-deps"
+version = "7.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
+dependencies = [
+ "cfg-expr",
+ "heck",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
+
+[[package]]
+name = "tempfile"
+version = "3.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl 2.0.17",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tiff"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
+dependencies = [
+ "fax",
+ "flate2",
+ "half",
+ "quick-error 2.0.1",
+ "weezl",
+ "zune-jpeg 0.4.21",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "toml"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "v_frame"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
+dependencies = [
+ "aligned-vec",
+ "num-traits",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version-compare"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "wmap-parser"
+version = "1.0.0"
+
+[[package]]
+name = "wmap-renderer"
+version = "0.1.0"
+dependencies = [
+ "cairo-rs",
+ "criterion",
+ "geo",
+ "image",
+ "once_cell",
+ "proptest",
+ "tempfile",
+ "thiserror 1.0.69",
+ "wmap-parser",
+]
+
+[[package]]
+name = "y4m"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-core"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773"
+
+[[package]]
+name = "zune-inflate"
+version = "0.2.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core 0.4.12",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
+dependencies = [
+ "zune-core 0.5.0",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0bc362d
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "wmap-renderer"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+# Core rendering
+cairo-rs = { version = "0.20", features = ["png", "svg"] }
+
+# Local dependency (not published)
+wmap-parser = { path = "../wmap-parser-rust" }
+
+# Geometry
+geo = "0.28"
+
+# Error handling
+thiserror = "1.0"
+
+# Utilities
+once_cell = "1.19"
+tempfile = "3.15"
+
+[dev-dependencies]
+# Testing
+criterion = "0.5"
+proptest = "1.4"
+image = "0.25"
+
+[[bench]]
+name = "rendering"
+harness = false
+
+[profile.release]
+opt-level = 3
+lto = true
+codegen-units = 1
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a9b196f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ wmap-renderer-rust, Rust wmap wardley map file renderer.
+ Copyright (C) 2025 Ruben Beltran del Rio
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0d4b7d3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,38 @@
+profile := dev
+target = $(shell rustc -vV | grep host | awk '{print $$2}')
+
+default: build
+
+set_rust:
+ rustup default stable
+
+prepare:
+ rustup target add $(target)
+
+build: prepare
+ cargo build --profile $(profile) --target $(target)
+
+test:
+ cargo test
+
+coverage:
+ cargo tarpaulin --fail-under 100
+
+benchmark:
+ cargo bench
+
+format:
+ cargo fmt && cargo clippy --fix
+
+lint:
+ cargo fmt -- --check && cargo clippy
+
+release:
+ cargo publish
+
+ci: lint coverage
+ifneq (,$(findstring refs/tags/,$(GIT_REF)))
+ $(MAKE) release
+endif
+
+.PHONY: default set_rust prepare build test coverage format lint benchmark release ci
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..98e45f2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,114 @@
+# Wardley Map Renderer (Rust)
+
+A high-performance Wardley map renderer written in Rust, providing complete feature parity with the JavaScript/Canvas implementation.
+
+## Features
+
+- ✅ **Multiple Output Formats**: PNG and SVG support via cairo-rs
+- ✅ **All Rendering Features**:
+ - Axes with customizable stage labels
+ - Components with 4 shape types (circle, square, triangle, x)
+ - Dependencies with optional arrowheads
+ - Evolution arrows with bidirectional support
+ - Inertia indicators
+ - Notes with multiline text support
+ - Groups with concave hull rendering
+ - Background evolution stage patterns
+- ✅ **21 Stage Types**: Activities, Behavior, Certainty, Comparison, Cynefin, Data, Decision Drivers, Efficiency, Failure, Focus of Value, Knowledge, Knowledge Management, Market, Market Action, Market Perception, Perception in Industry, Practice, Publication Types, Ubiquity, Understanding, User Perception, Evolution Stage
+- ✅ **Complete Configuration**: Colors, sizes, fonts, line heights, and opacity all configurable
+- ✅ **Visual Parity**: Matches JavaScript implementation output exactly
+
+## Usage
+
+```rust
+use wmap_renderer::{render_to_png, render_to_svg, Configuration, StageType};
+
+let map_source = r#"
+Tea (90, 50)
+Cup (80, 40)
+Tea -> Cup
+"#;
+
+let map = wmap_parser::parse(map_source);
+let config = Configuration::default();
+
+// Render to PNG
+let png_data = render_to_png(&map, StageType::Activities, &config)?;
+std::fs::write("map.png", png_data)?;
+
+// Render to SVG
+let svg_data = render_to_svg(&map, StageType::Activities, &config)?;
+std::fs::write("map.svg", svg_data)?;
+```
+
+## Configuration
+
+The renderer uses the same default configuration as the JavaScript version:
+
+- **Map Size**: 1300×1000 pixels (with 42px padding)
+- **Colors**: #0F261F for axes/vertices/labels, #FA2B00 for inertia, #4F8FE6 for evolution
+- **Fonts**: "Helvetica Neue, Helvetica, Arial, sans-serif"
+- **Sizes**: 25×25px vertices, 10px arrowheads, 0.5px line width
+
+All configuration options can be customized:
+
+```rust
+let mut config = Configuration::default();
+config.theme.colors.vertex = "#FF0000".to_string();
+config.theme.sizes.vertex_size = 30.0;
+config.options.show_background = false;
+```
+
+## Running Examples
+
+```bash
+cargo run --example generate_map
+```
+
+This will generate `example.png`, `example.svg`, and `example_cynefin.png` in the project root.
+
+## Running Tests
+
+```bash
+cargo test
+```
+
+All 22 tests should pass (17 unit tests + 5 integration tests).
+
+## Performance
+
+The Rust implementation is significantly faster than the JavaScript version:
+- Example map (~4 components): < 5ms
+- Large maps (100+ components): ~10-20ms
+- No garbage collection pauses
+- Efficient memory usage
+
+## Dependencies
+
+- **cairo-rs**: Vector graphics rendering (PNG, SVG support)
+- **geo**: Geometric algorithms (concave hull for groups)
+- **wmap-parser**: Wardley map source parser (local dependency)
+- **thiserror**: Error handling
+- **tempfile**: SVG output (temporary file handling)
+
+## Implementation Status
+
+### Completed Features
+- ✅ All rendering features (axes, vertices, dependencies, notes, inertias, evolutions, groups)
+- ✅ Background evolution stage patterns
+- ✅ All 21 stage types
+- ✅ All 4 vertex shapes
+- ✅ PNG and SVG output
+- ✅ Concave hull for groups
+- ✅ Configuration system matching JS defaults
+- ✅ Comprehensive test suite
+- ✅ Example programs
+
+### Future Optimizations (Optional)
+- ⏱ Smart label positioning algorithm (currently uses simple right-side positioning)
+- ⏱ Parallel rendering for large maps
+- ⏱ PDF output support
+
+## License
+
+See LICENSE file.
diff --git a/benches/huge.wmap b/benches/huge.wmap
new file mode 100644
index 0000000..c58f8c3
--- /dev/null
+++ b/benches/huge.wmap
@@ -0,0 +1,1960 @@
+[I] 0.25
+[II] 0.5
+[III] 0.75
+[IV] 1.0
+
+Component0 (9.95, 32.86) [X]
+Component1 (61.12, 59.35)
+Component2 (27.46, 61.14)
+Component3 (22.90, 50.28) [Circle]
+Component4 (8.84, 45.75) [Square]
+Component5 (34.22, 13.13) [Circle]
+Component6 (53.14, 32.02) [X]
+Component7 (88.66, 94.82)
+Component8 (26.58, 14.30) [Square]
+Component9 (87.07, 57.55) [X]
+Component10 (90.63, 12.80)
+Component11 (91.14, 18.89) [X]
+Component12 (9.25, 15.82) [Square]
+Component13 (23.84, 91.04) [Circle]
+Component14 (53.42, 50.15)
+Component15 (23.20, 1.09)
+Component16 (74.50, 14.99)
+Component17 (23.36, 83.91) [Triangle]
+Component18 (55.63, 41.97) [Triangle]
+Component19 (77.86, 4.08) [X]
+Component20 (16.02, 71.88) [X]
+Component21 (52.76, 85.80)
+Component22 (88.56, 76.18) [Square]
+Component23 (87.30, 28.67)
+Component24 (63.18, 77.45)
+Component25 (64.67, 24.42)
+Component26 (77.91, 68.51) [Square]
+Component27 (12.78, 49.59)
+Component28 (44.77, 15.02) [Circle]
+Component29 (58.72, 28.35) [Circle]
+Component30 (24.99, 58.90) [X]
+Component31 (25.36, 96.80)
+Component32 (39.32, 99.80) [Circle]
+Component33 (99.43, 39.47) [Circle]
+Component34 (81.44, 13.95)
+Component35 (16.49, 68.99)
+Component36 (7.24, 32.21)
+Component37 (3.68, 64.60) [X]
+Component38 (86.45, 26.42) [Circle]
+Component39 (76.81, 52.14)
+Component40 (58.24, 33.89)
+Component41 (69.90, 96.05)
+Component42 (32.45, 5.36) [Triangle]
+Component43 (64.31, 59.01) [Triangle]
+Component44 (85.51, 61.89)
+Component45 (74.47, 3.03)
+Component46 (59.49, 69.43) [Circle]
+Component47 (61.68, 28.42) [Square]
+Component48 (44.39, 8.48) [X]
+Component49 (72.53, 49.68)
+Component50 (36.04, 88.93) [Triangle]
+Component51 (5.46, 8.03)
+Component52 (70.84, 53.51) [X]
+Component53 (66.45, 12.27)
+Component54 (30.46, 81.10)
+Component55 (44.12, 3.73) [X]
+Component56 (38.53, 69.63) [Triangle]
+Component57 (3.78, 33.06) [Triangle]
+Component58 (6.34, 59.23)
+Component59 (44.77, 51.09) [Triangle]
+Component60 (34.70, 66.24) [Triangle]
+Component61 (68.32, 34.01)
+Component62 (2.66, 12.55)
+Component63 (48.15, 67.40) [X]
+Component64 (22.94, 92.05)
+Component65 (64.35, 62.72) [X]
+Component66 (62.49, 91.18) [Triangle]
+Component67 (37.77, 62.48) [Square]
+Component68 (61.24, 64.54)
+Component69 (8.86, 51.61) [Triangle]
+Component70 (4.27, 58.35) [Circle]
+Component71 (24.95, 89.36) [X]
+Component72 (63.65, 53.32) [Square]
+Component73 (71.29, 60.36) [Circle]
+Component74 (80.72, 87.52)
+Component75 (54.05, 36.18)
+Component76 (72.89, 59.69)
+Component77 (52.20, 58.81) [Circle]
+Component78 (55.08, 77.07) [Triangle]
+Component79 (55.84, 53.48)
+Component80 (29.05, 95.03)
+Component81 (49.75, 3.88)
+Component82 (45.87, 5.24)
+Component83 (52.70, 54.81)
+Component84 (77.16, 2.60) [Triangle]
+Component85 (57.90, 6.89) [Square]
+Component86 (31.42, 51.43) [Square]
+Component87 (84.99, 31.15)
+Component88 (97.75, 43.72) [Triangle]
+Component89 (62.25, 37.18) [X]
+Component90 (47.25, 5.86) [X]
+Component91 (44.99, 84.43) [Triangle]
+Component92 (83.02, 78.80) [X]
+Component93 (86.53, 22.19) [X]
+Component94 (29.50, 81.88) [Circle]
+Component95 (83.80, 12.03) [Circle]
+Component96 (6.33, 24.74)
+Component97 (96.48, 25.86)
+Component98 (35.34, 79.18)
+Component99 (58.59, 46.39) [Square]
+Component100 (87.06, 39.03) [Circle]
+Component101 (87.26, 56.31)
+Component102 (12.81, 16.15)
+Component103 (68.88, 6.87) [Square]
+Component104 (81.35, 46.02)
+Component105 (78.60, 19.95)
+Component106 (11.24, 77.61) [Circle]
+Component107 (19.09, 51.26)
+Component108 (76.93, 23.58)
+Component109 (1.85, 22.92)
+Component110 (18.90, 65.14) [Square]
+Component111 (24.65, 75.25)
+Component112 (1.87, 54.60)
+Component113 (88.58, 63.71)
+Component114 (97.17, 48.17) [Square]
+Component115 (51.49, 11.93) [Square]
+Component116 (17.79, 63.02) [X]
+Component117 (89.77, 18.04)
+Component118 (18.87, 79.15)
+Component119 (8.76, 76.67)
+Component120 (81.32, 63.54)
+Component121 (92.71, 98.97)
+Component122 (16.09, 28.54) [X]
+Component123 (48.43, 97.65)
+Component124 (50.37, 37.20)
+Component125 (50.74, 43.05) [Square]
+Component126 (75.40, 23.34)
+Component127 (73.92, 79.49) [Circle]
+Component128 (51.55, 20.51) [Circle]
+Component129 (70.45, 13.55) [Square]
+Component130 (57.22, 85.64) [X]
+Component131 (64.87, 52.42)
+Component132 (33.35, 93.91) [Square]
+Component133 (73.38, 47.59)
+Component134 (65.04, 86.19)
+Component135 (68.33, 7.91) [Triangle]
+Component136 (64.96, 64.20) [Circle]
+Component137 (12.52, 38.35)
+Component138 (10.65, 57.44) [Circle]
+Component139 (14.10, 12.62)
+Component140 (52.85, 26.35) [Triangle]
+Component141 (99.33, 72.83) [X]
+Component142 (58.66, 65.70) [X]
+Component143 (42.10, 3.23) [Triangle]
+Component144 (36.20, 47.76) [Circle]
+Component145 (75.65, 26.66) [Square]
+Component146 (21.33, 14.84)
+Component147 (21.88, 24.88) [Triangle]
+Component148 (55.89, 55.79) [Triangle]
+Component149 (10.73, 30.74) [Triangle]
+Component150 (13.37, 91.16)
+Component151 (83.17, 21.74)
+Component152 (95.03, 90.75)
+Component153 (84.26, 35.34) [X]
+Component154 (94.45, 87.23) [Triangle]
+Component155 (36.59, 75.57) [Circle]
+Component156 (42.65, 46.60) [Square]
+Component157 (6.47, 56.77) [Square]
+Component158 (44.90, 59.64) [Square]
+Component159 (76.18, 37.02) [Circle]
+Component160 (95.09, 86.50)
+Component161 (55.14, 41.38) [Triangle]
+Component162 (60.83, 35.01) [Circle]
+Component163 (10.27, 67.00) [Square]
+Component164 (76.09, 65.99) [Square]
+Component165 (49.23, 79.02) [Circle]
+Component166 (73.02, 29.83)
+Component167 (54.87, 64.16) [X]
+Component168 (52.68, 30.53) [Circle]
+Component169 (29.26, 87.91) [Triangle]
+Component170 (76.81, 44.95) [Triangle]
+Component171 (78.63, 55.50) [Circle]
+Component172 (72.66, 55.50) [X]
+Component173 (27.73, 64.42) [Triangle]
+Component174 (70.81, 23.22)
+Component175 (87.42, 5.68)
+Component176 (98.01, 65.20) [Triangle]
+Component177 (94.00, 8.83) [Circle]
+Component178 (80.21, 42.24)
+Component179 (10.48, 27.78) [Circle]
+Component180 (24.51, 49.49) [Square]
+Component181 (43.33, 70.82)
+Component182 (11.32, 32.01) [Circle]
+Component183 (99.25, 72.09)
+Component184 (48.90, 59.71)
+Component185 (57.10, 16.75) [Circle]
+Component186 (83.37, 34.12) [Triangle]
+Component187 (8.87, 29.27)
+Component188 (65.57, 61.54)
+Component189 (51.83, 71.22) [X]
+Component190 (27.04, 61.86) [Circle]
+Component191 (6.69, 41.47)
+Component192 (23.48, 62.15)
+Component193 (2.86, 64.07)
+Component194 (69.27, 74.67) [Circle]
+Component195 (66.48, 9.12) [Square]
+Component196 (28.76, 87.81)
+Component197 (26.97, 6.71)
+Component198 (15.19, 94.43) [Triangle]
+Component199 (57.23, 82.51) [Triangle]
+Component200 (42.97, 37.53)
+Component201 (29.44, 2.29)
+Component202 (76.46, 16.24) [Circle]
+Component203 (94.20, 17.90) [Circle]
+Component204 (30.13, 51.44) [Triangle]
+Component205 (68.98, 48.39)
+Component206 (24.23, 62.30) [Triangle]
+Component207 (24.83, 48.06)
+Component208 (8.33, 26.57) [X]
+Component209 (77.02, 35.30) [X]
+Component210 (74.96, 30.66) [Circle]
+Component211 (81.52, 58.49)
+Component212 (69.30, 92.38) [Circle]
+Component213 (40.11, 41.45) [Square]
+Component214 (73.47, 31.71) [X]
+Component215 (71.39, 7.86) [Triangle]
+Component216 (77.89, 60.06) [Circle]
+Component217 (44.90, 74.67) [Circle]
+Component218 (16.20, 4.97)
+Component219 (69.28, 44.39)
+Component220 (0.28, 68.43) [Circle]
+Component221 (8.56, 43.62)
+Component222 (5.11, 15.54) [X]
+Component223 (12.17, 11.72)
+Component224 (54.72, 10.39) [X]
+Component225 (30.14, 65.32) [Circle]
+Component226 (81.92, 66.20) [Circle]
+Component227 (43.65, 77.82) [Triangle]
+Component228 (56.60, 57.50)
+Component229 (28.99, 49.69) [Circle]
+Component230 (0.07, 44.12)
+Component231 (7.19, 10.93) [X]
+Component232 (54.32, 11.62) [X]
+Component233 (10.16, 30.70) [Triangle]
+Component234 (52.36, 88.84) [Square]
+Component235 (29.22, 81.16) [Square]
+Component236 (56.28, 34.89)
+Component237 (70.98, 18.55) [Triangle]
+Component238 (95.70, 52.33) [X]
+Component239 (86.93, 77.86) [Triangle]
+Component240 (43.57, 61.46) [Circle]
+Component241 (63.10, 56.33) [Circle]
+Component242 (90.98, 98.11)
+Component243 (43.27, 79.39) [Triangle]
+Component244 (14.72, 5.66)
+Component245 (92.51, 69.32) [Square]
+Component246 (78.17, 82.06) [X]
+Component247 (87.01, 27.90) [Triangle]
+Component248 (35.33, 38.07)
+Component249 (74.21, 13.77) [Circle]
+Component250 (94.92, 12.62)
+Component251 (85.66, 59.81)
+Component252 (96.82, 40.39) [X]
+Component253 (53.31, 54.76) [X]
+Component254 (78.65, 34.31)
+Component255 (63.15, 35.91) [X]
+Component256 (93.70, 14.87) [Square]
+Component257 (87.74, 38.01) [X]
+Component258 (59.70, 23.06) [Triangle]
+Component259 (24.24, 39.58) [Triangle]
+Component260 (70.42, 43.34)
+Component261 (11.55, 10.52) [Circle]
+Component262 (44.08, 56.93) [X]
+Component263 (67.31, 47.53) [Circle]
+Component264 (37.36, 90.08)
+Component265 (66.67, 53.36) [Triangle]
+Component266 (29.99, 41.40)
+Component267 (41.91, 74.03) [Triangle]
+Component268 (19.68, 31.07) [Triangle]
+Component269 (22.36, 32.00)
+Component270 (91.55, 6.41) [Square]
+Component271 (44.01, 17.22) [Square]
+Component272 (30.74, 29.46)
+Component273 (94.66, 90.42) [Triangle]
+Component274 (72.88, 98.16) [X]
+Component275 (67.60, 71.55)
+Component276 (15.18, 43.38) [Triangle]
+Component277 (35.33, 39.53)
+Component278 (94.24, 15.72) [Square]
+Component279 (7.41, 46.96) [X]
+Component280 (89.81, 55.32)
+Component281 (46.56, 78.51) [Circle]
+Component282 (82.99, 9.68) [Triangle]
+Component283 (47.47, 92.82) [X]
+Component284 (13.31, 26.85) [Square]
+Component285 (46.03, 43.09)
+Component286 (2.34, 57.09)
+Component287 (49.65, 31.24) [Circle]
+Component288 (83.79, 57.32)
+Component289 (6.61, 42.28) [Square]
+Component290 (47.25, 10.20) [Triangle]
+Component291 (20.57, 45.07) [Square]
+Component292 (15.35, 49.21) [Circle]
+Component293 (72.98, 80.41) [Triangle]
+Component294 (40.77, 79.66)
+Component295 (91.69, 47.22) [X]
+Component296 (10.54, 48.39) [X]
+Component297 (71.89, 61.99) [Square]
+Component298 (10.28, 43.01)
+Component299 (53.88, 72.55)
+Component300 (9.35, 56.50)
+Component301 (28.09, 6.29) [Circle]
+Component302 (58.57, 73.35)
+Component303 (84.96, 51.81)
+Component304 (83.02, 53.58) [X]
+Component305 (79.51, 51.53) [X]
+Component306 (14.24, 99.49)
+Component307 (8.57, 85.13)
+Component308 (43.93, 30.83)
+Component309 (80.62, 29.87) [X]
+Component310 (68.72, 48.61) [X]
+Component311 (2.37, 81.53)
+Component312 (72.73, 68.09) [Circle]
+Component313 (30.74, 2.94) [Circle]
+Component314 (62.13, 68.91) [X]
+Component315 (73.38, 43.60) [Circle]
+Component316 (27.37, 98.57)
+Component317 (85.82, 67.69) [Circle]
+Component318 (45.24, 69.35) [Circle]
+Component319 (2.34, 66.07) [X]
+Component320 (67.45, 72.20)
+Component321 (88.21, 91.50) [Triangle]
+Component322 (47.99, 43.92)
+Component323 (40.33, 85.89)
+Component324 (1.64, 52.02) [Square]
+Component325 (82.44, 91.99) [Square]
+Component326 (64.93, 6.74)
+Component327 (69.13, 32.76)
+Component328 (18.83, 7.75) [X]
+Component329 (83.61, 30.14) [Square]
+Component330 (49.46, 98.58)
+Component331 (53.31, 1.37) [Circle]
+Component332 (15.54, 23.31) [Circle]
+Component333 (81.39, 44.90)
+Component334 (40.59, 17.35) [Square]
+Component335 (96.35, 80.15) [Circle]
+Component336 (57.23, 15.80) [X]
+Component337 (69.45, 58.61)
+Component338 (75.96, 32.46) [X]
+Component339 (6.37, 65.81) [Triangle]
+Component340 (59.14, 70.50)
+Component341 (79.03, 41.50) [Square]
+Component342 (35.41, 80.78)
+Component343 (87.18, 45.70)
+Component344 (81.33, 58.50)
+Component345 (50.63, 17.49) [Square]
+Component346 (10.10, 83.23)
+Component347 (74.57, 16.10) [X]
+Component348 (42.80, 0.78)
+Component349 (58.17, 89.85) [Triangle]
+Component350 (80.79, 52.51) [X]
+Component351 (56.34, 15.08) [Circle]
+Component352 (98.43, 1.47) [Circle]
+Component353 (19.88, 99.79) [Circle]
+Component354 (51.99, 2.67) [Square]
+Component355 (44.85, 23.76) [Square]
+Component356 (14.24, 98.11) [Circle]
+Component357 (25.68, 57.61) [Square]
+Component358 (5.75, 71.94) [Square]
+Component359 (68.05, 63.63) [Square]
+Component360 (12.81, 46.56) [Square]
+Component361 (56.35, 20.51) [Square]
+Component362 (43.13, 82.54) [Circle]
+Component363 (72.98, 59.02) [Triangle]
+Component364 (67.80, 56.66) [Circle]
+Component365 (58.12, 58.28) [Square]
+Component366 (81.73, 0.72)
+Component367 (22.11, 82.65)
+Component368 (98.81, 19.86) [X]
+Component369 (17.27, 57.91)
+Component370 (71.83, 47.45)
+Component371 (44.85, 0.45) [Square]
+Component372 (57.89, 51.13) [Circle]
+Component373 (63.17, 2.05) [Square]
+Component374 (44.72, 91.15)
+Component375 (71.04, 46.51) [Triangle]
+Component376 (3.32, 46.78) [Triangle]
+Component377 (72.48, 23.81) [Circle]
+Component378 (98.32, 26.85)
+Component379 (23.27, 92.77) [X]
+Component380 (70.01, 22.45) [Square]
+Component381 (86.92, 0.65) [X]
+Component382 (0.25, 21.60) [Square]
+Component383 (3.44, 79.21)
+Component384 (35.36, 27.11) [X]
+Component385 (45.80, 29.73) [Triangle]
+Component386 (21.58, 74.37) [X]
+Component387 (76.83, 29.78)
+Component388 (11.95, 69.94)
+Component389 (10.05, 71.48)
+Component390 (11.26, 80.96)
+Component391 (89.52, 30.54) [Square]
+Component392 (59.05, 40.71) [Triangle]
+Component393 (60.09, 97.03) [X]
+Component394 (17.57, 50.48) [X]
+Component395 (36.74, 53.99) [Triangle]
+Component396 (70.58, 10.19)
+Component397 (70.27, 24.32) [Circle]
+Component398 (96.61, 90.28) [Triangle]
+Component399 (85.63, 16.61) [X]
+Component400 (22.40, 43.77) [Circle]
+Component401 (31.86, 93.15) [Triangle]
+Component402 (10.49, 77.04) [X]
+Component403 (61.76, 14.09) [Circle]
+Component404 (14.98, 44.56) [Square]
+Component405 (1.37, 41.79)
+Component406 (66.89, 99.29)
+Component407 (54.72, 26.16) [Circle]
+Component408 (4.02, 78.88) [X]
+Component409 (61.57, 55.03) [X]
+Component410 (15.00, 72.61) [Circle]
+Component411 (30.84, 58.13)
+Component412 (74.62, 34.29)
+Component413 (87.16, 42.02)
+Component414 (42.56, 13.70) [Triangle]
+Component415 (63.00, 19.68) [Circle]
+Component416 (84.00, 85.81) [Circle]
+Component417 (99.85, 26.55)
+Component418 (2.32, 39.70)
+Component419 (50.24, 60.73)
+Component420 (50.87, 91.30) [Square]
+Component421 (89.47, 44.46)
+Component422 (57.31, 31.98) [Circle]
+Component423 (35.68, 39.96) [Circle]
+Component424 (25.96, 65.24)
+Component425 (18.03, 51.91)
+Component426 (29.60, 99.06) [Triangle]
+Component427 (19.24, 90.42)
+Component428 (41.83, 16.49) [X]
+Component429 (23.20, 79.00) [Square]
+Component430 (58.74, 20.49) [Circle]
+Component431 (12.51, 51.74) [Triangle]
+Component432 (36.63, 90.02) [Square]
+Component433 (2.31, 38.41)
+Component434 (71.42, 22.16)
+Component435 (19.13, 72.21) [Triangle]
+Component436 (96.97, 89.38)
+Component437 (36.78, 84.85)
+Component438 (41.40, 18.25)
+Component439 (99.42, 98.89) [X]
+Component440 (65.90, 64.04) [Triangle]
+Component441 (66.50, 54.30)
+Component442 (25.00, 1.22)
+Component443 (91.63, 77.36) [X]
+Component444 (91.54, 35.26) [Square]
+Component445 (68.43, 4.16) [Circle]
+Component446 (51.33, 47.47) [Circle]
+Component447 (19.82, 70.34) [Triangle]
+Component448 (87.82, 97.43)
+Component449 (47.41, 73.13) [X]
+Component450 (9.65, 4.74) [Circle]
+Component451 (16.64, 31.95) [Circle]
+Component452 (98.44, 49.75) [Triangle]
+Component453 (12.91, 75.20)
+Component454 (68.13, 57.99)
+Component455 (88.52, 73.61) [Circle]
+Component456 (43.00, 47.91)
+Component457 (67.28, 76.60) [Circle]
+Component458 (30.11, 20.41) [Triangle]
+Component459 (32.38, 7.73)
+Component460 (48.65, 59.39) [Circle]
+Component461 (95.93, 13.43) [Circle]
+Component462 (76.05, 97.64) [Triangle]
+Component463 (10.57, 5.86)
+Component464 (15.16, 65.67) [Triangle]
+Component465 (49.29, 37.41)
+Component466 (62.86, 55.21)
+Component467 (41.70, 22.98) [X]
+Component468 (11.40, 42.51) [Square]
+Component469 (99.96, 9.88) [X]
+Component470 (74.36, 18.50) [Triangle]
+Component471 (27.71, 2.13) [X]
+Component472 (37.76, 41.14)
+Component473 (54.41, 69.25) [Triangle]
+Component474 (19.69, 0.90)
+Component475 (33.44, 22.53) [X]
+Component476 (78.46, 53.81)
+Component477 (27.36, 94.83) [X]
+Component478 (53.98, 15.10) [Triangle]
+Component479 (49.75, 90.23) [Square]
+Component480 (26.13, 83.45)
+Component481 (75.99, 39.97)
+Component482 (17.03, 54.73) [Triangle]
+Component483 (10.89, 86.65)
+Component484 (37.28, 90.48)
+Component485 (59.96, 76.87) [Triangle]
+Component486 (80.36, 98.15)
+Component487 (9.26, 96.01)
+Component488 (62.92, 30.57) [Square]
+Component489 (50.52, 20.80) [X]
+Component490 (23.88, 11.84)
+Component491 (77.93, 5.51)
+Component492 (56.88, 8.00)
+Component493 (86.70, 55.53) [Square]
+Component494 (62.22, 67.78) [Triangle]
+Component495 (50.63, 55.06) [Circle]
+Component496 (6.86, 22.43) [X]
+Component497 (93.32, 40.43) [Circle]
+Component498 (96.81, 51.60)
+Component499 (59.56, 57.21) [Square]
+Component500 (2.54, 51.36)
+Component501 (88.77, 48.72) [Square]
+Component502 (80.17, 78.87) [Circle]
+Component503 (63.34, 14.75) [Square]
+Component504 (21.89, 35.77) [Circle]
+Component505 (17.52, 41.69) [X]
+Component506 (59.66, 93.27) [X]
+Component507 (44.93, 75.12) [Square]
+Component508 (2.75, 88.38) [Triangle]
+Component509 (21.28, 6.63) [Square]
+Component510 (86.03, 50.85) [Circle]
+Component511 (44.85, 98.92) [Square]
+Component512 (82.29, 88.28)
+Component513 (99.39, 16.41) [Square]
+Component514 (38.85, 11.93) [Circle]
+Component515 (79.06, 95.86) [X]
+Component516 (0.49, 66.17) [X]
+Component517 (34.87, 73.24)
+Component518 (57.08, 37.29)
+Component519 (59.70, 27.17)
+Component520 (26.20, 98.63) [Square]
+Component521 (73.63, 43.73) [X]
+Component522 (52.00, 34.57) [X]
+Component523 (61.51, 25.40) [Triangle]
+Component524 (49.91, 28.64)
+Component525 (5.83, 22.08) [X]
+Component526 (62.78, 28.57)
+Component527 (17.11, 32.92) [X]
+Component528 (76.93, 82.48) [Circle]
+Component529 (18.24, 51.31)
+Component530 (35.40, 4.42)
+Component531 (65.91, 71.77) [X]
+Component532 (51.22, 26.10) [X]
+Component533 (32.79, 13.41) [X]
+Component534 (91.34, 95.42) [X]
+Component535 (84.52, 23.28) [Circle]
+Component536 (46.75, 70.21)
+Component537 (73.34, 99.55) [Triangle]
+Component538 (21.28, 97.53) [Square]
+Component539 (53.13, 34.63) [Circle]
+Component540 (45.98, 67.68)
+Component541 (76.47, 11.65) [Square]
+Component542 (8.72, 99.30) [Square]
+Component543 (40.19, 84.35) [Square]
+Component544 (54.66, 66.40) [Triangle]
+Component545 (30.53, 31.73) [Square]
+Component546 (72.43, 41.03)
+Component547 (77.05, 93.93)
+Component548 (84.24, 4.94)
+Component549 (48.93, 2.90) [Square]
+Component550 (22.94, 7.40)
+Component551 (91.81, 28.17)
+Component552 (12.27, 44.62) [Circle]
+Component553 (85.09, 90.79) [Circle]
+Component554 (14.90, 44.18) [X]
+Component555 (77.18, 32.00) [X]
+Component556 (90.01, 54.51)
+Component557 (21.88, 92.41) [Circle]
+Component558 (3.92, 26.93) [X]
+Component559 (17.08, 7.74)
+Component560 (96.04, 1.34) [Circle]
+Component561 (32.55, 12.12) [X]
+Component562 (32.12, 73.08) [Triangle]
+Component563 (34.09, 73.03) [Triangle]
+Component564 (83.10, 17.21) [Triangle]
+Component565 (47.20, 62.09) [Square]
+Component566 (66.85, 41.75) [X]
+Component567 (56.32, 73.47) [Square]
+Component568 (63.74, 14.66)
+Component569 (29.04, 21.96) [X]
+Component570 (40.40, 10.86)
+Component571 (5.16, 84.97) [Circle]
+Component572 (38.42, 2.42) [Square]
+Component573 (85.44, 74.93) [Square]
+Component574 (87.91, 0.60) [Square]
+Component575 (26.21, 7.64) [X]
+Component576 (74.56, 16.79) [Square]
+Component577 (77.85, 68.39)
+Component578 (62.92, 87.85) [X]
+Component579 (70.76, 28.86) [Circle]
+Component580 (82.86, 24.47) [X]
+Component581 (4.56, 9.62) [Triangle]
+Component582 (37.45, 94.56) [Circle]
+Component583 (12.14, 30.34) [X]
+Component584 (3.95, 57.21) [Square]
+Component585 (38.37, 72.37)
+Component586 (25.84, 82.02)
+Component587 (23.21, 59.31) [Square]
+Component588 (45.93, 79.55)
+Component589 (55.65, 75.30) [Square]
+Component590 (75.80, 51.42)
+Component591 (12.33, 87.97)
+Component592 (76.12, 25.18) [X]
+Component593 (42.48, 48.63)
+Component594 (83.10, 44.41) [Triangle]
+Component595 (85.27, 79.81) [Triangle]
+Component596 (2.29, 76.07)
+Component597 (79.85, 49.82)
+Component598 (48.10, 41.86) [X]
+Component599 (30.99, 12.78) [Circle]
+Component600 (85.67, 2.54) [Circle]
+Component601 (34.27, 62.27)
+Component602 (64.46, 94.92) [Triangle]
+Component603 (39.70, 37.16)
+Component604 (17.25, 23.20) [Circle]
+Component605 (84.77, 93.12) [Circle]
+Component606 (20.36, 65.56)
+Component607 (40.75, 34.65)
+Component608 (78.99, 67.22) [X]
+Component609 (33.60, 63.19) [X]
+Component610 (96.15, 23.34) [X]
+Component611 (94.94, 23.74)
+Component612 (18.31, 38.34) [Triangle]
+Component613 (60.20, 31.49) [Square]
+Component614 (71.73, 44.16) [X]
+Component615 (93.85, 69.80) [Triangle]
+Component616 (4.89, 51.46)
+Component617 (92.34, 23.59) [Square]
+Component618 (79.12, 23.87) [X]
+Component619 (5.24, 26.52) [Circle]
+Component620 (66.10, 17.05) [X]
+Component621 (80.15, 91.21)
+Component622 (60.56, 35.33)
+Component623 (38.84, 73.56) [X]
+Component624 (32.94, 65.46) [Square]
+Component625 (72.03, 93.52)
+Component626 (51.66, 1.88)
+Component627 (43.42, 69.63)
+Component628 (68.93, 78.58) [Triangle]
+Component629 (12.17, 88.50) [Square]
+Component630 (25.74, 39.64)
+Component631 (89.94, 17.59) [Triangle]
+Component632 (18.03, 5.97) [Square]
+Component633 (63.08, 82.42) [Circle]
+Component634 (64.06, 7.17) [Circle]
+Component635 (30.33, 44.27) [Triangle]
+Component636 (89.42, 2.98) [Square]
+Component637 (11.69, 51.64) [Triangle]
+Component638 (27.67, 38.16) [X]
+Component639 (82.42, 96.29) [Square]
+Component640 (94.21, 94.49)
+Component641 (8.58, 71.71)
+Component642 (70.45, 91.93)
+Component643 (10.96, 1.29) [Triangle]
+Component644 (10.57, 19.18) [Triangle]
+Component645 (57.76, 83.31) [Square]
+Component646 (12.13, 8.42) [Circle]
+Component647 (44.58, 80.07) [X]
+Component648 (81.68, 24.58)
+Component649 (52.65, 92.38)
+Component650 (39.35, 41.67) [Triangle]
+Component651 (55.90, 65.04)
+Component652 (67.25, 14.83) [Square]
+Component653 (8.49, 24.18)
+Component654 (65.87, 40.83) [X]
+Component655 (3.18, 17.64) [Square]
+Component656 (25.59, 33.81)
+Component657 (73.70, 19.39)
+Component658 (21.35, 87.93) [Square]
+Component659 (64.18, 97.24) [Square]
+Component660 (32.48, 6.54) [X]
+Component661 (86.73, 65.34) [Circle]
+Component662 (98.31, 91.84) [Triangle]
+Component663 (48.72, 75.26) [Square]
+Component664 (33.81, 25.98) [Triangle]
+Component665 (83.92, 87.11) [Triangle]
+Component666 (36.37, 54.35)
+Component667 (70.76, 58.70) [Circle]
+Component668 (87.91, 69.42)
+Component669 (5.06, 12.39)
+Component670 (22.26, 4.78)
+Component671 (82.28, 96.57) [Triangle]
+Component672 (28.71, 40.93) [Circle]
+Component673 (6.42, 25.09) [Square]
+Component674 (57.90, 76.51)
+Component675 (46.54, 26.90) [Triangle]
+Component676 (6.60, 37.36)
+Component677 (67.94, 26.83)
+Component678 (70.57, 66.78) [Triangle]
+Component679 (92.63, 29.54)
+Component680 (21.06, 56.15)
+Component681 (30.12, 87.84) [Square]
+Component682 (22.50, 17.49) [Triangle]
+Component683 (22.16, 99.28) [Square]
+Component684 (86.25, 85.65) [Triangle]
+Component685 (42.38, 97.43) [Square]
+Component686 (82.82, 98.13) [Triangle]
+Component687 (85.99, 22.81) [Square]
+Component688 (92.42, 13.14) [Circle]
+Component689 (86.08, 22.54) [Circle]
+Component690 (78.61, 48.10) [Square]
+Component691 (2.95, 70.77)
+Component692 (96.12, 92.64) [Square]
+Component693 (12.50, 97.26) [Square]
+Component694 (86.32, 25.85)
+Component695 (80.51, 28.95) [Triangle]
+Component696 (46.82, 6.61)
+Component697 (66.87, 17.17) [Circle]
+Component698 (99.04, 68.95) [Triangle]
+Component699 (77.10, 12.58) [Triangle]
+Component700 (81.22, 95.50) [Square]
+Component701 (58.44, 84.13)
+Component702 (80.19, 61.91)
+Component703 (96.80, 74.86) [Circle]
+Component704 (83.91, 79.21) [Circle]
+Component705 (94.51, 96.03) [Triangle]
+Component706 (51.67, 13.80)
+Component707 (29.79, 8.71) [Circle]
+Component708 (36.15, 69.18) [Triangle]
+Component709 (68.99, 71.52) [Circle]
+Component710 (88.45, 64.95) [Circle]
+Component711 (80.05, 71.13) [Circle]
+Component712 (50.04, 22.87) [Triangle]
+Component713 (17.38, 28.08) [Circle]
+Component714 (88.91, 43.58) [Triangle]
+Component715 (82.29, 88.78) [Circle]
+Component716 (68.36, 82.95) [Square]
+Component717 (10.29, 93.22)
+Component718 (92.84, 27.40)
+Component719 (39.48, 91.46) [Triangle]
+Component720 (69.69, 83.40)
+Component721 (33.85, 40.52) [Square]
+Component722 (97.83, 99.99) [Triangle]
+Component723 (62.84, 11.09) [X]
+Component724 (43.10, 47.36) [Triangle]
+Component725 (17.38, 82.24) [Triangle]
+Component726 (54.82, 94.05)
+Component727 (5.63, 25.27) [X]
+Component728 (80.85, 80.59) [Triangle]
+Component729 (59.77, 7.37) [X]
+Component730 (68.88, 77.36) [X]
+Component731 (93.49, 80.85) [Square]
+Component732 (52.99, 75.23) [Circle]
+Component733 (37.52, 58.87)
+Component734 (9.84, 11.57) [Triangle]
+Component735 (32.67, 39.28) [X]
+Component736 (46.91, 23.64) [Square]
+Component737 (33.46, 92.43) [Triangle]
+Component738 (60.48, 16.10) [X]
+Component739 (60.30, 74.77)
+Component740 (97.63, 8.09)
+Component741 (11.78, 92.13)
+Component742 (41.21, 41.45) [Circle]
+Component743 (4.93, 37.13) [X]
+Component744 (13.29, 7.31)
+Component745 (69.09, 18.30) [Circle]
+Component746 (49.88, 88.56)
+Component747 (66.69, 64.64) [X]
+Component748 (71.42, 24.62) [Triangle]
+Component749 (39.46, 93.61)
+Component750 (2.08, 22.34) [Circle]
+Component751 (89.58, 41.54)
+Component752 (47.94, 84.87)
+Component753 (65.47, 55.58) [X]
+Component754 (53.54, 5.12) [Square]
+Component755 (74.49, 17.69) [Square]
+Component756 (12.88, 88.92) [X]
+Component757 (52.18, 50.44) [X]
+Component758 (68.05, 52.05) [Circle]
+Component759 (93.47, 30.01) [Circle]
+Component760 (94.90, 99.55) [X]
+Component761 (84.05, 4.44) [Circle]
+Component762 (42.74, 36.71) [Square]
+Component763 (85.63, 69.38) [X]
+Component764 (65.40, 66.21) [Triangle]
+Component765 (21.67, 14.78) [Circle]
+Component766 (53.61, 70.93) [Triangle]
+Component767 (5.07, 78.99)
+Component768 (53.15, 36.64) [Circle]
+Component769 (46.97, 1.41)
+Component770 (33.90, 44.43)
+Component771 (39.45, 36.48)
+Component772 (67.78, 90.87) [Circle]
+Component773 (96.24, 3.57) [Triangle]
+Component774 (41.37, 82.60) [Triangle]
+Component775 (95.15, 52.79) [Square]
+Component776 (20.68, 54.81)
+Component777 (84.49, 48.27) [Circle]
+Component778 (99.01, 87.63)
+Component779 (49.10, 13.34)
+Component780 (65.15, 86.57)
+Component781 (91.51, 32.85)
+Component782 (60.79, 26.62) [Triangle]
+Component783 (34.55, 24.16)
+Component784 (25.73, 57.46)
+Component785 (23.74, 52.01)
+Component786 (21.19, 90.75) [Triangle]
+Component787 (83.22, 80.23) [Square]
+Component788 (48.28, 63.70) [Triangle]
+Component789 (47.02, 71.16)
+Component790 (98.87, 44.60)
+Component791 (80.99, 3.44) [Circle]
+Component792 (41.82, 95.50) [Triangle]
+Component793 (69.54, 96.23) [Triangle]
+Component794 (49.78, 89.18)
+Component795 (43.14, 80.71) [Triangle]
+Component796 (70.04, 8.33)
+Component797 (32.29, 29.23) [Square]
+Component798 (58.10, 42.58)
+Component799 (64.07, 98.61) [Circle]
+Component800 (35.86, 61.33) [X]
+Component801 (8.55, 54.52)
+Component802 (55.95, 99.76) [Triangle]
+Component803 (19.90, 4.60) [Circle]
+Component804 (49.99, 96.57)
+Component805 (73.19, 42.31) [Triangle]
+Component806 (52.10, 15.63) [Circle]
+Component807 (89.95, 99.16) [Square]
+Component808 (82.80, 94.93)
+Component809 (90.47, 43.12)
+Component810 (36.98, 51.49)
+Component811 (99.92, 14.67) [Square]
+Component812 (62.57, 8.28)
+Component813 (36.85, 24.38)
+Component814 (2.59, 63.52) [Square]
+Component815 (98.73, 86.84)
+Component816 (29.92, 3.73)
+Component817 (90.50, 63.09)
+Component818 (76.17, 28.16) [Triangle]
+Component819 (75.38, 42.93) [Square]
+Component820 (86.39, 19.53)
+Component821 (65.52, 54.88)
+Component822 (31.16, 50.90) [Square]
+Component823 (58.12, 53.66)
+Component824 (95.46, 46.39) [X]
+Component825 (76.11, 29.03)
+Component826 (38.60, 28.10) [X]
+Component827 (96.21, 96.79)
+Component828 (58.19, 88.64)
+Component829 (94.16, 47.60) [Square]
+Component830 (40.67, 86.94)
+Component831 (30.28, 55.52) [Square]
+Component832 (17.24, 40.01) [Square]
+Component833 (83.82, 84.98) [X]
+Component834 (67.50, 79.68)
+Component835 (30.15, 53.82) [Circle]
+Component836 (92.23, 78.01) [Triangle]
+Component837 (27.23, 92.60) [Square]
+Component838 (31.61, 25.63) [X]
+Component839 (61.71, 44.33) [Square]
+Component840 (78.48, 69.79) [Circle]
+Component841 (44.89, 97.80) [X]
+Component842 (48.90, 49.06) [Triangle]
+Component843 (15.44, 16.25) [Triangle]
+Component844 (8.31, 94.22)
+Component845 (31.64, 49.32)
+Component846 (75.81, 94.49) [X]
+Component847 (99.41, 85.31) [Circle]
+Component848 (91.64, 32.44) [Square]
+Component849 (49.66, 55.99)
+Component850 (90.85, 80.95)
+Component851 (20.72, 44.29) [X]
+Component852 (66.52, 1.30) [Square]
+Component853 (9.92, 86.11) [X]
+Component854 (33.23, 85.49)
+Component855 (65.31, 70.68) [Square]
+Component856 (17.92, 62.02) [Square]
+Component857 (95.52, 64.69)
+Component858 (11.31, 0.13)
+Component859 (59.34, 97.52)
+Component860 (17.28, 96.61)
+Component861 (33.92, 80.85) [Square]
+Component862 (54.06, 3.36) [Circle]
+Component863 (46.14, 16.09) [Triangle]
+Component864 (77.52, 15.25)
+Component865 (1.65, 25.12) [X]
+Component866 (11.77, 25.49)
+Component867 (41.81, 74.80) [Circle]
+Component868 (6.72, 7.94) [X]
+Component869 (78.18, 76.61)
+Component870 (84.57, 14.90) [Circle]
+Component871 (27.36, 76.49) [Square]
+Component872 (74.01, 20.52) [Circle]
+Component873 (72.83, 82.87)
+Component874 (9.74, 46.82) [X]
+Component875 (85.55, 26.40) [X]
+Component876 (90.96, 12.96) [Circle]
+Component877 (83.21, 30.43)
+Component878 (18.32, 76.50) [X]
+Component879 (64.86, 10.96) [Circle]
+Component880 (20.28, 82.11) [Square]
+Component881 (53.75, 89.58) [X]
+Component882 (60.19, 95.72) [Square]
+Component883 (18.98, 48.08) [X]
+Component884 (72.61, 93.24) [Circle]
+Component885 (35.46, 4.72)
+Component886 (52.80, 41.57) [Square]
+Component887 (53.44, 29.95)
+Component888 (10.23, 58.60) [Square]
+Component889 (68.29, 30.06) [Square]
+Component890 (76.88, 68.56)
+Component891 (31.04, 37.55) [Square]
+Component892 (17.55, 78.76)
+Component893 (10.10, 12.05) [X]
+Component894 (73.22, 27.89) [Square]
+Component895 (60.35, 81.53)
+Component896 (4.47, 73.49) [Triangle]
+Component897 (8.72, 67.26)
+Component898 (26.71, 23.78)
+Component899 (81.72, 36.94) [Triangle]
+Component900 (16.01, 86.38) [Square]
+Component901 (55.07, 85.27) [Triangle]
+Component902 (70.30, 12.88)
+Component903 (60.31, 62.86) [Triangle]
+Component904 (41.00, 37.55) [Triangle]
+Component905 (9.64, 61.89) [Circle]
+Component906 (54.10, 31.77) [X]
+Component907 (76.54, 62.41)
+Component908 (86.17, 96.56)
+Component909 (46.85, 49.44)
+Component910 (37.67, 41.22) [Triangle]
+Component911 (26.66, 62.43) [Triangle]
+Component912 (69.43, 3.66)
+Component913 (33.47, 70.71) [Square]
+Component914 (93.21, 77.98) [Circle]
+Component915 (90.74, 9.07)
+Component916 (93.97, 16.70) [Square]
+Component917 (8.15, 60.87)
+Component918 (50.09, 96.07) [Square]
+Component919 (75.88, 86.17) [Circle]
+Component920 (68.31, 86.98) [X]
+Component921 (67.83, 79.88)
+Component922 (97.10, 82.63) [X]
+Component923 (83.01, 1.06) [X]
+Component924 (39.58, 86.43) [Circle]
+Component925 (47.55, 84.77)
+Component926 (96.69, 55.45) [X]
+Component927 (77.39, 79.82)
+Component928 (43.07, 76.23) [X]
+Component929 (78.14, 10.36) [Triangle]
+Component930 (30.33, 63.76) [X]
+Component931 (41.25, 52.50)
+Component932 (31.96, 82.30)
+Component933 (65.06, 59.05) [Circle]
+Component934 (26.94, 44.22) [Circle]
+Component935 (27.72, 29.76)
+Component936 (70.78, 0.65) [Triangle]
+Component937 (58.58, 74.89)
+Component938 (98.48, 3.16)
+Component939 (17.30, 75.27)
+Component940 (48.66, 72.60) [X]
+Component941 (85.22, 46.47) [Triangle]
+Component942 (67.36, 93.17) [Triangle]
+Component943 (79.00, 3.03)
+Component944 (93.55, 16.21)
+Component945 (33.57, 39.55)
+Component946 (30.28, 29.41)
+Component947 (98.43, 70.60) [X]
+Component948 (96.67, 13.72) [Triangle]
+Component949 (38.28, 48.43) [Triangle]
+Component950 (88.84, 46.42) [Circle]
+Component951 (73.14, 28.70) [X]
+Component952 (64.62, 10.80) [X]
+Component953 (17.27, 35.05)
+Component954 (28.44, 50.29) [Circle]
+Component955 (68.78, 52.58)
+Component956 (92.74, 72.49)
+Component957 (74.46, 73.66) [Circle]
+Component958 (91.38, 5.63) [Square]
+Component959 (17.30, 2.57) [Triangle]
+Component960 (77.85, 76.45) [Square]
+Component961 (67.51, 95.09)
+Component962 (22.14, 74.92) [Circle]
+Component963 (11.07, 79.09) [Circle]
+Component964 (93.58, 6.56)
+Component965 (30.32, 76.50) [Circle]
+Component966 (88.27, 78.61) [Circle]
+Component967 (99.07, 68.94) [X]
+Component968 (4.65, 2.36) [Square]
+Component969 (58.21, 65.30) [Triangle]
+Component970 (97.18, 30.96) [Square]
+Component971 (14.96, 34.69)
+Component972 (24.39, 36.35)
+Component973 (68.87, 87.50)
+Component974 (8.47, 85.32) [Square]
+Component975 (22.83, 76.01) [Circle]
+Component976 (49.17, 0.04) [Circle]
+Component977 (68.21, 48.73)
+Component978 (89.13, 91.30) [Triangle]
+Component979 (65.46, 59.78) [Triangle]
+Component980 (90.34, 57.29)
+Component981 (96.18, 5.07) [Triangle]
+Component982 (76.27, 45.30) [Circle]
+Component983 (46.02, 62.58) [Circle]
+Component984 (85.41, 20.20) [X]
+Component985 (87.32, 88.29) [X]
+Component986 (99.07, 66.28)
+Component987 (80.59, 74.45)
+Component988 (4.96, 92.28)
+Component989 (29.93, 45.09) [Triangle]
+Component990 (41.97, 25.00)
+Component991 (17.13, 20.49)
+Component992 (95.65, 87.44) [Circle]
+Component993 (98.97, 40.15) [Circle]
+Component994 (18.60, 5.01)
+Component995 (3.77, 53.31) [Triangle]
+Component996 (39.81, 95.10) [X]
+Component997 (0.87, 96.79)
+Component998 (9.14, 29.01) [Triangle]
+Component999 (40.79, 92.24) [Circle]
+
+Component387 -> Component728
+Component563 -- Component366
+Component537 -- Component292
+Component166 -- Component631
+Component300 -> Component112
+Component878 -> Component414
+Component988 -> Component49
+Component174 -- Component89
+Component585 -- Component354
+Component681 -- Component25
+Component994 -- Component479
+Component68 -- Component702
+Component619 -> Component448
+Component899 -> Component43
+Component927 -> Component175
+Component732 -> Component404
+Component751 -- Component519
+Component982 -- Component595
+Component101 -- Component259
+Component933 -- Component208
+Component717 -- Component607
+Component897 -- Component386
+Component66 -> Component18
+Component517 -> Component829
+Component132 -> Component214
+Component228 -- Component446
+Component578 -- Component369
+Component841 -> Component805
+Component787 -- Component789
+Component478 -- Component421
+Component281 -> Component863
+Component218 -- Component232
+Component853 -> Component598
+Component277 -> Component714
+Component815 -- Component66
+Component17 -> Component878
+Component973 -- Component631
+Component855 -> Component787
+Component326 -- Component670
+Component535 -> Component24
+Component347 -> Component624
+Component921 -> Component172
+Component543 -- Component130
+Component879 -- Component456
+Component50 -> Component120
+Component243 -> Component753
+Component549 -> Component971
+Component563 -- Component895
+Component23 -- Component650
+Component772 -- Component980
+Component379 -- Component342
+Component744 -> Component785
+Component746 -- Component138
+Component188 -> Component96
+Component486 -> Component400
+Component40 -> Component475
+Component860 -- Component88
+Component30 -> Component105
+Component479 -> Component790
+Component542 -> Component193
+Component143 -> Component966
+Component183 -> Component260
+Component8 -- Component437
+Component579 -- Component4
+Component120 -- Component488
+Component961 -> Component470
+Component952 -> Component581
+Component60 -- Component682
+Component918 -- Component235
+Component9 -> Component1
+Component503 -- Component452
+Component74 -- Component911
+Component184 -> Component260
+Component370 -- Component927
+Component678 -> Component506
+Component152 -> Component403
+Component877 -- Component649
+Component108 -- Component944
+Component580 -> Component726
+Component691 -> Component384
+Component953 -- Component452
+Component660 -> Component582
+Component695 -> Component152
+Component222 -> Component858
+Component161 -- Component275
+Component604 -- Component112
+Component130 -- Component203
+Component256 -- Component892
+Component120 -- Component946
+Component97 -> Component208
+Component905 -> Component632
+Component918 -- Component195
+Component961 -- Component860
+Component33 -> Component336
+Component613 -- Component222
+Component466 -> Component796
+Component861 -> Component867
+Component145 -> Component425
+Component239 -- Component549
+Component340 -> Component634
+Component752 -> Component53
+Component633 -> Component681
+Component456 -> Component373
+Component71 -> Component343
+Component358 -- Component157
+Component615 -- Component657
+Component570 -- Component433
+Component561 -> Component90
+Component600 -- Component344
+Component254 -> Component746
+Component14 -> Component41
+Component340 -> Component126
+Component957 -> Component684
+Component259 -> Component949
+Component521 -- Component792
+Component90 -- Component929
+Component150 -> Component617
+Component854 -> Component538
+Component485 -> Component426
+Component639 -> Component596
+Component603 -> Component540
+Component815 -> Component365
+Component724 -> Component309
+Component736 -> Component800
+Component448 -> Component715
+Component507 -> Component303
+Component610 -> Component201
+Component712 -- Component599
+Component263 -> Component206
+Component321 -> Component519
+Component861 -> Component0
+Component160 -- Component223
+Component9 -> Component686
+Component866 -- Component200
+Component829 -- Component8
+Component73 -> Component557
+Component350 -> Component93
+Component379 -- Component467
+Component870 -> Component402
+Component385 -> Component675
+Component415 -- Component296
+Component698 -> Component36
+Component708 -> Component117
+Component550 -- Component427
+Component268 -> Component550
+Component573 -- Component124
+Component385 -- Component737
+Component968 -- Component7
+Component850 -> Component992
+Component166 -- Component337
+Component654 -- Component476
+Component768 -- Component834
+Component381 -- Component663
+Component540 -- Component770
+Component231 -> Component234
+Component602 -> Component180
+Component632 -- Component186
+Component568 -> Component583
+Component842 -- Component311
+Component23 -- Component344
+Component914 -- Component732
+Component514 -> Component426
+Component952 -> Component412
+Component192 -> Component734
+Component828 -> Component295
+Component341 -> Component530
+Component650 -- Component169
+Component668 -- Component446
+Component745 -> Component15
+Component995 -> Component321
+Component311 -> Component585
+Component16 -> Component94
+Component41 -> Component459
+Component576 -> Component647
+Component783 -- Component311
+Component842 -> Component857
+Component646 -> Component312
+Component252 -> Component703
+Component519 -- Component884
+Component767 -- Component465
+Component18 -> Component738
+Component743 -> Component828
+Component844 -> Component119
+Component270 -- Component552
+Component372 -- Component539
+Component259 -- Component720
+Component334 -- Component338
+Component357 -- Component551
+Component417 -- Component367
+Component905 -> Component693
+Component272 -> Component469
+Component127 -- Component888
+Component621 -- Component662
+Component424 -- Component512
+Component982 -> Component456
+Component58 -- Component333
+Component28 -> Component200
+Component110 -- Component703
+Component809 -> Component46
+Component414 -> Component881
+Component909 -- Component662
+Component804 -> Component531
+Component307 -> Component133
+Component815 -> Component64
+Component124 -- Component936
+Component413 -- Component299
+Component802 -- Component799
+Component448 -- Component874
+Component225 -- Component18
+Component553 -- Component730
+Component33 -> Component181
+Component751 -> Component567
+Component279 -> Component873
+Component768 -- Component982
+Component313 -> Component955
+Component375 -- Component681
+Component13 -> Component206
+Component772 -> Component981
+Component140 -> Component46
+Component462 -- Component533
+Component244 -- Component341
+Component471 -- Component172
+Component54 -- Component759
+Component923 -> Component449
+Component669 -> Component697
+Component646 -> Component19
+Component802 -> Component306
+Component756 -- Component343
+Component768 -- Component727
+Component906 -- Component19
+Component238 -- Component296
+Component599 -> Component74
+Component969 -- Component186
+Component249 -> Component355
+Component483 -- Component988
+Component669 -> Component226
+Component772 -- Component104
+Component533 -> Component890
+Component788 -- Component259
+Component967 -- Component260
+Component670 -- Component94
+Component291 -> Component139
+Component361 -> Component901
+Component338 -> Component953
+Component944 -- Component600
+Component40 -- Component464
+Component286 -- Component354
+Component599 -- Component912
+Component213 -- Component185
+Component63 -> Component48
+Component271 -- Component811
+Component112 -- Component782
+Component384 -- Component552
+Component351 -> Component162
+Component502 -- Component173
+Component81 -- Component405
+Component866 -- Component777
+Component219 -> Component279
+Component121 -> Component448
+Component437 -> Component890
+Component889 -> Component471
+Component436 -> Component705
+Component938 -- Component670
+Component733 -> Component709
+Component220 -- Component386
+Component57 -> Component652
+Component500 -- Component495
+Component282 -> Component724
+Component786 -> Component103
+Component844 -> Component822
+Component218 -- Component48
+Component662 -- Component544
+Component8 -- Component580
+Component137 -> Component943
+Component924 -> Component822
+Component562 -> Component337
+Component20 -- Component847
+Component60 -- Component310
+Component709 -- Component48
+Component476 -> Component453
+Component315 -- Component896
+Component29 -- Component429
+Component136 -> Component357
+Component782 -> Component890
+Component737 -> Component89
+Component806 -- Component929
+Component28 -> Component659
+Component698 -- Component852
+Component298 -> Component241
+Component64 -> Component863
+Component489 -> Component295
+Component204 -- Component248
+Component165 -> Component554
+Component970 -- Component465
+Component52 -- Component748
+Component900 -> Component759
+Component509 -> Component135
+Component187 -> Component516
+Component787 -- Component587
+Component939 -> Component917
+Component764 -> Component139
+Component64 -- Component79
+Component93 -> Component852
+Component589 -- Component991
+Component410 -- Component660
+Component242 -> Component87
+Component381 -> Component245
+Component413 -- Component262
+Component912 -- Component869
+Component334 -> Component483
+Component629 -> Component369
+Component293 -> Component159
+Component749 -- Component129
+Component244 -- Component394
+Component29 -- Component388
+Component523 -> Component443
+Component265 -- Component973
+Component380 -- Component983
+Component608 -- Component227
+Component119 -> Component472
+Component743 -- Component766
+Component83 -- Component942
+Component110 -- Component696
+Component754 -> Component24
+Component571 -- Component898
+Component290 -> Component615
+Component793 -- Component490
+Component746 -> Component992
+Component871 -- Component756
+Component623 -> Component259
+Component188 -> Component778
+Component348 -- Component392
+Component5 -- Component401
+Component162 -> Component680
+Component233 -- Component276
+Component859 -- Component42
+Component505 -- Component917
+Component328 -> Component892
+Component34 -- Component828
+Component526 -- Component744
+Component130 -- Component970
+Component673 -> Component884
+Component852 -> Component630
+Component102 -- Component41
+Component756 -- Component573
+Component31 -- Component592
+Component558 -> Component400
+Component1 -- Component576
+Component575 -> Component184
+Component19 -- Component732
+Component30 -- Component534
+Component343 -- Component614
+Component562 -> Component480
+Component361 -- Component979
+Component86 -> Component597
+Component192 -- Component600
+Component553 -- Component491
+Component530 -> Component323
+Component995 -> Component720
+Component40 -- Component963
+Component438 -- Component271
+Component941 -> Component86
+Component161 -> Component997
+Component493 -> Component351
+Component907 -- Component307
+Component803 -> Component218
+Component232 -> Component372
+Component74 -> Component63
+Component386 -- Component365
+Component445 -- Component476
+Component633 -- Component463
+Component605 -- Component387
+Component650 -> Component693
+Component338 -> Component843
+Component518 -> Component188
+Component349 -> Component454
+Component889 -> Component874
+Component764 -> Component65
+Component112 -- Component780
+Component253 -- Component757
+Component924 -- Component893
+Component547 -- Component297
+Component372 -> Component852
+Component682 -> Component865
+Component743 -> Component716
+Component31 -> Component618
+Component896 -> Component951
+Component157 -- Component649
+Component22 -- Component966
+Component811 -> Component86
+Component212 -> Component80
+Component473 -> Component236
+Component979 -> Component43
+Component668 -- Component995
+Component412 -> Component594
+Component516 -- Component472
+Component500 -> Component554
+Component148 -- Component185
+Component769 -- Component438
+Component512 -- Component570
+Component249 -> Component990
+Component185 -> Component233
+Component94 -- Component320
+Component858 -- Component197
+Component268 -- Component6
+Component529 -- Component806
+Component184 -- Component779
+Component235 -- Component894
+Component215 -> Component990
+Component7 -- Component427
+Component583 -> Component297
+Component504 -- Component148
+Component92 -- Component223
+Component693 -> Component151
+Component858 -- Component241
+Component523 -- Component919
+Component938 -> Component926
+Component550 -- Component761
+Component25 -- Component532
+Component789 -- Component151
+Component497 -- Component190
+Component443 -- Component319
+Component82 -> Component780
+Component523 -> Component205
+Component176 -- Component456
+Component600 -> Component231
+Component609 -- Component908
+Component374 -> Component895
+Component919 -> Component605
+Component74 -- Component953
+Component70 -> Component75
+Component757 -- Component405
+Component756 -- Component342
+Component847 -- Component252
+Component596 -> Component131
+Component407 -- Component957
+Component493 -> Component691
+Component618 -- Component506
+Component788 -- Component107
+Component700 -- Component258
+Component419 -> Component896
+Component936 -> Component433
+Component221 -> Component206
+Component167 -> Component57
+Component86 -> Component1
+Component825 -> Component326
+Component643 -- Component121
+Component912 -- Component132
+Component307 -- Component636
+Component218 -- Component84
+Component192 -> Component656
+Component346 -- Component347
+Component45 -- Component260
+Component962 -- Component33
+Component693 -> Component197
+Component308 -- Component622
+Component23 -> Component24
+Component625 -- Component402
+Component437 -> Component114
+Component186 -- Component586
+Component93 -- Component96
+Component830 -- Component485
+Component177 -> Component323
+Component727 -> Component725
+Component519 -> Component669
+Component213 -- Component611
+Component500 -- Component763
+Component638 -> Component337
+Component5 -> Component622
+Component814 -> Component914
+Component699 -> Component337
+Component472 -> Component262
+Component105 -> Component108
+Component519 -> Component575
+Component122 -> Component641
+Component27 -> Component683
+Component30 -> Component368
+Component947 -> Component361
+Component473 -> Component551
+Component195 -- Component865
+Component758 -> Component781
+Component988 -- Component322
+Component292 -- Component562
+Component94 -> Component179
+Component769 -- Component428
+Component813 -- Component177
+Component957 -> Component952
+Component361 -> Component268
+Component982 -- Component600
+Component771 -- Component657
+Component2 -> Component345
+Component90 -- Component447
+Component583 -> Component289
+Component478 -- Component980
+Component712 -- Component343
+Component203 -- Component449
+Component871 -> Component245
+Component446 -> Component557
+Component371 -- Component359
+Component664 -- Component272
+Component130 -> Component795
+Component383 -> Component371
+Component743 -> Component357
+Component949 -- Component591
+Component224 -> Component809
+Component401 -- Component928
+Component697 -- Component52
+Component523 -> Component549
+Component73 -> Component447
+Component39 -- Component682
+Component406 -- Component180
+Component653 -- Component605
+Component287 -> Component464
+Component600 -- Component794
+Component625 -> Component881
+Component402 -> Component53
+Component991 -- Component287
+Component267 -- Component157
+Component935 -- Component182
+Component368 -> Component689
+Component446 -> Component623
+Component95 -> Component969
+Component853 -> Component313
+Component227 -> Component415
+Component70 -- Component535
+Component877 -- Component64
+Component394 -> Component905
+Component811 -> Component339
+Component828 -- Component15
+Component4 -> Component752
+Component453 -> Component49
+Component604 -> Component990
+Component295 -- Component723
+Component562 -- Component691
+Component45 -- Component472
+Component284 -- Component226
+Component488 -- Component236
+Component542 -- Component143
+Component199 -> Component149
+Component629 -> Component805
+Component251 -- Component13
+Component44 -- Component301
+Component949 -> Component961
+Component187 -- Component713
+Component295 -- Component477
+Component284 -- Component505
+Component430 -- Component61
+Component504 -- Component579
+Component928 -- Component585
+Component864 -- Component173
+Component446 -> Component141
+Component471 -- Component749
+Component252 -> Component631
+Component520 -> Component375
+Component711 -> Component337
+Component360 -> Component985
+Component425 -- Component916
+Component288 -> Component466
+Component789 -> Component740
+Component235 -- Component578
+Component126 -- Component959
+Component800 -- Component367
+Component926 -> Component626
+Component19 -- Component799
+Component711 -> Component119
+Component31 -- Component805
+Component962 -> Component268
+Component254 -- Component192
+Component129 -> Component374
+Component173 -> Component244
+Component699 -- Component508
+Component767 -> Component372
+Component491 -> Component357
+Component301 -> Component935
+Component648 -- Component899
+Component748 -> Component97
+Component140 -> Component901
+Component503 -> Component68
+Component28 -- Component646
+Component38 -- Component713
+Component109 -- Component540
+Component186 -> Component519
+Component478 -- Component507
+Component885 -- Component794
+Component929 -- Component165
+Component207 -- Component615
+Component198 -- Component434
+Component919 -- Component880
+Component668 -> Component164
+Component1 -- Component792
+Component17 -- Component22
+Component215 -- Component796
+Component118 -> Component670
+Component480 -> Component506
+Component403 -- Component709
+Component864 -- Component180
+Component316 -> Component167
+Component652 -- Component916
+Component86 -- Component250
+Component664 -> Component26
+
+[Note] (2.94, 40.45) Note 0: Important information here
+[Note] (52.51, 36.04) Note 1: Important information here
+[Note] (58.98, 66.79) Note 2: Important information here
+[Note] (78.33, 13.50) Note 3: Important information here
+[Note] (79.45, 45.42) Note 4: Important information here
+[Note] (73.48, 17.13) Note 5: Important information here
+[Note] (50.74, 42.61) Note 6: Important information here
+[Note] (78.16, 67.95) Note 7: Important information here
+[Note] (39.81, 97.76) Note 8: Important information here
+[Note] (20.44, 8.41) Note 9: Important information here
+[Note] (57.71, 68.13) Note 10: Important information here
+[Note] (4.50, 43.31) Note 11: Important information here
+[Note] (81.32, 0.57) Note 12: Important information here
+[Note] (28.05, 35.60) Note 13: Important information here
+[Note] (32.09, 5.25) Note 14: Important information here
+[Note] (15.56, 33.99) Note 15: Important information here
+[Note] (6.30, 36.55) Note 16: Important information here
+[Note] (26.69, 87.57) Note 17: Important information here
+[Note] (84.58, 54.27) Note 18: Important information here
+[Note] (51.43, 97.01) Note 19: Important information here
+[Note] (52.32, 80.60) Note 20: Important information here
+[Note] (25.39, 94.75) Note 21: Important information here
+[Note] (14.65, 36.06) Note 22: Important information here
+[Note] (33.20, 19.14) Note 23: Important information here
+[Note] (61.89, 14.32) Note 24: Important information here
+[Note] (19.45, 14.75) Note 25: Important information here
+[Note] (72.54, 34.44) Note 26: Important information here
+[Note] (30.25, 60.76) Note 27: Important information here
+[Note] (97.08, 95.72) Note 28: Important information here
+[Note] (36.72, 82.06) Note 29: Important information here
+[Note] (66.76, 96.51) Note 30: Important information here
+[Note] (44.97, 98.71) Note 31: Important information here
+[Note] (33.53, 71.85) Note 32: Important information here
+[Note] (1.12, 36.27) Note 33: Important information here
+[Note] (93.86, 41.54) Note 34: Important information here
+[Note] (45.49, 97.58) Note 35: Important information here
+[Note] (11.85, 27.02) Note 36: Important information here
+[Note] (22.05, 41.78) Note 37: Important information here
+[Note] (24.21, 47.46) Note 38: Important information here
+[Note] (35.59, 77.67) Note 39: Important information here
+[Note] (60.38, 33.28) Note 40: Important information here
+[Note] (11.99, 57.45) Note 41: Important information here
+[Note] (36.12, 68.56) Note 42: Important information here
+[Note] (12.09, 2.53) Note 43: Important information here
+[Note] (76.81, 69.61) Note 44: Important information here
+[Note] (90.95, 51.34) Note 45: Important information here
+[Note] (62.74, 25.63) Note 46: Important information here
+[Note] (23.00, 12.92) Note 47: Important information here
+[Note] (89.56, 11.80) Note 48: Important information here
+[Note] (91.20, 54.14) Note 49: Important information here
+[Note] (9.82, 29.43) Note 50: Important information here
+[Note] (39.10, 54.60) Note 51: Important information here
+[Note] (18.37, 26.61) Note 52: Important information here
+[Note] (73.22, 5.28) Note 53: Important information here
+[Note] (48.00, 29.66) Note 54: Important information here
+[Note] (29.62, 96.82) Note 55: Important information here
+[Note] (43.04, 99.15) Note 56: Important information here
+[Note] (35.35, 59.90) Note 57: Important information here
+[Note] (14.89, 41.05) Note 58: Important information here
+[Note] (63.43, 25.91) Note 59: Important information here
+[Note] (8.89, 85.37) Note 60: Important information here
+[Note] (40.78, 28.82) Note 61: Important information here
+[Note] (45.40, 95.24) Note 62: Important information here
+[Note] (38.42, 69.68) Note 63: Important information here
+[Note] (98.58, 97.72) Note 64: Important information here
+[Note] (55.42, 94.26) Note 65: Important information here
+[Note] (79.78, 9.46) Note 66: Important information here
+[Note] (74.35, 80.17) Note 67: Important information here
+[Note] (51.98, 69.94) Note 68: Important information here
+[Note] (10.33, 99.47) Note 69: Important information here
+[Note] (49.91, 26.62) Note 70: Important information here
+[Note] (99.67, 89.15) Note 71: Important information here
+[Note] (83.91, 73.43) Note 72: Important information here
+[Note] (96.77, 80.50) Note 73: Important information here
+[Note] (79.78, 29.10) Note 74: Important information here
+[Note] (73.99, 22.14) Note 75: Important information here
+[Note] (0.40, 71.89) Note 76: Important information here
+[Note] (96.44, 0.13) Note 77: Important information here
+[Note] (37.22, 36.88) Note 78: Important information here
+[Note] (49.86, 14.12) Note 79: Important information here
+[Note] (54.71, 0.77) Note 80: Important information here
+[Note] (91.08, 10.71) Note 81: Important information here
+[Note] (82.91, 82.87) Note 82: Important information here
+[Note] (16.74, 3.24) Note 83: Important information here
+[Note] (72.61, 55.76) Note 84: Important information here
+[Note] (11.39, 18.81) Note 85: Important information here
+[Note] (95.79, 22.32) Note 86: Important information here
+[Note] (2.01, 62.41) Note 87: Important information here
+[Note] (13.64, 62.81) Note 88: Important information here
+[Note] (57.72, 29.73) Note 89: Important information here
+[Note] (75.37, 73.59) Note 90: Important information here
+[Note] (61.03, 3.80) Note 91: Important information here
+[Note] (48.59, 37.15) Note 92: Important information here
+[Note] (22.25, 75.74) Note 93: Important information here
+[Note] (93.34, 15.31) Note 94: Important information here
+[Note] (29.08, 62.29) Note 95: Important information here
+[Note] (90.50, 87.93) Note 96: Important information here
+[Note] (16.57, 55.43) Note 97: Important information here
+[Note] (94.92, 72.00) Note 98: Important information here
+[Note] (76.78, 69.76) Note 99: Important information here
+
+[Group] Component166, Component358, Component162, Component735, Component888, Component887
+[Group] Component813, Component533, Component171, Component890, Component903, Component427, Component946, Component701, Component485
+[Group] Component361, Component442, Component616, Component723, Component141
+[Group] Component928, Component96, Component157, Component210, Component556
+[Group] Component640, Component776, Component24, Component77, Component122, Component770, Component749, Component138, Component885
+[Group] Component321, Component517, Component33, Component560, Component553, Component851, Component109, Component798, Component164
+[Group] Component750, Component439, Component500, Component242, Component758
+[Group] Component631, Component186, Component77, Component263, Component342, Component988, Component55, Component868
+[Group] Component700, Component735, Component71, Component491, Component265, Component584, Component703, Component858
+[Group] Component34, Component461, Component400, Component364, Component53
+[Group] Component328, Component186, Component561, Component769, Component403, Component477, Component319, Component319, Component512
+[Group] Component773, Component847, Component838, Component153, Component187, Component545, Component450, Component125, Component610, Component328
+[Group] Component7, Component340, Component456, Component183, Component990, Component303, Component767
+[Group] Component777, Component607, Component466, Component751, Component848, Component803, Component994, Component561
+[Group] Component773, Component278, Component623, Component201, Component281
+[Group] Component357, Component963, Component582, Component828, Component733, Component258, Component904, Component261, Component277, Component949
+[Group] Component906, Component471, Component230, Component135, Component370, Component930, Component74, Component177, Component389
+[Group] Component836, Component154, Component590, Component2, Component459, Component732, Component512, Component759, Component760, Component840
+[Group] Component625, Component499, Component9, Component58, Component259, Component602, Component488, Component176, Component221, Component781
+[Group] Component202, Component212, Component576, Component111, Component612, Component856, Component916
+[Group] Component517, Component296, Component112, Component53, Component760, Component856, Component209, Component31, Component638
+[Group] Component237, Component717, Component314, Component724, Component896, Component946, Component228, Component563, Component994, Component18
+[Group] Component102, Component958, Component317, Component552, Component821, Component802, Component725, Component856, Component878, Component922
+[Group] Component638, Component280, Component163, Component527, Component205, Component74, Component753, Component170, Component172, Component610
+[Group] Component885, Component857, Component835, Component834, Component599, Component965
+[Group] Component355, Component341, Component305, Component329, Component705, Component665
+[Group] Component933, Component454, Component769, Component783, Component454, Component118, Component616
+[Group] Component659, Component141, Component574, Component505, Component259, Component538, Component250, Component383, Component374, Component948
+[Group] Component22, Component487, Component234, Component640, Component450, Component55
+[Group] Component110, Component166, Component875, Component745, Component109
+[Group] Component536, Component346, Component210, Component131, Component732, Component786, Component506, Component834, Component773, Component285
+[Group] Component562, Component401, Component560, Component186, Component710, Component41, Component519, Component902, Component489, Component304
+[Group] Component895, Component503, Component939, Component501, Component255
+[Group] Component344, Component39, Component167, Component74, Component680, Component962, Component789, Component528, Component199
+[Group] Component24, Component917, Component390, Component67, Component184, Component502, Component131
+[Group] Component146, Component868, Component949, Component619, Component382
+[Group] Component263, Component544, Component629, Component77, Component983, Component303
+[Group] Component873, Component161, Component398, Component672, Component847, Component834, Component364
+[Group] Component78, Component511, Component14, Component998, Component276
+[Group] Component161, Component681, Component53, Component139, Component606
+[Group] Component651, Component241, Component157, Component273, Component316, Component233, Component725, Component317
+[Group] Component347, Component471, Component365, Component748, Component266, Component849, Component868, Component478, Component320
+[Group] Component702, Component625, Component547, Component230, Component791, Component492, Component484
+[Group] Component294, Component37, Component146, Component889, Component343, Component761, Component100
+[Group] Component62, Component902, Component833, Component184, Component124, Component322, Component346, Component874, Component785
+[Group] Component838, Component501, Component979, Component48, Component404, Component24, Component644, Component759, Component138, Component470
+[Group] Component764, Component133, Component294, Component987, Component134, Component160, Component60
+[Group] Component916, Component310, Component269, Component134, Component967, Component418, Component946, Component333
+[Group] Component983, Component468, Component392, Component878, Component42, Component643, Component428, Component967, Component559, Component133
+[Group] Component928, Component925, Component32, Component478, Component979, Component488, Component674
+
+[Inertia] Component269
+[Inertia] Component778
+[Inertia] Component942
+[Inertia] Component159
+[Inertia] Component579
+[Inertia] Component156
+[Inertia] Component864
+[Inertia] Component25
+[Inertia] Component940
+[Inertia] Component571
+[Inertia] Component124
+[Inertia] Component206
+[Inertia] Component353
+[Inertia] Component695
+[Inertia] Component549
+[Inertia] Component118
+[Inertia] Component914
+[Inertia] Component67
+[Inertia] Component73
+[Inertia] Component688
+[Inertia] Component413
+[Inertia] Component828
+[Inertia] Component720
+[Inertia] Component31
+[Inertia] Component203
+[Inertia] Component835
+[Inertia] Component73
+[Inertia] Component770
+[Inertia] Component340
+[Inertia] Component608
+[Inertia] Component82
+[Inertia] Component349
+[Inertia] Component972
+[Inertia] Component63
+[Inertia] Component30
+[Inertia] Component392
+[Inertia] Component455
+[Inertia] Component910
+[Inertia] Component76
+[Inertia] Component843
+[Inertia] Component120
+[Inertia] Component831
+[Inertia] Component555
+[Inertia] Component531
+[Inertia] Component850
+[Inertia] Component991
+[Inertia] Component789
+[Inertia] Component834
+[Inertia] Component341
+[Inertia] Component611
+[Inertia] Component990
+[Inertia] Component599
+[Inertia] Component872
+[Inertia] Component44
+[Inertia] Component574
+[Inertia] Component727
+[Inertia] Component525
+[Inertia] Component73
+[Inertia] Component539
+[Inertia] Component596
+[Inertia] Component458
+[Inertia] Component982
+[Inertia] Component1
+[Inertia] Component604
+[Inertia] Component573
+[Inertia] Component208
+[Inertia] Component317
+[Inertia] Component815
+[Inertia] Component81
+[Inertia] Component703
+[Inertia] Component949
+[Inertia] Component634
+[Inertia] Component651
+[Inertia] Component687
+[Inertia] Component430
+[Inertia] Component407
+[Inertia] Component802
+[Inertia] Component716
+[Inertia] Component348
+[Inertia] Component769
+[Inertia] Component684
+[Inertia] Component260
+[Inertia] Component52
+[Inertia] Component707
+[Inertia] Component99
+[Inertia] Component100
+[Inertia] Component776
+[Inertia] Component104
+[Inertia] Component489
+[Inertia] Component702
+[Inertia] Component923
+[Inertia] Component927
+[Inertia] Component409
+[Inertia] Component149
+[Inertia] Component816
+[Inertia] Component170
+[Inertia] Component953
+[Inertia] Component816
+[Inertia] Component83
+[Inertia] Component65
+
+[Evolution] Component902 + 9.56
+[Evolution] Component565 + 6.91
+[Evolution] Component691 + 46.82
+[Evolution] Component502 - 45.86
+[Evolution] Component298 - 11.79
+[Evolution] Component902 - 29.01
+[Evolution] Component313 + 20.43
+[Evolution] Component452 - 21.03
+[Evolution] Component489 + 42.37
+[Evolution] Component59 + 26.46
+[Evolution] Component124 - 48.39
+[Evolution] Component920 - 47.99
+[Evolution] Component835 - 44.60
+[Evolution] Component408 + 9.26
+[Evolution] Component381 + 7.89
+[Evolution] Component822 + 37.66
+[Evolution] Component244 + 3.26
+[Evolution] Component294 + 38.15
+[Evolution] Component985 - 14.38
+[Evolution] Component810 + 3.40
+[Evolution] Component0 - 19.05
+[Evolution] Component976 + 6.90
+[Evolution] Component516 + 30.13
+[Evolution] Component193 + 10.17
+[Evolution] Component653 - 28.96
+[Evolution] Component29 + 48.14
+[Evolution] Component810 + 6.28
+[Evolution] Component617 + 2.99
+[Evolution] Component228 + 35.90
+[Evolution] Component164 + 33.70
+[Evolution] Component297 - 42.43
+[Evolution] Component42 + 47.32
+[Evolution] Component752 + 21.97
+[Evolution] Component930 - 38.23
+[Evolution] Component305 - 49.76
+[Evolution] Component748 + 41.84
+[Evolution] Component2 - 41.73
+[Evolution] Component524 + 6.93
+[Evolution] Component350 + 36.75
+[Evolution] Component129 + 24.85
+[Evolution] Component934 + 42.24
+[Evolution] Component568 - 45.29
+[Evolution] Component18 + 27.95
+[Evolution] Component125 + 42.13
+[Evolution] Component843 - 25.92
+[Evolution] Component502 - 7.53
+[Evolution] Component845 + 41.54
+[Evolution] Component269 + 47.41
+[Evolution] Component440 + 23.12
+[Evolution] Component370 - 14.15
+[Evolution] Component885 + 27.46
+[Evolution] Component512 - 18.65
+[Evolution] Component313 - 41.73
+[Evolution] Component173 + 21.42
+[Evolution] Component182 + 32.77
+[Evolution] Component681 + 0.34
+[Evolution] Component71 - 0.81
+[Evolution] Component256 + 30.35
+[Evolution] Component380 - 30.76
+[Evolution] Component790 - 4.48
+[Evolution] Component329 - 8.79
+[Evolution] Component456 - 22.37
+[Evolution] Component829 - 40.43
+[Evolution] Component586 + 20.49
+[Evolution] Component918 - 13.15
+[Evolution] Component638 + 22.34
+[Evolution] Component41 + 31.82
+[Evolution] Component958 - 26.38
+[Evolution] Component43 - 19.16
+[Evolution] Component287 + 19.71
+[Evolution] Component33 - 17.17
+[Evolution] Component826 - 44.03
+[Evolution] Component290 + 24.18
+[Evolution] Component17 - 4.30
+[Evolution] Component550 + 3.36
+[Evolution] Component262 - 29.09
+[Evolution] Component472 + 10.38
+[Evolution] Component792 - 2.78
+[Evolution] Component79 + 20.09
+[Evolution] Component695 + 36.78
+[Evolution] Component413 + 7.77
+[Evolution] Component937 + 41.23
+[Evolution] Component749 + 39.04
+[Evolution] Component970 + 37.45
+[Evolution] Component748 - 10.69
+[Evolution] Component16 - 19.91
+[Evolution] Component10 + 2.90
+[Evolution] Component337 + 21.52
+[Evolution] Component190 + 13.97
+[Evolution] Component773 + 25.37
+[Evolution] Component16 - 41.09
+[Evolution] Component387 + 46.74
+[Evolution] Component355 - 46.48
+[Evolution] Component511 - 26.32
+[Evolution] Component4 + 36.69
+[Evolution] Component811 + 30.11
+[Evolution] Component885 - 28.46
+[Evolution] Component854 - 41.72
+[Evolution] Component493 + 40.34
+[Evolution] Component541 - 6.05 \ No newline at end of file
diff --git a/benches/rendering.rs b/benches/rendering.rs
new file mode 100644
index 0000000..549ad21
--- /dev/null
+++ b/benches/rendering.rs
@@ -0,0 +1,25 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use std::fs;
+use wmap_renderer::{render_to_png, Configuration, StageType};
+
+fn benchmark_rendering(c: &mut Criterion) {
+ // Read the huge.wmap file for realistic benchmarking
+ let source = fs::read_to_string("benches/huge.wmap").expect("Failed to read benches/huge.wmap");
+ let map = wmap_parser::parse(&source);
+ let config = Configuration::default();
+
+ c.bench_function("render huge map", |b| {
+ b.iter(|| render_to_png(black_box(&map), StageType::default(), &config))
+ });
+
+ // Also benchmark a simple map
+ let simple_source = "Tea (90, 50)\nCup (80, 40)\nTea -> Cup";
+ let simple_map = wmap_parser::parse(simple_source);
+
+ c.bench_function("render simple map", |b| {
+ b.iter(|| render_to_png(black_box(&simple_map), StageType::default(), &config))
+ });
+}
+
+criterion_group!(benches, benchmark_rendering);
+criterion_main!(benches);
diff --git a/examples/example.wmap b/examples/example.wmap
new file mode 100644
index 0000000..a238ab1
--- /dev/null
+++ b/examples/example.wmap
@@ -0,0 +1,122 @@
+Anchor (40, 0)
+Tinker (4,30) [x]
+Tailor (7.03, 49.21) [Square]
+Soldier (28, 15) [Triangle]
+AMnchor (28.22, 45.27)
+Tonker (67.3, 4.4) [X]
+Tailor1 (1,1) [Square]
+Tailor2 (10,1) [Square]
+Tailor3 (1,10) [Square]
+Tailor4 (20,1) [Square]
+Tailor5 (1,20) [Square]
+Tailor7 (20,10) [Square]
+Tailor8 (9.36, 16.97) [Square]
+Tailor777(30,10) [Square]
+Tailor10 (18.77, 26.25) [Square]
+Tailor11 (10,10) [Square]
+Tailor12 (30,20)
+Tailor13 (31.10, 28.89)
+Tailor14 (13.44, 21.48) [Square]
+Tailor19 (45, 2) [triangle]
+B (50, 50)
+Hellele
+
+TailorX (20, 50) [Square]
+
+Hello World (90, 20) [x]
+
+Anchor -> Soldier… this is changing.
+Anchor -> A
+Masdlajksd -- Tailor13
+
+Mabanchorn -- Anchornalsdjalks
+
+A
+--B
+
+X (58.52, 27.07)
+
+Tailor11--Tailor12
+Tailor12--Tailor13
+Tailor777--Tailor8
+Tailor777--Tailor7
+Tailor777--Tailor6
+Tailor7-Tailor6
+
+Tinker -- Tailor
+Soldier -- Tinker
+
+[Evolution] Tinker +20
+[Inertia] Soldier
+
+[i] 15
+[ii] 35
+[iii] 80
+
+this is kind of ok … a little bit better to be honest. (69.76, 89.84)
+
+OK.
+
+This map so far is fst. asdasd asda
+
+Holy shit, this is still indeed fast. But everything flashes as I type! This is now happening so fast
+
+Hello my old friend.
+
+OK so this map should eventually start getting slow too!
+
+let’s see how long after.asd asda sda
+
+asda sdasdasd asdasdasdasdasdasd
+OK… Well we are typing. asd as asd as dasd a asdasda sdasd asd
+
+ asd asd asd asdasd asdasd
+Alright, the whole text is still fa asdlashing. Wow. This is pretty bad.
+aas dasd adsa sd a asd asd asdjlkasjd laksdjasd basldjas!!!
+
+we have deleted this (20, 40) [triangle]
+
+a(12, 12)
+Time to see if we get the same performance loss as withthis the other one. Well I will continue. Hello this is me. and i’m
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+b(75,75)
+c(9, 90)
+
+a -- b
+b -- c
+
+d (45, 88)
+
+c -- d
+d -- a
+d -- b
+
+q (90, 60)
+b -- q
+q -- a
+q -- c
+
+[Evolution] c +20
+[Evolution] d +10
+[Evolution] a -20
+[Evolution] q +5
+
+m(50, 60)
+n (65, 65)
+o (45, 63)
+
+m -- o
+o -- n
+n -- m
+o -- c
+n -- q
+m -- d
+
+testasd asdas
+test2asdasa asdasd asd
+
+[Note] (5, 35) Notes also influence the placement of\n labels.
+
+[Group] Tailor2, Tailor7, Tailor11, Tailor12, Tailor10, Anchor, Tinker
+[Group] Tailor3, Tailor5, Tailor14pr \ No newline at end of file
diff --git a/examples/generate_map.rs b/examples/generate_map.rs
new file mode 100644
index 0000000..8b9cf80
--- /dev/null
+++ b/examples/generate_map.rs
@@ -0,0 +1,38 @@
+use std::fs;
+use wmap_renderer::{render_to_png, render_to_svg, Configuration, StageType};
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ // Read the example.wmap file
+ let map_source = fs::read_to_string("examples/example.wmap")?;
+ let map = wmap_parser::parse(&map_source);
+ let config = Configuration::default();
+
+ // Generate PNG with smart positioning
+ println!("Generating PNG with smart positioning...");
+ let png_data = render_to_png(&map, StageType::Activities, &config)?;
+ fs::write("example.png", png_data)?;
+ println!("Generated example.png");
+
+ // Generate SVG with smart positioning
+ println!("Generating SVG with smart positioning...");
+ let svg_data = render_to_svg(&map, StageType::Activities, &config)?;
+ fs::write("example.svg", svg_data)?;
+ println!("Generated example.svg");
+
+ // Generate with different stage type
+ println!("Generating with publication types stage type...");
+ let png_publication_types = render_to_png(&map, StageType::PublicationTypes, &config)?;
+ fs::write("example_publication_types.png", png_publication_types)?;
+ println!("Generated example_publication_types.png");
+
+ // Generate without smart positioning (simple right-side positioning)
+ println!("Generating without smart positioning...");
+ let mut config_no_smart = Configuration::default();
+ config_no_smart.options.smart_label_positioning = false;
+ let png_no_smart = render_to_png(&map, StageType::Activities, &config_no_smart)?;
+ fs::write("example_no_smart_positioning.png", png_no_smart)?;
+ println!("Generated example_no_smart_positioning.png");
+
+ println!("\nAll examples generated successfully!");
+ Ok(())
+}
diff --git a/examples/huge.wmap b/examples/huge.wmap
new file mode 100644
index 0000000..c58f8c3
--- /dev/null
+++ b/examples/huge.wmap
@@ -0,0 +1,1960 @@
+[I] 0.25
+[II] 0.5
+[III] 0.75
+[IV] 1.0
+
+Component0 (9.95, 32.86) [X]
+Component1 (61.12, 59.35)
+Component2 (27.46, 61.14)
+Component3 (22.90, 50.28) [Circle]
+Component4 (8.84, 45.75) [Square]
+Component5 (34.22, 13.13) [Circle]
+Component6 (53.14, 32.02) [X]
+Component7 (88.66, 94.82)
+Component8 (26.58, 14.30) [Square]
+Component9 (87.07, 57.55) [X]
+Component10 (90.63, 12.80)
+Component11 (91.14, 18.89) [X]
+Component12 (9.25, 15.82) [Square]
+Component13 (23.84, 91.04) [Circle]
+Component14 (53.42, 50.15)
+Component15 (23.20, 1.09)
+Component16 (74.50, 14.99)
+Component17 (23.36, 83.91) [Triangle]
+Component18 (55.63, 41.97) [Triangle]
+Component19 (77.86, 4.08) [X]
+Component20 (16.02, 71.88) [X]
+Component21 (52.76, 85.80)
+Component22 (88.56, 76.18) [Square]
+Component23 (87.30, 28.67)
+Component24 (63.18, 77.45)
+Component25 (64.67, 24.42)
+Component26 (77.91, 68.51) [Square]
+Component27 (12.78, 49.59)
+Component28 (44.77, 15.02) [Circle]
+Component29 (58.72, 28.35) [Circle]
+Component30 (24.99, 58.90) [X]
+Component31 (25.36, 96.80)
+Component32 (39.32, 99.80) [Circle]
+Component33 (99.43, 39.47) [Circle]
+Component34 (81.44, 13.95)
+Component35 (16.49, 68.99)
+Component36 (7.24, 32.21)
+Component37 (3.68, 64.60) [X]
+Component38 (86.45, 26.42) [Circle]
+Component39 (76.81, 52.14)
+Component40 (58.24, 33.89)
+Component41 (69.90, 96.05)
+Component42 (32.45, 5.36) [Triangle]
+Component43 (64.31, 59.01) [Triangle]
+Component44 (85.51, 61.89)
+Component45 (74.47, 3.03)
+Component46 (59.49, 69.43) [Circle]
+Component47 (61.68, 28.42) [Square]
+Component48 (44.39, 8.48) [X]
+Component49 (72.53, 49.68)
+Component50 (36.04, 88.93) [Triangle]
+Component51 (5.46, 8.03)
+Component52 (70.84, 53.51) [X]
+Component53 (66.45, 12.27)
+Component54 (30.46, 81.10)
+Component55 (44.12, 3.73) [X]
+Component56 (38.53, 69.63) [Triangle]
+Component57 (3.78, 33.06) [Triangle]
+Component58 (6.34, 59.23)
+Component59 (44.77, 51.09) [Triangle]
+Component60 (34.70, 66.24) [Triangle]
+Component61 (68.32, 34.01)
+Component62 (2.66, 12.55)
+Component63 (48.15, 67.40) [X]
+Component64 (22.94, 92.05)
+Component65 (64.35, 62.72) [X]
+Component66 (62.49, 91.18) [Triangle]
+Component67 (37.77, 62.48) [Square]
+Component68 (61.24, 64.54)
+Component69 (8.86, 51.61) [Triangle]
+Component70 (4.27, 58.35) [Circle]
+Component71 (24.95, 89.36) [X]
+Component72 (63.65, 53.32) [Square]
+Component73 (71.29, 60.36) [Circle]
+Component74 (80.72, 87.52)
+Component75 (54.05, 36.18)
+Component76 (72.89, 59.69)
+Component77 (52.20, 58.81) [Circle]
+Component78 (55.08, 77.07) [Triangle]
+Component79 (55.84, 53.48)
+Component80 (29.05, 95.03)
+Component81 (49.75, 3.88)
+Component82 (45.87, 5.24)
+Component83 (52.70, 54.81)
+Component84 (77.16, 2.60) [Triangle]
+Component85 (57.90, 6.89) [Square]
+Component86 (31.42, 51.43) [Square]
+Component87 (84.99, 31.15)
+Component88 (97.75, 43.72) [Triangle]
+Component89 (62.25, 37.18) [X]
+Component90 (47.25, 5.86) [X]
+Component91 (44.99, 84.43) [Triangle]
+Component92 (83.02, 78.80) [X]
+Component93 (86.53, 22.19) [X]
+Component94 (29.50, 81.88) [Circle]
+Component95 (83.80, 12.03) [Circle]
+Component96 (6.33, 24.74)
+Component97 (96.48, 25.86)
+Component98 (35.34, 79.18)
+Component99 (58.59, 46.39) [Square]
+Component100 (87.06, 39.03) [Circle]
+Component101 (87.26, 56.31)
+Component102 (12.81, 16.15)
+Component103 (68.88, 6.87) [Square]
+Component104 (81.35, 46.02)
+Component105 (78.60, 19.95)
+Component106 (11.24, 77.61) [Circle]
+Component107 (19.09, 51.26)
+Component108 (76.93, 23.58)
+Component109 (1.85, 22.92)
+Component110 (18.90, 65.14) [Square]
+Component111 (24.65, 75.25)
+Component112 (1.87, 54.60)
+Component113 (88.58, 63.71)
+Component114 (97.17, 48.17) [Square]
+Component115 (51.49, 11.93) [Square]
+Component116 (17.79, 63.02) [X]
+Component117 (89.77, 18.04)
+Component118 (18.87, 79.15)
+Component119 (8.76, 76.67)
+Component120 (81.32, 63.54)
+Component121 (92.71, 98.97)
+Component122 (16.09, 28.54) [X]
+Component123 (48.43, 97.65)
+Component124 (50.37, 37.20)
+Component125 (50.74, 43.05) [Square]
+Component126 (75.40, 23.34)
+Component127 (73.92, 79.49) [Circle]
+Component128 (51.55, 20.51) [Circle]
+Component129 (70.45, 13.55) [Square]
+Component130 (57.22, 85.64) [X]
+Component131 (64.87, 52.42)
+Component132 (33.35, 93.91) [Square]
+Component133 (73.38, 47.59)
+Component134 (65.04, 86.19)
+Component135 (68.33, 7.91) [Triangle]
+Component136 (64.96, 64.20) [Circle]
+Component137 (12.52, 38.35)
+Component138 (10.65, 57.44) [Circle]
+Component139 (14.10, 12.62)
+Component140 (52.85, 26.35) [Triangle]
+Component141 (99.33, 72.83) [X]
+Component142 (58.66, 65.70) [X]
+Component143 (42.10, 3.23) [Triangle]
+Component144 (36.20, 47.76) [Circle]
+Component145 (75.65, 26.66) [Square]
+Component146 (21.33, 14.84)
+Component147 (21.88, 24.88) [Triangle]
+Component148 (55.89, 55.79) [Triangle]
+Component149 (10.73, 30.74) [Triangle]
+Component150 (13.37, 91.16)
+Component151 (83.17, 21.74)
+Component152 (95.03, 90.75)
+Component153 (84.26, 35.34) [X]
+Component154 (94.45, 87.23) [Triangle]
+Component155 (36.59, 75.57) [Circle]
+Component156 (42.65, 46.60) [Square]
+Component157 (6.47, 56.77) [Square]
+Component158 (44.90, 59.64) [Square]
+Component159 (76.18, 37.02) [Circle]
+Component160 (95.09, 86.50)
+Component161 (55.14, 41.38) [Triangle]
+Component162 (60.83, 35.01) [Circle]
+Component163 (10.27, 67.00) [Square]
+Component164 (76.09, 65.99) [Square]
+Component165 (49.23, 79.02) [Circle]
+Component166 (73.02, 29.83)
+Component167 (54.87, 64.16) [X]
+Component168 (52.68, 30.53) [Circle]
+Component169 (29.26, 87.91) [Triangle]
+Component170 (76.81, 44.95) [Triangle]
+Component171 (78.63, 55.50) [Circle]
+Component172 (72.66, 55.50) [X]
+Component173 (27.73, 64.42) [Triangle]
+Component174 (70.81, 23.22)
+Component175 (87.42, 5.68)
+Component176 (98.01, 65.20) [Triangle]
+Component177 (94.00, 8.83) [Circle]
+Component178 (80.21, 42.24)
+Component179 (10.48, 27.78) [Circle]
+Component180 (24.51, 49.49) [Square]
+Component181 (43.33, 70.82)
+Component182 (11.32, 32.01) [Circle]
+Component183 (99.25, 72.09)
+Component184 (48.90, 59.71)
+Component185 (57.10, 16.75) [Circle]
+Component186 (83.37, 34.12) [Triangle]
+Component187 (8.87, 29.27)
+Component188 (65.57, 61.54)
+Component189 (51.83, 71.22) [X]
+Component190 (27.04, 61.86) [Circle]
+Component191 (6.69, 41.47)
+Component192 (23.48, 62.15)
+Component193 (2.86, 64.07)
+Component194 (69.27, 74.67) [Circle]
+Component195 (66.48, 9.12) [Square]
+Component196 (28.76, 87.81)
+Component197 (26.97, 6.71)
+Component198 (15.19, 94.43) [Triangle]
+Component199 (57.23, 82.51) [Triangle]
+Component200 (42.97, 37.53)
+Component201 (29.44, 2.29)
+Component202 (76.46, 16.24) [Circle]
+Component203 (94.20, 17.90) [Circle]
+Component204 (30.13, 51.44) [Triangle]
+Component205 (68.98, 48.39)
+Component206 (24.23, 62.30) [Triangle]
+Component207 (24.83, 48.06)
+Component208 (8.33, 26.57) [X]
+Component209 (77.02, 35.30) [X]
+Component210 (74.96, 30.66) [Circle]
+Component211 (81.52, 58.49)
+Component212 (69.30, 92.38) [Circle]
+Component213 (40.11, 41.45) [Square]
+Component214 (73.47, 31.71) [X]
+Component215 (71.39, 7.86) [Triangle]
+Component216 (77.89, 60.06) [Circle]
+Component217 (44.90, 74.67) [Circle]
+Component218 (16.20, 4.97)
+Component219 (69.28, 44.39)
+Component220 (0.28, 68.43) [Circle]
+Component221 (8.56, 43.62)
+Component222 (5.11, 15.54) [X]
+Component223 (12.17, 11.72)
+Component224 (54.72, 10.39) [X]
+Component225 (30.14, 65.32) [Circle]
+Component226 (81.92, 66.20) [Circle]
+Component227 (43.65, 77.82) [Triangle]
+Component228 (56.60, 57.50)
+Component229 (28.99, 49.69) [Circle]
+Component230 (0.07, 44.12)
+Component231 (7.19, 10.93) [X]
+Component232 (54.32, 11.62) [X]
+Component233 (10.16, 30.70) [Triangle]
+Component234 (52.36, 88.84) [Square]
+Component235 (29.22, 81.16) [Square]
+Component236 (56.28, 34.89)
+Component237 (70.98, 18.55) [Triangle]
+Component238 (95.70, 52.33) [X]
+Component239 (86.93, 77.86) [Triangle]
+Component240 (43.57, 61.46) [Circle]
+Component241 (63.10, 56.33) [Circle]
+Component242 (90.98, 98.11)
+Component243 (43.27, 79.39) [Triangle]
+Component244 (14.72, 5.66)
+Component245 (92.51, 69.32) [Square]
+Component246 (78.17, 82.06) [X]
+Component247 (87.01, 27.90) [Triangle]
+Component248 (35.33, 38.07)
+Component249 (74.21, 13.77) [Circle]
+Component250 (94.92, 12.62)
+Component251 (85.66, 59.81)
+Component252 (96.82, 40.39) [X]
+Component253 (53.31, 54.76) [X]
+Component254 (78.65, 34.31)
+Component255 (63.15, 35.91) [X]
+Component256 (93.70, 14.87) [Square]
+Component257 (87.74, 38.01) [X]
+Component258 (59.70, 23.06) [Triangle]
+Component259 (24.24, 39.58) [Triangle]
+Component260 (70.42, 43.34)
+Component261 (11.55, 10.52) [Circle]
+Component262 (44.08, 56.93) [X]
+Component263 (67.31, 47.53) [Circle]
+Component264 (37.36, 90.08)
+Component265 (66.67, 53.36) [Triangle]
+Component266 (29.99, 41.40)
+Component267 (41.91, 74.03) [Triangle]
+Component268 (19.68, 31.07) [Triangle]
+Component269 (22.36, 32.00)
+Component270 (91.55, 6.41) [Square]
+Component271 (44.01, 17.22) [Square]
+Component272 (30.74, 29.46)
+Component273 (94.66, 90.42) [Triangle]
+Component274 (72.88, 98.16) [X]
+Component275 (67.60, 71.55)
+Component276 (15.18, 43.38) [Triangle]
+Component277 (35.33, 39.53)
+Component278 (94.24, 15.72) [Square]
+Component279 (7.41, 46.96) [X]
+Component280 (89.81, 55.32)
+Component281 (46.56, 78.51) [Circle]
+Component282 (82.99, 9.68) [Triangle]
+Component283 (47.47, 92.82) [X]
+Component284 (13.31, 26.85) [Square]
+Component285 (46.03, 43.09)
+Component286 (2.34, 57.09)
+Component287 (49.65, 31.24) [Circle]
+Component288 (83.79, 57.32)
+Component289 (6.61, 42.28) [Square]
+Component290 (47.25, 10.20) [Triangle]
+Component291 (20.57, 45.07) [Square]
+Component292 (15.35, 49.21) [Circle]
+Component293 (72.98, 80.41) [Triangle]
+Component294 (40.77, 79.66)
+Component295 (91.69, 47.22) [X]
+Component296 (10.54, 48.39) [X]
+Component297 (71.89, 61.99) [Square]
+Component298 (10.28, 43.01)
+Component299 (53.88, 72.55)
+Component300 (9.35, 56.50)
+Component301 (28.09, 6.29) [Circle]
+Component302 (58.57, 73.35)
+Component303 (84.96, 51.81)
+Component304 (83.02, 53.58) [X]
+Component305 (79.51, 51.53) [X]
+Component306 (14.24, 99.49)
+Component307 (8.57, 85.13)
+Component308 (43.93, 30.83)
+Component309 (80.62, 29.87) [X]
+Component310 (68.72, 48.61) [X]
+Component311 (2.37, 81.53)
+Component312 (72.73, 68.09) [Circle]
+Component313 (30.74, 2.94) [Circle]
+Component314 (62.13, 68.91) [X]
+Component315 (73.38, 43.60) [Circle]
+Component316 (27.37, 98.57)
+Component317 (85.82, 67.69) [Circle]
+Component318 (45.24, 69.35) [Circle]
+Component319 (2.34, 66.07) [X]
+Component320 (67.45, 72.20)
+Component321 (88.21, 91.50) [Triangle]
+Component322 (47.99, 43.92)
+Component323 (40.33, 85.89)
+Component324 (1.64, 52.02) [Square]
+Component325 (82.44, 91.99) [Square]
+Component326 (64.93, 6.74)
+Component327 (69.13, 32.76)
+Component328 (18.83, 7.75) [X]
+Component329 (83.61, 30.14) [Square]
+Component330 (49.46, 98.58)
+Component331 (53.31, 1.37) [Circle]
+Component332 (15.54, 23.31) [Circle]
+Component333 (81.39, 44.90)
+Component334 (40.59, 17.35) [Square]
+Component335 (96.35, 80.15) [Circle]
+Component336 (57.23, 15.80) [X]
+Component337 (69.45, 58.61)
+Component338 (75.96, 32.46) [X]
+Component339 (6.37, 65.81) [Triangle]
+Component340 (59.14, 70.50)
+Component341 (79.03, 41.50) [Square]
+Component342 (35.41, 80.78)
+Component343 (87.18, 45.70)
+Component344 (81.33, 58.50)
+Component345 (50.63, 17.49) [Square]
+Component346 (10.10, 83.23)
+Component347 (74.57, 16.10) [X]
+Component348 (42.80, 0.78)
+Component349 (58.17, 89.85) [Triangle]
+Component350 (80.79, 52.51) [X]
+Component351 (56.34, 15.08) [Circle]
+Component352 (98.43, 1.47) [Circle]
+Component353 (19.88, 99.79) [Circle]
+Component354 (51.99, 2.67) [Square]
+Component355 (44.85, 23.76) [Square]
+Component356 (14.24, 98.11) [Circle]
+Component357 (25.68, 57.61) [Square]
+Component358 (5.75, 71.94) [Square]
+Component359 (68.05, 63.63) [Square]
+Component360 (12.81, 46.56) [Square]
+Component361 (56.35, 20.51) [Square]
+Component362 (43.13, 82.54) [Circle]
+Component363 (72.98, 59.02) [Triangle]
+Component364 (67.80, 56.66) [Circle]
+Component365 (58.12, 58.28) [Square]
+Component366 (81.73, 0.72)
+Component367 (22.11, 82.65)
+Component368 (98.81, 19.86) [X]
+Component369 (17.27, 57.91)
+Component370 (71.83, 47.45)
+Component371 (44.85, 0.45) [Square]
+Component372 (57.89, 51.13) [Circle]
+Component373 (63.17, 2.05) [Square]
+Component374 (44.72, 91.15)
+Component375 (71.04, 46.51) [Triangle]
+Component376 (3.32, 46.78) [Triangle]
+Component377 (72.48, 23.81) [Circle]
+Component378 (98.32, 26.85)
+Component379 (23.27, 92.77) [X]
+Component380 (70.01, 22.45) [Square]
+Component381 (86.92, 0.65) [X]
+Component382 (0.25, 21.60) [Square]
+Component383 (3.44, 79.21)
+Component384 (35.36, 27.11) [X]
+Component385 (45.80, 29.73) [Triangle]
+Component386 (21.58, 74.37) [X]
+Component387 (76.83, 29.78)
+Component388 (11.95, 69.94)
+Component389 (10.05, 71.48)
+Component390 (11.26, 80.96)
+Component391 (89.52, 30.54) [Square]
+Component392 (59.05, 40.71) [Triangle]
+Component393 (60.09, 97.03) [X]
+Component394 (17.57, 50.48) [X]
+Component395 (36.74, 53.99) [Triangle]
+Component396 (70.58, 10.19)
+Component397 (70.27, 24.32) [Circle]
+Component398 (96.61, 90.28) [Triangle]
+Component399 (85.63, 16.61) [X]
+Component400 (22.40, 43.77) [Circle]
+Component401 (31.86, 93.15) [Triangle]
+Component402 (10.49, 77.04) [X]
+Component403 (61.76, 14.09) [Circle]
+Component404 (14.98, 44.56) [Square]
+Component405 (1.37, 41.79)
+Component406 (66.89, 99.29)
+Component407 (54.72, 26.16) [Circle]
+Component408 (4.02, 78.88) [X]
+Component409 (61.57, 55.03) [X]
+Component410 (15.00, 72.61) [Circle]
+Component411 (30.84, 58.13)
+Component412 (74.62, 34.29)
+Component413 (87.16, 42.02)
+Component414 (42.56, 13.70) [Triangle]
+Component415 (63.00, 19.68) [Circle]
+Component416 (84.00, 85.81) [Circle]
+Component417 (99.85, 26.55)
+Component418 (2.32, 39.70)
+Component419 (50.24, 60.73)
+Component420 (50.87, 91.30) [Square]
+Component421 (89.47, 44.46)
+Component422 (57.31, 31.98) [Circle]
+Component423 (35.68, 39.96) [Circle]
+Component424 (25.96, 65.24)
+Component425 (18.03, 51.91)
+Component426 (29.60, 99.06) [Triangle]
+Component427 (19.24, 90.42)
+Component428 (41.83, 16.49) [X]
+Component429 (23.20, 79.00) [Square]
+Component430 (58.74, 20.49) [Circle]
+Component431 (12.51, 51.74) [Triangle]
+Component432 (36.63, 90.02) [Square]
+Component433 (2.31, 38.41)
+Component434 (71.42, 22.16)
+Component435 (19.13, 72.21) [Triangle]
+Component436 (96.97, 89.38)
+Component437 (36.78, 84.85)
+Component438 (41.40, 18.25)
+Component439 (99.42, 98.89) [X]
+Component440 (65.90, 64.04) [Triangle]
+Component441 (66.50, 54.30)
+Component442 (25.00, 1.22)
+Component443 (91.63, 77.36) [X]
+Component444 (91.54, 35.26) [Square]
+Component445 (68.43, 4.16) [Circle]
+Component446 (51.33, 47.47) [Circle]
+Component447 (19.82, 70.34) [Triangle]
+Component448 (87.82, 97.43)
+Component449 (47.41, 73.13) [X]
+Component450 (9.65, 4.74) [Circle]
+Component451 (16.64, 31.95) [Circle]
+Component452 (98.44, 49.75) [Triangle]
+Component453 (12.91, 75.20)
+Component454 (68.13, 57.99)
+Component455 (88.52, 73.61) [Circle]
+Component456 (43.00, 47.91)
+Component457 (67.28, 76.60) [Circle]
+Component458 (30.11, 20.41) [Triangle]
+Component459 (32.38, 7.73)
+Component460 (48.65, 59.39) [Circle]
+Component461 (95.93, 13.43) [Circle]
+Component462 (76.05, 97.64) [Triangle]
+Component463 (10.57, 5.86)
+Component464 (15.16, 65.67) [Triangle]
+Component465 (49.29, 37.41)
+Component466 (62.86, 55.21)
+Component467 (41.70, 22.98) [X]
+Component468 (11.40, 42.51) [Square]
+Component469 (99.96, 9.88) [X]
+Component470 (74.36, 18.50) [Triangle]
+Component471 (27.71, 2.13) [X]
+Component472 (37.76, 41.14)
+Component473 (54.41, 69.25) [Triangle]
+Component474 (19.69, 0.90)
+Component475 (33.44, 22.53) [X]
+Component476 (78.46, 53.81)
+Component477 (27.36, 94.83) [X]
+Component478 (53.98, 15.10) [Triangle]
+Component479 (49.75, 90.23) [Square]
+Component480 (26.13, 83.45)
+Component481 (75.99, 39.97)
+Component482 (17.03, 54.73) [Triangle]
+Component483 (10.89, 86.65)
+Component484 (37.28, 90.48)
+Component485 (59.96, 76.87) [Triangle]
+Component486 (80.36, 98.15)
+Component487 (9.26, 96.01)
+Component488 (62.92, 30.57) [Square]
+Component489 (50.52, 20.80) [X]
+Component490 (23.88, 11.84)
+Component491 (77.93, 5.51)
+Component492 (56.88, 8.00)
+Component493 (86.70, 55.53) [Square]
+Component494 (62.22, 67.78) [Triangle]
+Component495 (50.63, 55.06) [Circle]
+Component496 (6.86, 22.43) [X]
+Component497 (93.32, 40.43) [Circle]
+Component498 (96.81, 51.60)
+Component499 (59.56, 57.21) [Square]
+Component500 (2.54, 51.36)
+Component501 (88.77, 48.72) [Square]
+Component502 (80.17, 78.87) [Circle]
+Component503 (63.34, 14.75) [Square]
+Component504 (21.89, 35.77) [Circle]
+Component505 (17.52, 41.69) [X]
+Component506 (59.66, 93.27) [X]
+Component507 (44.93, 75.12) [Square]
+Component508 (2.75, 88.38) [Triangle]
+Component509 (21.28, 6.63) [Square]
+Component510 (86.03, 50.85) [Circle]
+Component511 (44.85, 98.92) [Square]
+Component512 (82.29, 88.28)
+Component513 (99.39, 16.41) [Square]
+Component514 (38.85, 11.93) [Circle]
+Component515 (79.06, 95.86) [X]
+Component516 (0.49, 66.17) [X]
+Component517 (34.87, 73.24)
+Component518 (57.08, 37.29)
+Component519 (59.70, 27.17)
+Component520 (26.20, 98.63) [Square]
+Component521 (73.63, 43.73) [X]
+Component522 (52.00, 34.57) [X]
+Component523 (61.51, 25.40) [Triangle]
+Component524 (49.91, 28.64)
+Component525 (5.83, 22.08) [X]
+Component526 (62.78, 28.57)
+Component527 (17.11, 32.92) [X]
+Component528 (76.93, 82.48) [Circle]
+Component529 (18.24, 51.31)
+Component530 (35.40, 4.42)
+Component531 (65.91, 71.77) [X]
+Component532 (51.22, 26.10) [X]
+Component533 (32.79, 13.41) [X]
+Component534 (91.34, 95.42) [X]
+Component535 (84.52, 23.28) [Circle]
+Component536 (46.75, 70.21)
+Component537 (73.34, 99.55) [Triangle]
+Component538 (21.28, 97.53) [Square]
+Component539 (53.13, 34.63) [Circle]
+Component540 (45.98, 67.68)
+Component541 (76.47, 11.65) [Square]
+Component542 (8.72, 99.30) [Square]
+Component543 (40.19, 84.35) [Square]
+Component544 (54.66, 66.40) [Triangle]
+Component545 (30.53, 31.73) [Square]
+Component546 (72.43, 41.03)
+Component547 (77.05, 93.93)
+Component548 (84.24, 4.94)
+Component549 (48.93, 2.90) [Square]
+Component550 (22.94, 7.40)
+Component551 (91.81, 28.17)
+Component552 (12.27, 44.62) [Circle]
+Component553 (85.09, 90.79) [Circle]
+Component554 (14.90, 44.18) [X]
+Component555 (77.18, 32.00) [X]
+Component556 (90.01, 54.51)
+Component557 (21.88, 92.41) [Circle]
+Component558 (3.92, 26.93) [X]
+Component559 (17.08, 7.74)
+Component560 (96.04, 1.34) [Circle]
+Component561 (32.55, 12.12) [X]
+Component562 (32.12, 73.08) [Triangle]
+Component563 (34.09, 73.03) [Triangle]
+Component564 (83.10, 17.21) [Triangle]
+Component565 (47.20, 62.09) [Square]
+Component566 (66.85, 41.75) [X]
+Component567 (56.32, 73.47) [Square]
+Component568 (63.74, 14.66)
+Component569 (29.04, 21.96) [X]
+Component570 (40.40, 10.86)
+Component571 (5.16, 84.97) [Circle]
+Component572 (38.42, 2.42) [Square]
+Component573 (85.44, 74.93) [Square]
+Component574 (87.91, 0.60) [Square]
+Component575 (26.21, 7.64) [X]
+Component576 (74.56, 16.79) [Square]
+Component577 (77.85, 68.39)
+Component578 (62.92, 87.85) [X]
+Component579 (70.76, 28.86) [Circle]
+Component580 (82.86, 24.47) [X]
+Component581 (4.56, 9.62) [Triangle]
+Component582 (37.45, 94.56) [Circle]
+Component583 (12.14, 30.34) [X]
+Component584 (3.95, 57.21) [Square]
+Component585 (38.37, 72.37)
+Component586 (25.84, 82.02)
+Component587 (23.21, 59.31) [Square]
+Component588 (45.93, 79.55)
+Component589 (55.65, 75.30) [Square]
+Component590 (75.80, 51.42)
+Component591 (12.33, 87.97)
+Component592 (76.12, 25.18) [X]
+Component593 (42.48, 48.63)
+Component594 (83.10, 44.41) [Triangle]
+Component595 (85.27, 79.81) [Triangle]
+Component596 (2.29, 76.07)
+Component597 (79.85, 49.82)
+Component598 (48.10, 41.86) [X]
+Component599 (30.99, 12.78) [Circle]
+Component600 (85.67, 2.54) [Circle]
+Component601 (34.27, 62.27)
+Component602 (64.46, 94.92) [Triangle]
+Component603 (39.70, 37.16)
+Component604 (17.25, 23.20) [Circle]
+Component605 (84.77, 93.12) [Circle]
+Component606 (20.36, 65.56)
+Component607 (40.75, 34.65)
+Component608 (78.99, 67.22) [X]
+Component609 (33.60, 63.19) [X]
+Component610 (96.15, 23.34) [X]
+Component611 (94.94, 23.74)
+Component612 (18.31, 38.34) [Triangle]
+Component613 (60.20, 31.49) [Square]
+Component614 (71.73, 44.16) [X]
+Component615 (93.85, 69.80) [Triangle]
+Component616 (4.89, 51.46)
+Component617 (92.34, 23.59) [Square]
+Component618 (79.12, 23.87) [X]
+Component619 (5.24, 26.52) [Circle]
+Component620 (66.10, 17.05) [X]
+Component621 (80.15, 91.21)
+Component622 (60.56, 35.33)
+Component623 (38.84, 73.56) [X]
+Component624 (32.94, 65.46) [Square]
+Component625 (72.03, 93.52)
+Component626 (51.66, 1.88)
+Component627 (43.42, 69.63)
+Component628 (68.93, 78.58) [Triangle]
+Component629 (12.17, 88.50) [Square]
+Component630 (25.74, 39.64)
+Component631 (89.94, 17.59) [Triangle]
+Component632 (18.03, 5.97) [Square]
+Component633 (63.08, 82.42) [Circle]
+Component634 (64.06, 7.17) [Circle]
+Component635 (30.33, 44.27) [Triangle]
+Component636 (89.42, 2.98) [Square]
+Component637 (11.69, 51.64) [Triangle]
+Component638 (27.67, 38.16) [X]
+Component639 (82.42, 96.29) [Square]
+Component640 (94.21, 94.49)
+Component641 (8.58, 71.71)
+Component642 (70.45, 91.93)
+Component643 (10.96, 1.29) [Triangle]
+Component644 (10.57, 19.18) [Triangle]
+Component645 (57.76, 83.31) [Square]
+Component646 (12.13, 8.42) [Circle]
+Component647 (44.58, 80.07) [X]
+Component648 (81.68, 24.58)
+Component649 (52.65, 92.38)
+Component650 (39.35, 41.67) [Triangle]
+Component651 (55.90, 65.04)
+Component652 (67.25, 14.83) [Square]
+Component653 (8.49, 24.18)
+Component654 (65.87, 40.83) [X]
+Component655 (3.18, 17.64) [Square]
+Component656 (25.59, 33.81)
+Component657 (73.70, 19.39)
+Component658 (21.35, 87.93) [Square]
+Component659 (64.18, 97.24) [Square]
+Component660 (32.48, 6.54) [X]
+Component661 (86.73, 65.34) [Circle]
+Component662 (98.31, 91.84) [Triangle]
+Component663 (48.72, 75.26) [Square]
+Component664 (33.81, 25.98) [Triangle]
+Component665 (83.92, 87.11) [Triangle]
+Component666 (36.37, 54.35)
+Component667 (70.76, 58.70) [Circle]
+Component668 (87.91, 69.42)
+Component669 (5.06, 12.39)
+Component670 (22.26, 4.78)
+Component671 (82.28, 96.57) [Triangle]
+Component672 (28.71, 40.93) [Circle]
+Component673 (6.42, 25.09) [Square]
+Component674 (57.90, 76.51)
+Component675 (46.54, 26.90) [Triangle]
+Component676 (6.60, 37.36)
+Component677 (67.94, 26.83)
+Component678 (70.57, 66.78) [Triangle]
+Component679 (92.63, 29.54)
+Component680 (21.06, 56.15)
+Component681 (30.12, 87.84) [Square]
+Component682 (22.50, 17.49) [Triangle]
+Component683 (22.16, 99.28) [Square]
+Component684 (86.25, 85.65) [Triangle]
+Component685 (42.38, 97.43) [Square]
+Component686 (82.82, 98.13) [Triangle]
+Component687 (85.99, 22.81) [Square]
+Component688 (92.42, 13.14) [Circle]
+Component689 (86.08, 22.54) [Circle]
+Component690 (78.61, 48.10) [Square]
+Component691 (2.95, 70.77)
+Component692 (96.12, 92.64) [Square]
+Component693 (12.50, 97.26) [Square]
+Component694 (86.32, 25.85)
+Component695 (80.51, 28.95) [Triangle]
+Component696 (46.82, 6.61)
+Component697 (66.87, 17.17) [Circle]
+Component698 (99.04, 68.95) [Triangle]
+Component699 (77.10, 12.58) [Triangle]
+Component700 (81.22, 95.50) [Square]
+Component701 (58.44, 84.13)
+Component702 (80.19, 61.91)
+Component703 (96.80, 74.86) [Circle]
+Component704 (83.91, 79.21) [Circle]
+Component705 (94.51, 96.03) [Triangle]
+Component706 (51.67, 13.80)
+Component707 (29.79, 8.71) [Circle]
+Component708 (36.15, 69.18) [Triangle]
+Component709 (68.99, 71.52) [Circle]
+Component710 (88.45, 64.95) [Circle]
+Component711 (80.05, 71.13) [Circle]
+Component712 (50.04, 22.87) [Triangle]
+Component713 (17.38, 28.08) [Circle]
+Component714 (88.91, 43.58) [Triangle]
+Component715 (82.29, 88.78) [Circle]
+Component716 (68.36, 82.95) [Square]
+Component717 (10.29, 93.22)
+Component718 (92.84, 27.40)
+Component719 (39.48, 91.46) [Triangle]
+Component720 (69.69, 83.40)
+Component721 (33.85, 40.52) [Square]
+Component722 (97.83, 99.99) [Triangle]
+Component723 (62.84, 11.09) [X]
+Component724 (43.10, 47.36) [Triangle]
+Component725 (17.38, 82.24) [Triangle]
+Component726 (54.82, 94.05)
+Component727 (5.63, 25.27) [X]
+Component728 (80.85, 80.59) [Triangle]
+Component729 (59.77, 7.37) [X]
+Component730 (68.88, 77.36) [X]
+Component731 (93.49, 80.85) [Square]
+Component732 (52.99, 75.23) [Circle]
+Component733 (37.52, 58.87)
+Component734 (9.84, 11.57) [Triangle]
+Component735 (32.67, 39.28) [X]
+Component736 (46.91, 23.64) [Square]
+Component737 (33.46, 92.43) [Triangle]
+Component738 (60.48, 16.10) [X]
+Component739 (60.30, 74.77)
+Component740 (97.63, 8.09)
+Component741 (11.78, 92.13)
+Component742 (41.21, 41.45) [Circle]
+Component743 (4.93, 37.13) [X]
+Component744 (13.29, 7.31)
+Component745 (69.09, 18.30) [Circle]
+Component746 (49.88, 88.56)
+Component747 (66.69, 64.64) [X]
+Component748 (71.42, 24.62) [Triangle]
+Component749 (39.46, 93.61)
+Component750 (2.08, 22.34) [Circle]
+Component751 (89.58, 41.54)
+Component752 (47.94, 84.87)
+Component753 (65.47, 55.58) [X]
+Component754 (53.54, 5.12) [Square]
+Component755 (74.49, 17.69) [Square]
+Component756 (12.88, 88.92) [X]
+Component757 (52.18, 50.44) [X]
+Component758 (68.05, 52.05) [Circle]
+Component759 (93.47, 30.01) [Circle]
+Component760 (94.90, 99.55) [X]
+Component761 (84.05, 4.44) [Circle]
+Component762 (42.74, 36.71) [Square]
+Component763 (85.63, 69.38) [X]
+Component764 (65.40, 66.21) [Triangle]
+Component765 (21.67, 14.78) [Circle]
+Component766 (53.61, 70.93) [Triangle]
+Component767 (5.07, 78.99)
+Component768 (53.15, 36.64) [Circle]
+Component769 (46.97, 1.41)
+Component770 (33.90, 44.43)
+Component771 (39.45, 36.48)
+Component772 (67.78, 90.87) [Circle]
+Component773 (96.24, 3.57) [Triangle]
+Component774 (41.37, 82.60) [Triangle]
+Component775 (95.15, 52.79) [Square]
+Component776 (20.68, 54.81)
+Component777 (84.49, 48.27) [Circle]
+Component778 (99.01, 87.63)
+Component779 (49.10, 13.34)
+Component780 (65.15, 86.57)
+Component781 (91.51, 32.85)
+Component782 (60.79, 26.62) [Triangle]
+Component783 (34.55, 24.16)
+Component784 (25.73, 57.46)
+Component785 (23.74, 52.01)
+Component786 (21.19, 90.75) [Triangle]
+Component787 (83.22, 80.23) [Square]
+Component788 (48.28, 63.70) [Triangle]
+Component789 (47.02, 71.16)
+Component790 (98.87, 44.60)
+Component791 (80.99, 3.44) [Circle]
+Component792 (41.82, 95.50) [Triangle]
+Component793 (69.54, 96.23) [Triangle]
+Component794 (49.78, 89.18)
+Component795 (43.14, 80.71) [Triangle]
+Component796 (70.04, 8.33)
+Component797 (32.29, 29.23) [Square]
+Component798 (58.10, 42.58)
+Component799 (64.07, 98.61) [Circle]
+Component800 (35.86, 61.33) [X]
+Component801 (8.55, 54.52)
+Component802 (55.95, 99.76) [Triangle]
+Component803 (19.90, 4.60) [Circle]
+Component804 (49.99, 96.57)
+Component805 (73.19, 42.31) [Triangle]
+Component806 (52.10, 15.63) [Circle]
+Component807 (89.95, 99.16) [Square]
+Component808 (82.80, 94.93)
+Component809 (90.47, 43.12)
+Component810 (36.98, 51.49)
+Component811 (99.92, 14.67) [Square]
+Component812 (62.57, 8.28)
+Component813 (36.85, 24.38)
+Component814 (2.59, 63.52) [Square]
+Component815 (98.73, 86.84)
+Component816 (29.92, 3.73)
+Component817 (90.50, 63.09)
+Component818 (76.17, 28.16) [Triangle]
+Component819 (75.38, 42.93) [Square]
+Component820 (86.39, 19.53)
+Component821 (65.52, 54.88)
+Component822 (31.16, 50.90) [Square]
+Component823 (58.12, 53.66)
+Component824 (95.46, 46.39) [X]
+Component825 (76.11, 29.03)
+Component826 (38.60, 28.10) [X]
+Component827 (96.21, 96.79)
+Component828 (58.19, 88.64)
+Component829 (94.16, 47.60) [Square]
+Component830 (40.67, 86.94)
+Component831 (30.28, 55.52) [Square]
+Component832 (17.24, 40.01) [Square]
+Component833 (83.82, 84.98) [X]
+Component834 (67.50, 79.68)
+Component835 (30.15, 53.82) [Circle]
+Component836 (92.23, 78.01) [Triangle]
+Component837 (27.23, 92.60) [Square]
+Component838 (31.61, 25.63) [X]
+Component839 (61.71, 44.33) [Square]
+Component840 (78.48, 69.79) [Circle]
+Component841 (44.89, 97.80) [X]
+Component842 (48.90, 49.06) [Triangle]
+Component843 (15.44, 16.25) [Triangle]
+Component844 (8.31, 94.22)
+Component845 (31.64, 49.32)
+Component846 (75.81, 94.49) [X]
+Component847 (99.41, 85.31) [Circle]
+Component848 (91.64, 32.44) [Square]
+Component849 (49.66, 55.99)
+Component850 (90.85, 80.95)
+Component851 (20.72, 44.29) [X]
+Component852 (66.52, 1.30) [Square]
+Component853 (9.92, 86.11) [X]
+Component854 (33.23, 85.49)
+Component855 (65.31, 70.68) [Square]
+Component856 (17.92, 62.02) [Square]
+Component857 (95.52, 64.69)
+Component858 (11.31, 0.13)
+Component859 (59.34, 97.52)
+Component860 (17.28, 96.61)
+Component861 (33.92, 80.85) [Square]
+Component862 (54.06, 3.36) [Circle]
+Component863 (46.14, 16.09) [Triangle]
+Component864 (77.52, 15.25)
+Component865 (1.65, 25.12) [X]
+Component866 (11.77, 25.49)
+Component867 (41.81, 74.80) [Circle]
+Component868 (6.72, 7.94) [X]
+Component869 (78.18, 76.61)
+Component870 (84.57, 14.90) [Circle]
+Component871 (27.36, 76.49) [Square]
+Component872 (74.01, 20.52) [Circle]
+Component873 (72.83, 82.87)
+Component874 (9.74, 46.82) [X]
+Component875 (85.55, 26.40) [X]
+Component876 (90.96, 12.96) [Circle]
+Component877 (83.21, 30.43)
+Component878 (18.32, 76.50) [X]
+Component879 (64.86, 10.96) [Circle]
+Component880 (20.28, 82.11) [Square]
+Component881 (53.75, 89.58) [X]
+Component882 (60.19, 95.72) [Square]
+Component883 (18.98, 48.08) [X]
+Component884 (72.61, 93.24) [Circle]
+Component885 (35.46, 4.72)
+Component886 (52.80, 41.57) [Square]
+Component887 (53.44, 29.95)
+Component888 (10.23, 58.60) [Square]
+Component889 (68.29, 30.06) [Square]
+Component890 (76.88, 68.56)
+Component891 (31.04, 37.55) [Square]
+Component892 (17.55, 78.76)
+Component893 (10.10, 12.05) [X]
+Component894 (73.22, 27.89) [Square]
+Component895 (60.35, 81.53)
+Component896 (4.47, 73.49) [Triangle]
+Component897 (8.72, 67.26)
+Component898 (26.71, 23.78)
+Component899 (81.72, 36.94) [Triangle]
+Component900 (16.01, 86.38) [Square]
+Component901 (55.07, 85.27) [Triangle]
+Component902 (70.30, 12.88)
+Component903 (60.31, 62.86) [Triangle]
+Component904 (41.00, 37.55) [Triangle]
+Component905 (9.64, 61.89) [Circle]
+Component906 (54.10, 31.77) [X]
+Component907 (76.54, 62.41)
+Component908 (86.17, 96.56)
+Component909 (46.85, 49.44)
+Component910 (37.67, 41.22) [Triangle]
+Component911 (26.66, 62.43) [Triangle]
+Component912 (69.43, 3.66)
+Component913 (33.47, 70.71) [Square]
+Component914 (93.21, 77.98) [Circle]
+Component915 (90.74, 9.07)
+Component916 (93.97, 16.70) [Square]
+Component917 (8.15, 60.87)
+Component918 (50.09, 96.07) [Square]
+Component919 (75.88, 86.17) [Circle]
+Component920 (68.31, 86.98) [X]
+Component921 (67.83, 79.88)
+Component922 (97.10, 82.63) [X]
+Component923 (83.01, 1.06) [X]
+Component924 (39.58, 86.43) [Circle]
+Component925 (47.55, 84.77)
+Component926 (96.69, 55.45) [X]
+Component927 (77.39, 79.82)
+Component928 (43.07, 76.23) [X]
+Component929 (78.14, 10.36) [Triangle]
+Component930 (30.33, 63.76) [X]
+Component931 (41.25, 52.50)
+Component932 (31.96, 82.30)
+Component933 (65.06, 59.05) [Circle]
+Component934 (26.94, 44.22) [Circle]
+Component935 (27.72, 29.76)
+Component936 (70.78, 0.65) [Triangle]
+Component937 (58.58, 74.89)
+Component938 (98.48, 3.16)
+Component939 (17.30, 75.27)
+Component940 (48.66, 72.60) [X]
+Component941 (85.22, 46.47) [Triangle]
+Component942 (67.36, 93.17) [Triangle]
+Component943 (79.00, 3.03)
+Component944 (93.55, 16.21)
+Component945 (33.57, 39.55)
+Component946 (30.28, 29.41)
+Component947 (98.43, 70.60) [X]
+Component948 (96.67, 13.72) [Triangle]
+Component949 (38.28, 48.43) [Triangle]
+Component950 (88.84, 46.42) [Circle]
+Component951 (73.14, 28.70) [X]
+Component952 (64.62, 10.80) [X]
+Component953 (17.27, 35.05)
+Component954 (28.44, 50.29) [Circle]
+Component955 (68.78, 52.58)
+Component956 (92.74, 72.49)
+Component957 (74.46, 73.66) [Circle]
+Component958 (91.38, 5.63) [Square]
+Component959 (17.30, 2.57) [Triangle]
+Component960 (77.85, 76.45) [Square]
+Component961 (67.51, 95.09)
+Component962 (22.14, 74.92) [Circle]
+Component963 (11.07, 79.09) [Circle]
+Component964 (93.58, 6.56)
+Component965 (30.32, 76.50) [Circle]
+Component966 (88.27, 78.61) [Circle]
+Component967 (99.07, 68.94) [X]
+Component968 (4.65, 2.36) [Square]
+Component969 (58.21, 65.30) [Triangle]
+Component970 (97.18, 30.96) [Square]
+Component971 (14.96, 34.69)
+Component972 (24.39, 36.35)
+Component973 (68.87, 87.50)
+Component974 (8.47, 85.32) [Square]
+Component975 (22.83, 76.01) [Circle]
+Component976 (49.17, 0.04) [Circle]
+Component977 (68.21, 48.73)
+Component978 (89.13, 91.30) [Triangle]
+Component979 (65.46, 59.78) [Triangle]
+Component980 (90.34, 57.29)
+Component981 (96.18, 5.07) [Triangle]
+Component982 (76.27, 45.30) [Circle]
+Component983 (46.02, 62.58) [Circle]
+Component984 (85.41, 20.20) [X]
+Component985 (87.32, 88.29) [X]
+Component986 (99.07, 66.28)
+Component987 (80.59, 74.45)
+Component988 (4.96, 92.28)
+Component989 (29.93, 45.09) [Triangle]
+Component990 (41.97, 25.00)
+Component991 (17.13, 20.49)
+Component992 (95.65, 87.44) [Circle]
+Component993 (98.97, 40.15) [Circle]
+Component994 (18.60, 5.01)
+Component995 (3.77, 53.31) [Triangle]
+Component996 (39.81, 95.10) [X]
+Component997 (0.87, 96.79)
+Component998 (9.14, 29.01) [Triangle]
+Component999 (40.79, 92.24) [Circle]
+
+Component387 -> Component728
+Component563 -- Component366
+Component537 -- Component292
+Component166 -- Component631
+Component300 -> Component112
+Component878 -> Component414
+Component988 -> Component49
+Component174 -- Component89
+Component585 -- Component354
+Component681 -- Component25
+Component994 -- Component479
+Component68 -- Component702
+Component619 -> Component448
+Component899 -> Component43
+Component927 -> Component175
+Component732 -> Component404
+Component751 -- Component519
+Component982 -- Component595
+Component101 -- Component259
+Component933 -- Component208
+Component717 -- Component607
+Component897 -- Component386
+Component66 -> Component18
+Component517 -> Component829
+Component132 -> Component214
+Component228 -- Component446
+Component578 -- Component369
+Component841 -> Component805
+Component787 -- Component789
+Component478 -- Component421
+Component281 -> Component863
+Component218 -- Component232
+Component853 -> Component598
+Component277 -> Component714
+Component815 -- Component66
+Component17 -> Component878
+Component973 -- Component631
+Component855 -> Component787
+Component326 -- Component670
+Component535 -> Component24
+Component347 -> Component624
+Component921 -> Component172
+Component543 -- Component130
+Component879 -- Component456
+Component50 -> Component120
+Component243 -> Component753
+Component549 -> Component971
+Component563 -- Component895
+Component23 -- Component650
+Component772 -- Component980
+Component379 -- Component342
+Component744 -> Component785
+Component746 -- Component138
+Component188 -> Component96
+Component486 -> Component400
+Component40 -> Component475
+Component860 -- Component88
+Component30 -> Component105
+Component479 -> Component790
+Component542 -> Component193
+Component143 -> Component966
+Component183 -> Component260
+Component8 -- Component437
+Component579 -- Component4
+Component120 -- Component488
+Component961 -> Component470
+Component952 -> Component581
+Component60 -- Component682
+Component918 -- Component235
+Component9 -> Component1
+Component503 -- Component452
+Component74 -- Component911
+Component184 -> Component260
+Component370 -- Component927
+Component678 -> Component506
+Component152 -> Component403
+Component877 -- Component649
+Component108 -- Component944
+Component580 -> Component726
+Component691 -> Component384
+Component953 -- Component452
+Component660 -> Component582
+Component695 -> Component152
+Component222 -> Component858
+Component161 -- Component275
+Component604 -- Component112
+Component130 -- Component203
+Component256 -- Component892
+Component120 -- Component946
+Component97 -> Component208
+Component905 -> Component632
+Component918 -- Component195
+Component961 -- Component860
+Component33 -> Component336
+Component613 -- Component222
+Component466 -> Component796
+Component861 -> Component867
+Component145 -> Component425
+Component239 -- Component549
+Component340 -> Component634
+Component752 -> Component53
+Component633 -> Component681
+Component456 -> Component373
+Component71 -> Component343
+Component358 -- Component157
+Component615 -- Component657
+Component570 -- Component433
+Component561 -> Component90
+Component600 -- Component344
+Component254 -> Component746
+Component14 -> Component41
+Component340 -> Component126
+Component957 -> Component684
+Component259 -> Component949
+Component521 -- Component792
+Component90 -- Component929
+Component150 -> Component617
+Component854 -> Component538
+Component485 -> Component426
+Component639 -> Component596
+Component603 -> Component540
+Component815 -> Component365
+Component724 -> Component309
+Component736 -> Component800
+Component448 -> Component715
+Component507 -> Component303
+Component610 -> Component201
+Component712 -- Component599
+Component263 -> Component206
+Component321 -> Component519
+Component861 -> Component0
+Component160 -- Component223
+Component9 -> Component686
+Component866 -- Component200
+Component829 -- Component8
+Component73 -> Component557
+Component350 -> Component93
+Component379 -- Component467
+Component870 -> Component402
+Component385 -> Component675
+Component415 -- Component296
+Component698 -> Component36
+Component708 -> Component117
+Component550 -- Component427
+Component268 -> Component550
+Component573 -- Component124
+Component385 -- Component737
+Component968 -- Component7
+Component850 -> Component992
+Component166 -- Component337
+Component654 -- Component476
+Component768 -- Component834
+Component381 -- Component663
+Component540 -- Component770
+Component231 -> Component234
+Component602 -> Component180
+Component632 -- Component186
+Component568 -> Component583
+Component842 -- Component311
+Component23 -- Component344
+Component914 -- Component732
+Component514 -> Component426
+Component952 -> Component412
+Component192 -> Component734
+Component828 -> Component295
+Component341 -> Component530
+Component650 -- Component169
+Component668 -- Component446
+Component745 -> Component15
+Component995 -> Component321
+Component311 -> Component585
+Component16 -> Component94
+Component41 -> Component459
+Component576 -> Component647
+Component783 -- Component311
+Component842 -> Component857
+Component646 -> Component312
+Component252 -> Component703
+Component519 -- Component884
+Component767 -- Component465
+Component18 -> Component738
+Component743 -> Component828
+Component844 -> Component119
+Component270 -- Component552
+Component372 -- Component539
+Component259 -- Component720
+Component334 -- Component338
+Component357 -- Component551
+Component417 -- Component367
+Component905 -> Component693
+Component272 -> Component469
+Component127 -- Component888
+Component621 -- Component662
+Component424 -- Component512
+Component982 -> Component456
+Component58 -- Component333
+Component28 -> Component200
+Component110 -- Component703
+Component809 -> Component46
+Component414 -> Component881
+Component909 -- Component662
+Component804 -> Component531
+Component307 -> Component133
+Component815 -> Component64
+Component124 -- Component936
+Component413 -- Component299
+Component802 -- Component799
+Component448 -- Component874
+Component225 -- Component18
+Component553 -- Component730
+Component33 -> Component181
+Component751 -> Component567
+Component279 -> Component873
+Component768 -- Component982
+Component313 -> Component955
+Component375 -- Component681
+Component13 -> Component206
+Component772 -> Component981
+Component140 -> Component46
+Component462 -- Component533
+Component244 -- Component341
+Component471 -- Component172
+Component54 -- Component759
+Component923 -> Component449
+Component669 -> Component697
+Component646 -> Component19
+Component802 -> Component306
+Component756 -- Component343
+Component768 -- Component727
+Component906 -- Component19
+Component238 -- Component296
+Component599 -> Component74
+Component969 -- Component186
+Component249 -> Component355
+Component483 -- Component988
+Component669 -> Component226
+Component772 -- Component104
+Component533 -> Component890
+Component788 -- Component259
+Component967 -- Component260
+Component670 -- Component94
+Component291 -> Component139
+Component361 -> Component901
+Component338 -> Component953
+Component944 -- Component600
+Component40 -- Component464
+Component286 -- Component354
+Component599 -- Component912
+Component213 -- Component185
+Component63 -> Component48
+Component271 -- Component811
+Component112 -- Component782
+Component384 -- Component552
+Component351 -> Component162
+Component502 -- Component173
+Component81 -- Component405
+Component866 -- Component777
+Component219 -> Component279
+Component121 -> Component448
+Component437 -> Component890
+Component889 -> Component471
+Component436 -> Component705
+Component938 -- Component670
+Component733 -> Component709
+Component220 -- Component386
+Component57 -> Component652
+Component500 -- Component495
+Component282 -> Component724
+Component786 -> Component103
+Component844 -> Component822
+Component218 -- Component48
+Component662 -- Component544
+Component8 -- Component580
+Component137 -> Component943
+Component924 -> Component822
+Component562 -> Component337
+Component20 -- Component847
+Component60 -- Component310
+Component709 -- Component48
+Component476 -> Component453
+Component315 -- Component896
+Component29 -- Component429
+Component136 -> Component357
+Component782 -> Component890
+Component737 -> Component89
+Component806 -- Component929
+Component28 -> Component659
+Component698 -- Component852
+Component298 -> Component241
+Component64 -> Component863
+Component489 -> Component295
+Component204 -- Component248
+Component165 -> Component554
+Component970 -- Component465
+Component52 -- Component748
+Component900 -> Component759
+Component509 -> Component135
+Component187 -> Component516
+Component787 -- Component587
+Component939 -> Component917
+Component764 -> Component139
+Component64 -- Component79
+Component93 -> Component852
+Component589 -- Component991
+Component410 -- Component660
+Component242 -> Component87
+Component381 -> Component245
+Component413 -- Component262
+Component912 -- Component869
+Component334 -> Component483
+Component629 -> Component369
+Component293 -> Component159
+Component749 -- Component129
+Component244 -- Component394
+Component29 -- Component388
+Component523 -> Component443
+Component265 -- Component973
+Component380 -- Component983
+Component608 -- Component227
+Component119 -> Component472
+Component743 -- Component766
+Component83 -- Component942
+Component110 -- Component696
+Component754 -> Component24
+Component571 -- Component898
+Component290 -> Component615
+Component793 -- Component490
+Component746 -> Component992
+Component871 -- Component756
+Component623 -> Component259
+Component188 -> Component778
+Component348 -- Component392
+Component5 -- Component401
+Component162 -> Component680
+Component233 -- Component276
+Component859 -- Component42
+Component505 -- Component917
+Component328 -> Component892
+Component34 -- Component828
+Component526 -- Component744
+Component130 -- Component970
+Component673 -> Component884
+Component852 -> Component630
+Component102 -- Component41
+Component756 -- Component573
+Component31 -- Component592
+Component558 -> Component400
+Component1 -- Component576
+Component575 -> Component184
+Component19 -- Component732
+Component30 -- Component534
+Component343 -- Component614
+Component562 -> Component480
+Component361 -- Component979
+Component86 -> Component597
+Component192 -- Component600
+Component553 -- Component491
+Component530 -> Component323
+Component995 -> Component720
+Component40 -- Component963
+Component438 -- Component271
+Component941 -> Component86
+Component161 -> Component997
+Component493 -> Component351
+Component907 -- Component307
+Component803 -> Component218
+Component232 -> Component372
+Component74 -> Component63
+Component386 -- Component365
+Component445 -- Component476
+Component633 -- Component463
+Component605 -- Component387
+Component650 -> Component693
+Component338 -> Component843
+Component518 -> Component188
+Component349 -> Component454
+Component889 -> Component874
+Component764 -> Component65
+Component112 -- Component780
+Component253 -- Component757
+Component924 -- Component893
+Component547 -- Component297
+Component372 -> Component852
+Component682 -> Component865
+Component743 -> Component716
+Component31 -> Component618
+Component896 -> Component951
+Component157 -- Component649
+Component22 -- Component966
+Component811 -> Component86
+Component212 -> Component80
+Component473 -> Component236
+Component979 -> Component43
+Component668 -- Component995
+Component412 -> Component594
+Component516 -- Component472
+Component500 -> Component554
+Component148 -- Component185
+Component769 -- Component438
+Component512 -- Component570
+Component249 -> Component990
+Component185 -> Component233
+Component94 -- Component320
+Component858 -- Component197
+Component268 -- Component6
+Component529 -- Component806
+Component184 -- Component779
+Component235 -- Component894
+Component215 -> Component990
+Component7 -- Component427
+Component583 -> Component297
+Component504 -- Component148
+Component92 -- Component223
+Component693 -> Component151
+Component858 -- Component241
+Component523 -- Component919
+Component938 -> Component926
+Component550 -- Component761
+Component25 -- Component532
+Component789 -- Component151
+Component497 -- Component190
+Component443 -- Component319
+Component82 -> Component780
+Component523 -> Component205
+Component176 -- Component456
+Component600 -> Component231
+Component609 -- Component908
+Component374 -> Component895
+Component919 -> Component605
+Component74 -- Component953
+Component70 -> Component75
+Component757 -- Component405
+Component756 -- Component342
+Component847 -- Component252
+Component596 -> Component131
+Component407 -- Component957
+Component493 -> Component691
+Component618 -- Component506
+Component788 -- Component107
+Component700 -- Component258
+Component419 -> Component896
+Component936 -> Component433
+Component221 -> Component206
+Component167 -> Component57
+Component86 -> Component1
+Component825 -> Component326
+Component643 -- Component121
+Component912 -- Component132
+Component307 -- Component636
+Component218 -- Component84
+Component192 -> Component656
+Component346 -- Component347
+Component45 -- Component260
+Component962 -- Component33
+Component693 -> Component197
+Component308 -- Component622
+Component23 -> Component24
+Component625 -- Component402
+Component437 -> Component114
+Component186 -- Component586
+Component93 -- Component96
+Component830 -- Component485
+Component177 -> Component323
+Component727 -> Component725
+Component519 -> Component669
+Component213 -- Component611
+Component500 -- Component763
+Component638 -> Component337
+Component5 -> Component622
+Component814 -> Component914
+Component699 -> Component337
+Component472 -> Component262
+Component105 -> Component108
+Component519 -> Component575
+Component122 -> Component641
+Component27 -> Component683
+Component30 -> Component368
+Component947 -> Component361
+Component473 -> Component551
+Component195 -- Component865
+Component758 -> Component781
+Component988 -- Component322
+Component292 -- Component562
+Component94 -> Component179
+Component769 -- Component428
+Component813 -- Component177
+Component957 -> Component952
+Component361 -> Component268
+Component982 -- Component600
+Component771 -- Component657
+Component2 -> Component345
+Component90 -- Component447
+Component583 -> Component289
+Component478 -- Component980
+Component712 -- Component343
+Component203 -- Component449
+Component871 -> Component245
+Component446 -> Component557
+Component371 -- Component359
+Component664 -- Component272
+Component130 -> Component795
+Component383 -> Component371
+Component743 -> Component357
+Component949 -- Component591
+Component224 -> Component809
+Component401 -- Component928
+Component697 -- Component52
+Component523 -> Component549
+Component73 -> Component447
+Component39 -- Component682
+Component406 -- Component180
+Component653 -- Component605
+Component287 -> Component464
+Component600 -- Component794
+Component625 -> Component881
+Component402 -> Component53
+Component991 -- Component287
+Component267 -- Component157
+Component935 -- Component182
+Component368 -> Component689
+Component446 -> Component623
+Component95 -> Component969
+Component853 -> Component313
+Component227 -> Component415
+Component70 -- Component535
+Component877 -- Component64
+Component394 -> Component905
+Component811 -> Component339
+Component828 -- Component15
+Component4 -> Component752
+Component453 -> Component49
+Component604 -> Component990
+Component295 -- Component723
+Component562 -- Component691
+Component45 -- Component472
+Component284 -- Component226
+Component488 -- Component236
+Component542 -- Component143
+Component199 -> Component149
+Component629 -> Component805
+Component251 -- Component13
+Component44 -- Component301
+Component949 -> Component961
+Component187 -- Component713
+Component295 -- Component477
+Component284 -- Component505
+Component430 -- Component61
+Component504 -- Component579
+Component928 -- Component585
+Component864 -- Component173
+Component446 -> Component141
+Component471 -- Component749
+Component252 -> Component631
+Component520 -> Component375
+Component711 -> Component337
+Component360 -> Component985
+Component425 -- Component916
+Component288 -> Component466
+Component789 -> Component740
+Component235 -- Component578
+Component126 -- Component959
+Component800 -- Component367
+Component926 -> Component626
+Component19 -- Component799
+Component711 -> Component119
+Component31 -- Component805
+Component962 -> Component268
+Component254 -- Component192
+Component129 -> Component374
+Component173 -> Component244
+Component699 -- Component508
+Component767 -> Component372
+Component491 -> Component357
+Component301 -> Component935
+Component648 -- Component899
+Component748 -> Component97
+Component140 -> Component901
+Component503 -> Component68
+Component28 -- Component646
+Component38 -- Component713
+Component109 -- Component540
+Component186 -> Component519
+Component478 -- Component507
+Component885 -- Component794
+Component929 -- Component165
+Component207 -- Component615
+Component198 -- Component434
+Component919 -- Component880
+Component668 -> Component164
+Component1 -- Component792
+Component17 -- Component22
+Component215 -- Component796
+Component118 -> Component670
+Component480 -> Component506
+Component403 -- Component709
+Component864 -- Component180
+Component316 -> Component167
+Component652 -- Component916
+Component86 -- Component250
+Component664 -> Component26
+
+[Note] (2.94, 40.45) Note 0: Important information here
+[Note] (52.51, 36.04) Note 1: Important information here
+[Note] (58.98, 66.79) Note 2: Important information here
+[Note] (78.33, 13.50) Note 3: Important information here
+[Note] (79.45, 45.42) Note 4: Important information here
+[Note] (73.48, 17.13) Note 5: Important information here
+[Note] (50.74, 42.61) Note 6: Important information here
+[Note] (78.16, 67.95) Note 7: Important information here
+[Note] (39.81, 97.76) Note 8: Important information here
+[Note] (20.44, 8.41) Note 9: Important information here
+[Note] (57.71, 68.13) Note 10: Important information here
+[Note] (4.50, 43.31) Note 11: Important information here
+[Note] (81.32, 0.57) Note 12: Important information here
+[Note] (28.05, 35.60) Note 13: Important information here
+[Note] (32.09, 5.25) Note 14: Important information here
+[Note] (15.56, 33.99) Note 15: Important information here
+[Note] (6.30, 36.55) Note 16: Important information here
+[Note] (26.69, 87.57) Note 17: Important information here
+[Note] (84.58, 54.27) Note 18: Important information here
+[Note] (51.43, 97.01) Note 19: Important information here
+[Note] (52.32, 80.60) Note 20: Important information here
+[Note] (25.39, 94.75) Note 21: Important information here
+[Note] (14.65, 36.06) Note 22: Important information here
+[Note] (33.20, 19.14) Note 23: Important information here
+[Note] (61.89, 14.32) Note 24: Important information here
+[Note] (19.45, 14.75) Note 25: Important information here
+[Note] (72.54, 34.44) Note 26: Important information here
+[Note] (30.25, 60.76) Note 27: Important information here
+[Note] (97.08, 95.72) Note 28: Important information here
+[Note] (36.72, 82.06) Note 29: Important information here
+[Note] (66.76, 96.51) Note 30: Important information here
+[Note] (44.97, 98.71) Note 31: Important information here
+[Note] (33.53, 71.85) Note 32: Important information here
+[Note] (1.12, 36.27) Note 33: Important information here
+[Note] (93.86, 41.54) Note 34: Important information here
+[Note] (45.49, 97.58) Note 35: Important information here
+[Note] (11.85, 27.02) Note 36: Important information here
+[Note] (22.05, 41.78) Note 37: Important information here
+[Note] (24.21, 47.46) Note 38: Important information here
+[Note] (35.59, 77.67) Note 39: Important information here
+[Note] (60.38, 33.28) Note 40: Important information here
+[Note] (11.99, 57.45) Note 41: Important information here
+[Note] (36.12, 68.56) Note 42: Important information here
+[Note] (12.09, 2.53) Note 43: Important information here
+[Note] (76.81, 69.61) Note 44: Important information here
+[Note] (90.95, 51.34) Note 45: Important information here
+[Note] (62.74, 25.63) Note 46: Important information here
+[Note] (23.00, 12.92) Note 47: Important information here
+[Note] (89.56, 11.80) Note 48: Important information here
+[Note] (91.20, 54.14) Note 49: Important information here
+[Note] (9.82, 29.43) Note 50: Important information here
+[Note] (39.10, 54.60) Note 51: Important information here
+[Note] (18.37, 26.61) Note 52: Important information here
+[Note] (73.22, 5.28) Note 53: Important information here
+[Note] (48.00, 29.66) Note 54: Important information here
+[Note] (29.62, 96.82) Note 55: Important information here
+[Note] (43.04, 99.15) Note 56: Important information here
+[Note] (35.35, 59.90) Note 57: Important information here
+[Note] (14.89, 41.05) Note 58: Important information here
+[Note] (63.43, 25.91) Note 59: Important information here
+[Note] (8.89, 85.37) Note 60: Important information here
+[Note] (40.78, 28.82) Note 61: Important information here
+[Note] (45.40, 95.24) Note 62: Important information here
+[Note] (38.42, 69.68) Note 63: Important information here
+[Note] (98.58, 97.72) Note 64: Important information here
+[Note] (55.42, 94.26) Note 65: Important information here
+[Note] (79.78, 9.46) Note 66: Important information here
+[Note] (74.35, 80.17) Note 67: Important information here
+[Note] (51.98, 69.94) Note 68: Important information here
+[Note] (10.33, 99.47) Note 69: Important information here
+[Note] (49.91, 26.62) Note 70: Important information here
+[Note] (99.67, 89.15) Note 71: Important information here
+[Note] (83.91, 73.43) Note 72: Important information here
+[Note] (96.77, 80.50) Note 73: Important information here
+[Note] (79.78, 29.10) Note 74: Important information here
+[Note] (73.99, 22.14) Note 75: Important information here
+[Note] (0.40, 71.89) Note 76: Important information here
+[Note] (96.44, 0.13) Note 77: Important information here
+[Note] (37.22, 36.88) Note 78: Important information here
+[Note] (49.86, 14.12) Note 79: Important information here
+[Note] (54.71, 0.77) Note 80: Important information here
+[Note] (91.08, 10.71) Note 81: Important information here
+[Note] (82.91, 82.87) Note 82: Important information here
+[Note] (16.74, 3.24) Note 83: Important information here
+[Note] (72.61, 55.76) Note 84: Important information here
+[Note] (11.39, 18.81) Note 85: Important information here
+[Note] (95.79, 22.32) Note 86: Important information here
+[Note] (2.01, 62.41) Note 87: Important information here
+[Note] (13.64, 62.81) Note 88: Important information here
+[Note] (57.72, 29.73) Note 89: Important information here
+[Note] (75.37, 73.59) Note 90: Important information here
+[Note] (61.03, 3.80) Note 91: Important information here
+[Note] (48.59, 37.15) Note 92: Important information here
+[Note] (22.25, 75.74) Note 93: Important information here
+[Note] (93.34, 15.31) Note 94: Important information here
+[Note] (29.08, 62.29) Note 95: Important information here
+[Note] (90.50, 87.93) Note 96: Important information here
+[Note] (16.57, 55.43) Note 97: Important information here
+[Note] (94.92, 72.00) Note 98: Important information here
+[Note] (76.78, 69.76) Note 99: Important information here
+
+[Group] Component166, Component358, Component162, Component735, Component888, Component887
+[Group] Component813, Component533, Component171, Component890, Component903, Component427, Component946, Component701, Component485
+[Group] Component361, Component442, Component616, Component723, Component141
+[Group] Component928, Component96, Component157, Component210, Component556
+[Group] Component640, Component776, Component24, Component77, Component122, Component770, Component749, Component138, Component885
+[Group] Component321, Component517, Component33, Component560, Component553, Component851, Component109, Component798, Component164
+[Group] Component750, Component439, Component500, Component242, Component758
+[Group] Component631, Component186, Component77, Component263, Component342, Component988, Component55, Component868
+[Group] Component700, Component735, Component71, Component491, Component265, Component584, Component703, Component858
+[Group] Component34, Component461, Component400, Component364, Component53
+[Group] Component328, Component186, Component561, Component769, Component403, Component477, Component319, Component319, Component512
+[Group] Component773, Component847, Component838, Component153, Component187, Component545, Component450, Component125, Component610, Component328
+[Group] Component7, Component340, Component456, Component183, Component990, Component303, Component767
+[Group] Component777, Component607, Component466, Component751, Component848, Component803, Component994, Component561
+[Group] Component773, Component278, Component623, Component201, Component281
+[Group] Component357, Component963, Component582, Component828, Component733, Component258, Component904, Component261, Component277, Component949
+[Group] Component906, Component471, Component230, Component135, Component370, Component930, Component74, Component177, Component389
+[Group] Component836, Component154, Component590, Component2, Component459, Component732, Component512, Component759, Component760, Component840
+[Group] Component625, Component499, Component9, Component58, Component259, Component602, Component488, Component176, Component221, Component781
+[Group] Component202, Component212, Component576, Component111, Component612, Component856, Component916
+[Group] Component517, Component296, Component112, Component53, Component760, Component856, Component209, Component31, Component638
+[Group] Component237, Component717, Component314, Component724, Component896, Component946, Component228, Component563, Component994, Component18
+[Group] Component102, Component958, Component317, Component552, Component821, Component802, Component725, Component856, Component878, Component922
+[Group] Component638, Component280, Component163, Component527, Component205, Component74, Component753, Component170, Component172, Component610
+[Group] Component885, Component857, Component835, Component834, Component599, Component965
+[Group] Component355, Component341, Component305, Component329, Component705, Component665
+[Group] Component933, Component454, Component769, Component783, Component454, Component118, Component616
+[Group] Component659, Component141, Component574, Component505, Component259, Component538, Component250, Component383, Component374, Component948
+[Group] Component22, Component487, Component234, Component640, Component450, Component55
+[Group] Component110, Component166, Component875, Component745, Component109
+[Group] Component536, Component346, Component210, Component131, Component732, Component786, Component506, Component834, Component773, Component285
+[Group] Component562, Component401, Component560, Component186, Component710, Component41, Component519, Component902, Component489, Component304
+[Group] Component895, Component503, Component939, Component501, Component255
+[Group] Component344, Component39, Component167, Component74, Component680, Component962, Component789, Component528, Component199
+[Group] Component24, Component917, Component390, Component67, Component184, Component502, Component131
+[Group] Component146, Component868, Component949, Component619, Component382
+[Group] Component263, Component544, Component629, Component77, Component983, Component303
+[Group] Component873, Component161, Component398, Component672, Component847, Component834, Component364
+[Group] Component78, Component511, Component14, Component998, Component276
+[Group] Component161, Component681, Component53, Component139, Component606
+[Group] Component651, Component241, Component157, Component273, Component316, Component233, Component725, Component317
+[Group] Component347, Component471, Component365, Component748, Component266, Component849, Component868, Component478, Component320
+[Group] Component702, Component625, Component547, Component230, Component791, Component492, Component484
+[Group] Component294, Component37, Component146, Component889, Component343, Component761, Component100
+[Group] Component62, Component902, Component833, Component184, Component124, Component322, Component346, Component874, Component785
+[Group] Component838, Component501, Component979, Component48, Component404, Component24, Component644, Component759, Component138, Component470
+[Group] Component764, Component133, Component294, Component987, Component134, Component160, Component60
+[Group] Component916, Component310, Component269, Component134, Component967, Component418, Component946, Component333
+[Group] Component983, Component468, Component392, Component878, Component42, Component643, Component428, Component967, Component559, Component133
+[Group] Component928, Component925, Component32, Component478, Component979, Component488, Component674
+
+[Inertia] Component269
+[Inertia] Component778
+[Inertia] Component942
+[Inertia] Component159
+[Inertia] Component579
+[Inertia] Component156
+[Inertia] Component864
+[Inertia] Component25
+[Inertia] Component940
+[Inertia] Component571
+[Inertia] Component124
+[Inertia] Component206
+[Inertia] Component353
+[Inertia] Component695
+[Inertia] Component549
+[Inertia] Component118
+[Inertia] Component914
+[Inertia] Component67
+[Inertia] Component73
+[Inertia] Component688
+[Inertia] Component413
+[Inertia] Component828
+[Inertia] Component720
+[Inertia] Component31
+[Inertia] Component203
+[Inertia] Component835
+[Inertia] Component73
+[Inertia] Component770
+[Inertia] Component340
+[Inertia] Component608
+[Inertia] Component82
+[Inertia] Component349
+[Inertia] Component972
+[Inertia] Component63
+[Inertia] Component30
+[Inertia] Component392
+[Inertia] Component455
+[Inertia] Component910
+[Inertia] Component76
+[Inertia] Component843
+[Inertia] Component120
+[Inertia] Component831
+[Inertia] Component555
+[Inertia] Component531
+[Inertia] Component850
+[Inertia] Component991
+[Inertia] Component789
+[Inertia] Component834
+[Inertia] Component341
+[Inertia] Component611
+[Inertia] Component990
+[Inertia] Component599
+[Inertia] Component872
+[Inertia] Component44
+[Inertia] Component574
+[Inertia] Component727
+[Inertia] Component525
+[Inertia] Component73
+[Inertia] Component539
+[Inertia] Component596
+[Inertia] Component458
+[Inertia] Component982
+[Inertia] Component1
+[Inertia] Component604
+[Inertia] Component573
+[Inertia] Component208
+[Inertia] Component317
+[Inertia] Component815
+[Inertia] Component81
+[Inertia] Component703
+[Inertia] Component949
+[Inertia] Component634
+[Inertia] Component651
+[Inertia] Component687
+[Inertia] Component430
+[Inertia] Component407
+[Inertia] Component802
+[Inertia] Component716
+[Inertia] Component348
+[Inertia] Component769
+[Inertia] Component684
+[Inertia] Component260
+[Inertia] Component52
+[Inertia] Component707
+[Inertia] Component99
+[Inertia] Component100
+[Inertia] Component776
+[Inertia] Component104
+[Inertia] Component489
+[Inertia] Component702
+[Inertia] Component923
+[Inertia] Component927
+[Inertia] Component409
+[Inertia] Component149
+[Inertia] Component816
+[Inertia] Component170
+[Inertia] Component953
+[Inertia] Component816
+[Inertia] Component83
+[Inertia] Component65
+
+[Evolution] Component902 + 9.56
+[Evolution] Component565 + 6.91
+[Evolution] Component691 + 46.82
+[Evolution] Component502 - 45.86
+[Evolution] Component298 - 11.79
+[Evolution] Component902 - 29.01
+[Evolution] Component313 + 20.43
+[Evolution] Component452 - 21.03
+[Evolution] Component489 + 42.37
+[Evolution] Component59 + 26.46
+[Evolution] Component124 - 48.39
+[Evolution] Component920 - 47.99
+[Evolution] Component835 - 44.60
+[Evolution] Component408 + 9.26
+[Evolution] Component381 + 7.89
+[Evolution] Component822 + 37.66
+[Evolution] Component244 + 3.26
+[Evolution] Component294 + 38.15
+[Evolution] Component985 - 14.38
+[Evolution] Component810 + 3.40
+[Evolution] Component0 - 19.05
+[Evolution] Component976 + 6.90
+[Evolution] Component516 + 30.13
+[Evolution] Component193 + 10.17
+[Evolution] Component653 - 28.96
+[Evolution] Component29 + 48.14
+[Evolution] Component810 + 6.28
+[Evolution] Component617 + 2.99
+[Evolution] Component228 + 35.90
+[Evolution] Component164 + 33.70
+[Evolution] Component297 - 42.43
+[Evolution] Component42 + 47.32
+[Evolution] Component752 + 21.97
+[Evolution] Component930 - 38.23
+[Evolution] Component305 - 49.76
+[Evolution] Component748 + 41.84
+[Evolution] Component2 - 41.73
+[Evolution] Component524 + 6.93
+[Evolution] Component350 + 36.75
+[Evolution] Component129 + 24.85
+[Evolution] Component934 + 42.24
+[Evolution] Component568 - 45.29
+[Evolution] Component18 + 27.95
+[Evolution] Component125 + 42.13
+[Evolution] Component843 - 25.92
+[Evolution] Component502 - 7.53
+[Evolution] Component845 + 41.54
+[Evolution] Component269 + 47.41
+[Evolution] Component440 + 23.12
+[Evolution] Component370 - 14.15
+[Evolution] Component885 + 27.46
+[Evolution] Component512 - 18.65
+[Evolution] Component313 - 41.73
+[Evolution] Component173 + 21.42
+[Evolution] Component182 + 32.77
+[Evolution] Component681 + 0.34
+[Evolution] Component71 - 0.81
+[Evolution] Component256 + 30.35
+[Evolution] Component380 - 30.76
+[Evolution] Component790 - 4.48
+[Evolution] Component329 - 8.79
+[Evolution] Component456 - 22.37
+[Evolution] Component829 - 40.43
+[Evolution] Component586 + 20.49
+[Evolution] Component918 - 13.15
+[Evolution] Component638 + 22.34
+[Evolution] Component41 + 31.82
+[Evolution] Component958 - 26.38
+[Evolution] Component43 - 19.16
+[Evolution] Component287 + 19.71
+[Evolution] Component33 - 17.17
+[Evolution] Component826 - 44.03
+[Evolution] Component290 + 24.18
+[Evolution] Component17 - 4.30
+[Evolution] Component550 + 3.36
+[Evolution] Component262 - 29.09
+[Evolution] Component472 + 10.38
+[Evolution] Component792 - 2.78
+[Evolution] Component79 + 20.09
+[Evolution] Component695 + 36.78
+[Evolution] Component413 + 7.77
+[Evolution] Component937 + 41.23
+[Evolution] Component749 + 39.04
+[Evolution] Component970 + 37.45
+[Evolution] Component748 - 10.69
+[Evolution] Component16 - 19.91
+[Evolution] Component10 + 2.90
+[Evolution] Component337 + 21.52
+[Evolution] Component190 + 13.97
+[Evolution] Component773 + 25.37
+[Evolution] Component16 - 41.09
+[Evolution] Component387 + 46.74
+[Evolution] Component355 - 46.48
+[Evolution] Component511 - 26.32
+[Evolution] Component4 + 36.69
+[Evolution] Component811 + 30.11
+[Evolution] Component885 - 28.46
+[Evolution] Component854 - 41.72
+[Evolution] Component493 + 40.34
+[Evolution] Component541 - 6.05 \ No newline at end of file
diff --git a/src/configuration.rs b/src/configuration.rs
new file mode 100644
index 0000000..775d6cd
--- /dev/null
+++ b/src/configuration.rs
@@ -0,0 +1,195 @@
+/// Configuration options for rendering
+#[derive(Debug, Clone)]
+pub struct Options {
+ pub show_background: bool,
+ pub smart_label_positioning: bool,
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Self {
+ show_background: true,
+ smart_label_positioning: true,
+ }
+ }
+}
+
+/// Color configuration
+#[derive(Debug, Clone)]
+pub struct Colors {
+ pub background: String,
+ pub axis: String,
+ pub vertex: String,
+ pub label: String,
+ pub stage_foreground: String,
+ pub stage_background: String,
+ pub inertia: String,
+ pub evolution: String,
+ pub groups: Vec<String>,
+}
+
+impl Default for Colors {
+ fn default() -> Self {
+ Self {
+ background: "#FFFFFF".to_string(),
+ axis: "#0F261F".to_string(),
+ vertex: "#0F261F".to_string(),
+ label: "#0F261F".to_string(),
+ stage_foreground: "#DAE6E3".to_string(),
+ stage_background: "#FFFFFF".to_string(),
+ inertia: "#FA2B00".to_string(),
+ evolution: "#4F8FE6".to_string(),
+ groups: vec![
+ "#3A5BA0".to_string(), // Olympic Blue
+ "#C44536".to_string(), // Jasper Red
+ "#8FBE92".to_string(), // Light Porcelain Green
+ "#FAD55C".to_string(), // Naples Yellow
+ "#EC9CAB".to_string(), // Hermosa Pink
+ ],
+ }
+ }
+}
+
+/// Size configuration
+#[derive(Debug, Clone)]
+pub struct Sizes {
+ pub map_width: f64,
+ pub map_height: f64,
+ pub padding: f64,
+ pub bottom_padding: f64,
+ pub line_width: f64,
+ pub vertex_size: f64,
+ pub arrowhead_size: f64,
+ pub stage_height: f64,
+}
+
+impl Default for Sizes {
+ fn default() -> Self {
+ Self {
+ map_width: 1300.0,
+ map_height: 1000.0,
+ padding: 42.0,
+ bottom_padding: 120.0,
+ line_width: 0.5,
+ vertex_size: 25.0,
+ arrowhead_size: 10.0,
+ stage_height: 100.0,
+ }
+ }
+}
+
+/// Font configuration
+#[derive(Debug, Clone)]
+pub struct Fonts {
+ pub family: String,
+ pub axis_label: f64,
+ pub vertex_label: f64,
+ pub note: f64,
+}
+
+impl Default for Fonts {
+ fn default() -> Self {
+ Self {
+ family: "Helvetica Neue, Helvetica, Arial, sans-serif".to_string(),
+ axis_label: 14.0,
+ vertex_label: 12.0,
+ note: 12.0,
+ }
+ }
+}
+
+/// Line height configuration
+#[derive(Debug, Clone)]
+pub struct LineHeights {
+ pub vertex_label: f64,
+ pub note: f64,
+}
+
+impl Default for LineHeights {
+ fn default() -> Self {
+ Self {
+ vertex_label: 18.0,
+ note: 18.0,
+ }
+ }
+}
+
+/// Opacity configuration
+#[derive(Debug, Clone)]
+pub struct Opacity {
+ pub groups: f64,
+}
+
+impl Default for Opacity {
+ fn default() -> Self {
+ Self { groups: 0.1 }
+ }
+}
+
+/// Theme configuration
+#[derive(Debug, Clone, Default)]
+pub struct Theme {
+ pub colors: Colors,
+ pub sizes: Sizes,
+ pub fonts: Fonts,
+ pub line_heights: LineHeights,
+ pub opacity: Opacity,
+}
+
+/// Main configuration struct
+#[derive(Debug, Clone, Default)]
+pub struct Configuration {
+ pub options: Options,
+ pub theme: Theme,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_default_configuration() {
+ let config = Configuration::default();
+
+ // Test options
+ assert!(config.options.show_background);
+ assert!(config.options.smart_label_positioning);
+
+ // Test colors (matching JS implementation exactly)
+ assert_eq!(config.theme.colors.background, "#FFFFFF");
+ assert_eq!(config.theme.colors.axis, "#0F261F");
+ assert_eq!(config.theme.colors.vertex, "#0F261F");
+ assert_eq!(config.theme.colors.label, "#0F261F");
+ assert_eq!(config.theme.colors.stage_foreground, "#DAE6E3");
+ assert_eq!(config.theme.colors.stage_background, "#FFFFFF");
+ assert_eq!(config.theme.colors.inertia, "#FA2B00");
+ assert_eq!(config.theme.colors.evolution, "#4F8FE6");
+ assert_eq!(config.theme.colors.groups.len(), 5);
+
+ // Test sizes (matching JS implementation with extra bottom padding for wrapped labels)
+ assert_eq!(config.theme.sizes.map_width, 1300.0);
+ assert_eq!(config.theme.sizes.map_height, 1000.0);
+ assert_eq!(config.theme.sizes.padding, 42.0);
+ assert_eq!(config.theme.sizes.bottom_padding, 120.0);
+ assert_eq!(config.theme.sizes.line_width, 0.5);
+ assert_eq!(config.theme.sizes.vertex_size, 25.0);
+ assert_eq!(config.theme.sizes.arrowhead_size, 10.0);
+ assert_eq!(config.theme.sizes.stage_height, 100.0);
+
+ // Test fonts
+ assert_eq!(
+ config.theme.fonts.family,
+ "Helvetica Neue, Helvetica, Arial, sans-serif"
+ );
+ assert_eq!(config.theme.fonts.axis_label, 14.0);
+ assert_eq!(config.theme.fonts.vertex_label, 12.0);
+ assert_eq!(config.theme.fonts.note, 12.0);
+
+ // Test line heights
+ assert_eq!(config.theme.line_heights.vertex_label, 18.0);
+ assert_eq!(config.theme.line_heights.note, 18.0);
+
+ // Test opacity
+ assert_eq!(config.theme.opacity.groups, 0.1);
+ }
+}
diff --git a/src/geometry.rs b/src/geometry.rs
new file mode 100644
index 0000000..5099ada
--- /dev/null
+++ b/src/geometry.rs
@@ -0,0 +1,142 @@
+/// Represents a line segment
+#[derive(Debug, Clone, Copy)]
+pub struct Line {
+ pub start: (f64, f64),
+ pub end: (f64, f64),
+}
+
+/// Represents a rectangle
+#[derive(Debug, Clone, Copy)]
+pub struct Rect {
+ pub x: f64,
+ pub y: f64,
+ pub width: f64,
+ pub height: f64,
+}
+
+impl Line {
+ pub fn new(start: (f64, f64), end: (f64, f64)) -> Self {
+ Self { start, end }
+ }
+}
+
+impl Rect {
+ pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
+ Self {
+ x,
+ y,
+ width,
+ height,
+ }
+ }
+}
+
+/// Checks if a point is inside a rectangle
+pub fn point_in_rect(point: (f64, f64), rect: &Rect) -> bool {
+ point.0 >= rect.x
+ && point.0 <= rect.x + rect.width
+ && point.1 >= rect.y
+ && point.1 <= rect.y + rect.height
+}
+
+/// Checks if two rectangles intersect (AABB collision)
+pub fn rect_intersects_rect(r1: &Rect, r2: &Rect) -> bool {
+ !(r1.x + r1.width < r2.x
+ || r2.x + r2.width < r1.x
+ || r1.y + r1.height < r2.y
+ || r2.y + r2.height < r1.y)
+}
+
+/// Checks if two line segments intersect using parametric equations
+pub fn line_intersects_line(l1: &Line, l2: &Line) -> bool {
+ let x1 = l1.start.0;
+ let y1 = l1.start.1;
+ let x2 = l1.end.0;
+ let y2 = l1.end.1;
+ let x3 = l2.start.0;
+ let y3 = l2.start.1;
+ let x4 = l2.end.0;
+ let y4 = l2.end.1;
+
+ let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if denom.abs() < 1e-10 {
+ return false; // Parallel or coincident
+ }
+
+ let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
+ let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
+
+ (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u)
+}
+
+/// Checks if a line segment intersects a rectangle
+pub fn line_intersects_rect(line: &Line, rect: &Rect) -> bool {
+ // Check if either endpoint is inside the rectangle
+ if point_in_rect(line.start, rect) || point_in_rect(line.end, rect) {
+ return true;
+ }
+
+ // Check intersection with all four edges
+ let edges = [
+ Line::new((rect.x, rect.y), (rect.x + rect.width, rect.y)), // Top
+ Line::new(
+ (rect.x + rect.width, rect.y),
+ (rect.x + rect.width, rect.y + rect.height),
+ ), // Right
+ Line::new(
+ (rect.x + rect.width, rect.y + rect.height),
+ (rect.x, rect.y + rect.height),
+ ), // Bottom
+ Line::new((rect.x, rect.y + rect.height), (rect.x, rect.y)), // Left
+ ];
+
+ edges.iter().any(|edge| line_intersects_line(line, edge))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_point_in_rect() {
+ let rect = Rect::new(10.0, 10.0, 20.0, 20.0);
+ assert!(point_in_rect((15.0, 15.0), &rect));
+ assert!(point_in_rect((10.0, 10.0), &rect));
+ assert!(point_in_rect((30.0, 30.0), &rect));
+ assert!(!point_in_rect((5.0, 15.0), &rect));
+ assert!(!point_in_rect((35.0, 15.0), &rect));
+ }
+
+ #[test]
+ fn test_rect_intersects_rect() {
+ let r1 = Rect::new(0.0, 0.0, 10.0, 10.0);
+ let r2 = Rect::new(5.0, 5.0, 10.0, 10.0);
+ let r3 = Rect::new(20.0, 20.0, 10.0, 10.0);
+
+ assert!(rect_intersects_rect(&r1, &r2));
+ assert!(rect_intersects_rect(&r2, &r1));
+ assert!(!rect_intersects_rect(&r1, &r3));
+ }
+
+ #[test]
+ fn test_line_intersects_line() {
+ let l1 = Line::new((0.0, 0.0), (10.0, 10.0));
+ let l2 = Line::new((0.0, 10.0), (10.0, 0.0));
+ let l3 = Line::new((20.0, 20.0), (30.0, 30.0));
+
+ assert!(line_intersects_line(&l1, &l2));
+ assert!(!line_intersects_line(&l1, &l3));
+ }
+
+ #[test]
+ fn test_line_intersects_rect() {
+ let rect = Rect::new(10.0, 10.0, 20.0, 20.0);
+ let l1 = Line::new((0.0, 15.0), (40.0, 15.0)); // Horizontal through
+ let l2 = Line::new((15.0, 0.0), (15.0, 40.0)); // Vertical through
+ let l3 = Line::new((0.0, 0.0), (5.0, 5.0)); // Outside
+
+ assert!(line_intersects_rect(&l1, &rect));
+ assert!(line_intersects_rect(&l2, &rect));
+ assert!(!line_intersects_rect(&l3, &rect));
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b56c670
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,105 @@
+mod configuration;
+mod geometry;
+mod patterns;
+mod renderer;
+mod shapes;
+mod smart_positioning;
+mod stage_type;
+mod utils;
+
+// Re-export parser types
+pub use wmap_parser::{
+ Component, Dependency, Evolution, Group, Inertia, Map, Note, Shape, Stage, StageData,
+};
+
+// Re-export our types
+pub use configuration::{
+ Colors, Configuration, Fonts, LineHeights, Opacity, Options, Sizes, Theme,
+};
+pub use stage_type::StageType;
+
+use thiserror::Error;
+
+/// Error types for rendering
+#[derive(Debug, Error)]
+pub enum RenderError {
+ #[error("Cairo rendering error: {0}")]
+ Cairo(String),
+
+ #[error("Invalid configuration: {0}")]
+ InvalidConfig(String),
+
+ #[error("Concave hull calculation failed")]
+ GeometryError,
+
+ #[error("IO error: {0}")]
+ Io(#[from] std::io::Error),
+}
+
+impl From<cairo::Error> for RenderError {
+ fn from(err: cairo::Error) -> Self {
+ RenderError::Cairo(err.to_string())
+ }
+}
+
+impl From<cairo::IoError> for RenderError {
+ fn from(err: cairo::IoError) -> Self {
+ RenderError::Cairo(err.to_string())
+ }
+}
+
+/// Renders a Wardley map to PNG format
+///
+/// # Arguments
+///
+/// * `map` - The parsed Wardley map
+/// * `stage_type` - The stage type to use for axis labels
+/// * `config` - Configuration options
+///
+/// # Returns
+///
+/// A vector of bytes containing the PNG image data
+pub fn render_to_png(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<Vec<u8>, RenderError> {
+ renderer::render_to_png(map, stage_type, config)
+}
+
+/// Renders a Wardley map to SVG format
+///
+/// # Arguments
+///
+/// * `map` - The parsed Wardley map
+/// * `stage_type` - The stage type to use for axis labels
+/// * `config` - Configuration options
+///
+/// # Returns
+///
+/// A string containing the SVG data
+pub fn render_to_svg(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<String, RenderError> {
+ renderer::render_to_svg(map, stage_type, config)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_default_configuration() {
+ let config = Configuration::default();
+ assert!(config.options.show_background);
+ assert!(config.options.smart_label_positioning);
+ }
+
+ #[test]
+ fn test_default_stage_type() {
+ let stage = StageType::default();
+ assert_eq!(stage, StageType::Activities);
+ }
+}
diff --git a/src/patterns.rs b/src/patterns.rs
new file mode 100644
index 0000000..12c790a
--- /dev/null
+++ b/src/patterns.rs
@@ -0,0 +1,77 @@
+use crate::utils::parse_color;
+
+/// 8x8 1-bit pattern data
+/// 0 = foreground color, 1 = background color
+pub const STITCH: [u8; 64] = [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+];
+
+pub const SHINGLES: [u8; 64] = [
+ 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1,
+];
+
+pub const SHADOW_GRID: [u8; 64] = [
+ 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0,
+];
+
+pub const WICKER: [u8; 64] = [
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+];
+
+/// Creates a cairo surface pattern from pattern data
+pub fn create_pattern(
+ pattern_data: &[u8; 64],
+ foreground: &str,
+ background: &str,
+) -> Result<cairo::SurfacePattern, cairo::Error> {
+ let surface = cairo::ImageSurface::create(cairo::Format::Rgb24, 8, 8)?;
+
+ {
+ let ctx = cairo::Context::new(&surface)?;
+ let fg = parse_color(foreground);
+ let bg = parse_color(background);
+
+ for y in 0..8 {
+ for x in 0..8 {
+ let value = pattern_data[y * 8 + x];
+ let (r, g, b) = if value == 0 { fg } else { bg };
+
+ ctx.set_source_rgb(r, g, b);
+ ctx.rectangle(x as f64, y as f64, 1.0, 1.0);
+ ctx.fill()?;
+ }
+ }
+ }
+
+ let pattern = cairo::SurfacePattern::create(&surface);
+ pattern.set_extend(cairo::Extend::Repeat);
+ Ok(pattern)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_pattern_data_lengths() {
+ assert_eq!(STITCH.len(), 64);
+ assert_eq!(SHINGLES.len(), 64);
+ assert_eq!(SHADOW_GRID.len(), 64);
+ assert_eq!(WICKER.len(), 64);
+ }
+
+ #[test]
+ fn test_pattern_values() {
+ // All pattern values should be 0 or 1
+ for &val in &STITCH {
+ assert!(val == 0 || val == 1);
+ }
+ for &val in &SHINGLES {
+ assert!(val == 0 || val == 1);
+ }
+ }
+}
diff --git a/src/renderer.rs b/src/renderer.rs
new file mode 100644
index 0000000..9991d56
--- /dev/null
+++ b/src/renderer.rs
@@ -0,0 +1,823 @@
+use std::io::Cursor;
+use wmap_parser::Map;
+
+use crate::configuration::Configuration;
+use crate::stage_type::StageType;
+use crate::utils::{percent_to_pixel, set_color};
+use crate::RenderError;
+
+/// Renders a map to PNG format
+pub fn render_to_png(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<Vec<u8>, RenderError> {
+ let total_width = (config.theme.sizes.map_width + config.theme.sizes.padding * 2.0) as i32;
+ let total_height = (config.theme.sizes.map_height
+ + config.theme.sizes.padding
+ + config.theme.sizes.bottom_padding) as i32;
+
+ let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, total_width, total_height)?;
+ let ctx = cairo::Context::new(&surface)?;
+
+ render(&ctx, map, stage_type, config)?;
+
+ let mut buffer = Cursor::new(Vec::new());
+ surface.write_to_png(&mut buffer)?;
+ Ok(buffer.into_inner())
+}
+
+/// Renders a map to SVG format
+pub fn render_to_svg(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<String, RenderError> {
+ use std::fs;
+ use tempfile::NamedTempFile;
+
+ let total_width = config.theme.sizes.map_width + config.theme.sizes.padding * 2.0;
+ let total_height = config.theme.sizes.map_height
+ + config.theme.sizes.padding
+ + config.theme.sizes.bottom_padding;
+
+ let temp_file = NamedTempFile::new()?;
+ let path = temp_file
+ .path()
+ .to_str()
+ .ok_or_else(|| RenderError::InvalidConfig("Failed to create temp file path".to_string()))?;
+
+ let surface = cairo::SvgSurface::new(total_width, total_height, Some(path))?;
+ let ctx = cairo::Context::new(&surface)?;
+
+ render(&ctx, map, stage_type, config)?;
+
+ drop(ctx);
+ surface.finish();
+
+ let svg_content = fs::read_to_string(path)?;
+ Ok(svg_content)
+}
+
+/// Core rendering function
+fn render(
+ ctx: &cairo::Context,
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ // Apply padding transform
+ ctx.save()?;
+ ctx.translate(config.theme.sizes.padding, config.theme.sizes.padding);
+
+ // Render in order (back to front)
+ draw_background(ctx, config)?;
+
+ if config.options.show_background {
+ draw_stage_patterns(ctx, stage_type, map, config)?;
+ }
+
+ draw_axes(ctx, stage_type, map, config)?;
+ draw_stage_dividers(ctx, map, config)?;
+ draw_dependencies(ctx, map, config)?;
+ draw_groups(ctx, map, config)?;
+ draw_vertices(ctx, map, config)?;
+ draw_vertex_labels(ctx, map, config)?;
+ draw_inertias(ctx, map, config)?;
+ draw_evolutions(ctx, map, config)?;
+ draw_notes(ctx, map, config)?;
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_background(ctx: &cairo::Context, config: &Configuration) -> Result<(), RenderError> {
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.background);
+ ctx.paint()?;
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_stage_patterns(
+ ctx: &cairo::Context,
+ _stage_type: StageType,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use crate::patterns::{create_pattern, SHADOW_GRID, SHINGLES, STITCH, WICKER};
+
+ let stages = get_stage_positions(map, config);
+ let patterns = [
+ create_pattern(
+ &STITCH,
+ &config.theme.colors.stage_foreground,
+ &config.theme.colors.stage_background,
+ )?,
+ create_pattern(
+ &SHINGLES,
+ &config.theme.colors.stage_foreground,
+ &config.theme.colors.stage_background,
+ )?,
+ create_pattern(
+ &SHADOW_GRID,
+ &config.theme.colors.stage_foreground,
+ &config.theme.colors.stage_background,
+ )?,
+ create_pattern(
+ &WICKER,
+ &config.theme.colors.stage_foreground,
+ &config.theme.colors.stage_background,
+ )?,
+ ];
+
+ ctx.save()?;
+
+ // Draw pattern for stage 0 to stage 1
+ ctx.rectangle(0.0, 0.0, stages[0], config.theme.sizes.map_height);
+ ctx.set_source(&patterns[0])?;
+ ctx.fill()?;
+
+ // Draw pattern for stage 1 to stage 2
+ ctx.rectangle(
+ stages[0],
+ 0.0,
+ stages[1] - stages[0],
+ config.theme.sizes.map_height,
+ );
+ ctx.set_source(&patterns[1])?;
+ ctx.fill()?;
+
+ // Draw pattern for stage 2 to stage 3
+ ctx.rectangle(
+ stages[1],
+ 0.0,
+ stages[2] - stages[1],
+ config.theme.sizes.map_height,
+ );
+ ctx.set_source(&patterns[2])?;
+ ctx.fill()?;
+
+ // Draw pattern for stage 3 to end
+ ctx.rectangle(
+ stages[2],
+ 0.0,
+ config.theme.sizes.map_width - stages[2],
+ config.theme.sizes.map_height,
+ );
+ ctx.set_source(&patterns[3])?;
+ ctx.fill()?;
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_axes(
+ ctx: &cairo::Context,
+ stage_type: StageType,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use std::f64::consts::PI;
+
+ let sizes = &config.theme.sizes;
+ let fonts = &config.theme.fonts;
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.axis);
+ ctx.set_line_width(sizes.line_width * 2.0);
+
+ // Y-axis and X-axis
+ ctx.move_to(0.0, 0.0);
+ ctx.line_to(0.0, sizes.map_height);
+ ctx.line_to(sizes.map_width, sizes.map_height);
+ ctx.stroke()?;
+
+ // Text setup
+ set_color(ctx, &config.theme.colors.label);
+ ctx.select_font_face(
+ "sans-serif",
+ cairo::FontSlant::Normal,
+ cairo::FontWeight::Normal,
+ );
+ ctx.set_font_size(fonts.axis_label);
+ ctx.set_line_width(1.0);
+
+ // Y-axis labels (rotated -90 degrees)
+ ctx.save()?;
+ ctx.translate(-20.0, 45.0);
+ ctx.rotate(-PI / 2.0);
+ ctx.move_to(0.0, 0.0);
+ ctx.show_text("Visible")?;
+ ctx.restore()?;
+
+ ctx.save()?;
+ ctx.translate(-20.0, sizes.map_height);
+ ctx.rotate(-PI / 2.0);
+ ctx.move_to(0.0, 0.0);
+ ctx.show_text("Invisible")?;
+ ctx.restore()?;
+
+ // X-axis labels (above the map, at negative Y, offset by half vertex height)
+ let stage_height = sizes.stage_height;
+ let text_offset_y = -stage_height / 4.0 + sizes.vertex_size / 2.0;
+ ctx.move_to(0.0, text_offset_y);
+ ctx.show_text("Uncharted")?;
+
+ let text_ext = ctx.text_extents("Industrialised")?;
+ ctx.move_to(sizes.map_width - text_ext.width(), text_offset_y);
+ ctx.show_text("Industrialised")?;
+
+ // Stage labels (below the map, offset by half vertex height, with text wrapping)
+ let stage_labels = stage_type.stages();
+ let stages = get_stage_positions(map, config);
+ let padding = 5.0; // kAxisLabelPadding
+ let stage_label_y = sizes.map_height + padding + sizes.vertex_size / 2.0;
+ let line_height = fonts.axis_label * 1.2; // 120% line height
+
+ // Calculate widths for each stage
+ let stage_widths = [
+ stages[0],
+ stages[1] - stages[0],
+ stages[2] - stages[1],
+ sizes.map_width - stages[2],
+ ];
+
+ // Draw each stage label with wrapping
+ for (i, &label) in stage_labels.iter().enumerate() {
+ let x = match i {
+ 0 => 0.0,
+ 1 => stages[0],
+ 2 => stages[1],
+ 3 => stages[2],
+ _ => continue,
+ };
+
+ let wrapped_lines = wrap_text(ctx, label, stage_widths[i])?;
+
+ for (line_index, line) in wrapped_lines.iter().enumerate() {
+ let y = stage_label_y + line_index as f64 * line_height;
+ ctx.move_to(x, y);
+ ctx.show_text(line)?;
+ }
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_stage_dividers(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ let stages = get_stage_positions(map, config);
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.axis);
+ ctx.set_line_width(config.theme.sizes.line_width * 2.0);
+ ctx.set_dash(&[10.0, 18.0], 0.0);
+
+ for &stage_x in &stages {
+ ctx.move_to(stage_x, 0.0);
+ ctx.line_to(stage_x, config.theme.sizes.map_height);
+ }
+
+ ctx.stroke()?;
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_dependencies(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use std::collections::HashMap;
+ use std::f64::consts::PI;
+
+ // Build component map for fast lookup
+ let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ component_map.insert(component.label.to_lowercase(), (x, y));
+ }
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.vertex);
+ ctx.set_line_width(config.theme.sizes.line_width);
+
+ let half_width = config.theme.sizes.vertex_size / 2.0;
+ let half_height = config.theme.sizes.vertex_size / 2.0;
+ let arrow_angle = PI / 4.0; // 45 degrees
+
+ for dependency in &map.dependencies {
+ let from_coords = match component_map.get(&dependency.from.to_lowercase()) {
+ Some(coords) => coords,
+ None => continue,
+ };
+ let to_coords = match component_map.get(&dependency.to.to_lowercase()) {
+ Some(coords) => coords,
+ None => continue,
+ };
+
+ let origin_x = from_coords.0 + half_width;
+ let origin_y = from_coords.1 + half_height;
+ let dest_x = to_coords.0 + half_width;
+ let dest_y = to_coords.1 + half_height;
+
+ let angle = (dest_y - origin_y).atan2(dest_x - origin_x);
+ let cosine = angle.cos();
+ let sine = angle.sin();
+
+ // Offset from vertex edges
+ let offset_origin_x = origin_x + half_width * cosine;
+ let offset_origin_y = origin_y + half_height * sine;
+ let offset_dest_x = dest_x - half_width * cosine;
+ let offset_dest_y = dest_y - half_height * sine;
+
+ // Draw line
+ ctx.move_to(offset_origin_x, offset_origin_y);
+ ctx.line_to(offset_dest_x, offset_dest_y);
+ ctx.stroke()?;
+
+ // Draw arrowhead if directed
+ if dependency.is_directed {
+ let upper_angle = angle - arrow_angle;
+ let lower_angle = angle + arrow_angle;
+ let arrowhead_size = config.theme.sizes.arrowhead_size;
+
+ ctx.move_to(
+ offset_dest_x - arrowhead_size * upper_angle.cos(),
+ offset_dest_y - arrowhead_size * upper_angle.sin(),
+ );
+ ctx.line_to(offset_dest_x, offset_dest_y);
+ ctx.line_to(
+ offset_dest_x - arrowhead_size * lower_angle.cos(),
+ offset_dest_y - arrowhead_size * lower_angle.sin(),
+ );
+ ctx.stroke()?;
+ }
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
+ use geo::{ConcaveHull, LineString, Point};
+ use std::collections::HashMap;
+ use std::f64::consts::PI;
+
+ // Build component map (top-left corner positions)
+ let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ component_map.insert(component.label.to_lowercase(), (x, y));
+ }
+
+ // Create offscreen surface for opacity handling
+ let width = (config.theme.sizes.map_width + config.theme.sizes.vertex_size) as i32;
+ let height = (config.theme.sizes.map_height + config.theme.sizes.vertex_size) as i32;
+ let offscreen = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
+ let offscreen_ctx = cairo::Context::new(&offscreen)?;
+
+ // Translate to account for vertex width/height
+ offscreen_ctx.translate(
+ config.theme.sizes.vertex_size / 2.0,
+ config.theme.sizes.vertex_size / 2.0,
+ );
+
+ for (group_index, group) in map.groups.iter().enumerate() {
+ let component_coords: Vec<(f64, f64)> = group
+ .components
+ .iter()
+ .filter_map(|label| component_map.get(&label.to_lowercase()).copied())
+ .collect();
+
+ if component_coords.is_empty() {
+ continue;
+ }
+
+ let color = &config.theme.colors.groups[group_index % config.theme.colors.groups.len()];
+ let is_line = component_coords.len() == 2;
+
+ let coords = if component_coords.len() == 1 {
+ // For 1 component, create a small circle
+ let (x, y) = component_coords[0];
+ let radius = 3.0;
+ let segments = 16;
+ let mut circle_coords = Vec::new();
+ for i in 0..=segments {
+ let angle = (i as f64 / segments as f64) * 2.0 * PI;
+ circle_coords.push((x + radius * angle.cos(), y + radius * angle.sin()));
+ }
+ circle_coords
+ } else if is_line {
+ // For 2 components, simple line
+ component_coords
+ } else {
+ // For 3+ components, use concave hull
+ let points: Vec<Point> = component_coords
+ .iter()
+ .map(|&(x, y)| Point::new(x, y))
+ .collect();
+
+ let line_string: LineString = points.into();
+ let hull = line_string.concave_hull(2.0);
+ hull.exterior().coords().map(|c| (c.x, c.y)).collect()
+ };
+
+ if coords.is_empty() {
+ continue;
+ }
+
+ // Clear offscreen canvas
+ offscreen_ctx.set_operator(cairo::Operator::Clear);
+ offscreen_ctx.paint()?;
+ offscreen_ctx.set_operator(cairo::Operator::Over);
+
+ // Draw path
+ set_color(&offscreen_ctx, color);
+ offscreen_ctx.move_to(coords[0].0, coords[0].1);
+ for &(x, y) in &coords[1..] {
+ offscreen_ctx.line_to(x, y);
+ }
+
+ // For polygons (not lines), close path and fill
+ if !is_line {
+ offscreen_ctx.close_path();
+ offscreen_ctx.fill_preserve()?;
+ }
+
+ // Draw stroke with round caps and joins for smoother appearance
+ offscreen_ctx.set_line_width(config.theme.sizes.vertex_size * 1.75);
+ offscreen_ctx.set_line_cap(cairo::LineCap::Round);
+ offscreen_ctx.set_line_join(cairo::LineJoin::Round);
+ offscreen_ctx.stroke()?;
+
+ // Composite onto main context with opacity
+ ctx.save()?;
+ ctx.set_source_surface(&offscreen, 0.0, 0.0)?;
+ ctx.paint_with_alpha(config.theme.opacity.groups)?;
+ ctx.restore()?;
+ }
+
+ Ok(())
+}
+
+fn draw_vertices(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use crate::shapes::draw_vertex;
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.vertex);
+
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+
+ draw_vertex(
+ ctx,
+ &component.shape,
+ x,
+ y,
+ config.theme.sizes.vertex_size,
+ config.theme.sizes.vertex_size,
+ )?;
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_vertex_labels(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.label);
+ ctx.select_font_face(
+ "sans-serif",
+ cairo::FontSlant::Normal,
+ cairo::FontWeight::Normal,
+ );
+ ctx.set_font_size(config.theme.fonts.vertex_label);
+
+ for component in &map.components {
+ let (label_x, label_y) = if config.options.smart_label_positioning {
+ // Use smart label positioning to find optimal position
+ use crate::smart_positioning::calculate_optimal_label_position;
+
+ let position = calculate_optimal_label_position(
+ component,
+ map,
+ config.theme.sizes.map_width,
+ config.theme.sizes.map_height,
+ config.theme.sizes.vertex_size,
+ ctx,
+ config.theme.fonts.vertex_label,
+ )?;
+
+ // Adjust Y position for text baseline
+ (position.x, position.y + 7.0)
+ } else {
+ // Simple label positioning (to the right of vertex)
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ (
+ x + config.theme.sizes.vertex_size + 5.0,
+ y + config.theme.sizes.vertex_size / 2.0 + config.theme.fonts.vertex_label / 3.0,
+ )
+ };
+
+ ctx.move_to(label_x, label_y);
+ ctx.show_text(&component.label)?;
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_inertias(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use std::collections::HashMap;
+
+ // Build component map (top-left corner positions)
+ let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ component_map.insert(component.label.to_lowercase(), (x, y));
+ }
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.inertia);
+
+ let corner_radius = 2.0;
+ let rect_width = config.theme.sizes.vertex_size / 2.0;
+ let rect_height = config.theme.sizes.vertex_size * 2.0;
+
+ for inertia in &map.inertias {
+ if let Some(&(origin_x, origin_y)) = component_map.get(&inertia.component.to_lowercase()) {
+ // Position relative to vertex top-left corner
+ let x = origin_x + 3.0 * config.theme.sizes.vertex_size;
+ let y = origin_y - (config.theme.sizes.vertex_size * 2.0) / 3.0;
+
+ // Draw rounded rectangle
+ draw_rounded_rect(ctx, x, y, rect_width, rect_height, corner_radius)?;
+ ctx.fill()?;
+ }
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_rounded_rect(
+ ctx: &cairo::Context,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+ radius: f64,
+) -> Result<(), cairo::Error> {
+ let r = radius;
+ ctx.new_path();
+ ctx.arc(
+ x + r,
+ y + r,
+ r,
+ std::f64::consts::PI,
+ 3.0 * std::f64::consts::PI / 2.0,
+ );
+ ctx.arc(
+ x + width - r,
+ y + r,
+ r,
+ 3.0 * std::f64::consts::PI / 2.0,
+ 0.0,
+ );
+ ctx.arc(
+ x + width - r,
+ y + height - r,
+ r,
+ 0.0,
+ std::f64::consts::PI / 2.0,
+ );
+ ctx.arc(
+ x + r,
+ y + height - r,
+ r,
+ std::f64::consts::PI / 2.0,
+ std::f64::consts::PI,
+ );
+ ctx.close_path();
+ Ok(())
+}
+
+/// Wraps text to fit within a given width, breaking at word boundaries
+fn wrap_text(
+ ctx: &cairo::Context,
+ text: &str,
+ max_width: f64,
+) -> Result<Vec<String>, cairo::Error> {
+ let words: Vec<&str> = text.split_whitespace().collect();
+ let mut lines = Vec::new();
+ let mut current_line = String::new();
+
+ for word in words {
+ let test_line = if current_line.is_empty() {
+ word.to_string()
+ } else {
+ format!("{} {}", current_line, word)
+ };
+
+ let extents = ctx.text_extents(&test_line)?;
+
+ if extents.width() <= max_width {
+ current_line = test_line;
+ } else {
+ if !current_line.is_empty() {
+ lines.push(current_line);
+ }
+ current_line = word.to_string();
+ }
+ }
+
+ if !current_line.is_empty() {
+ lines.push(current_line);
+ }
+
+ // If no lines were created (single very long word), just use the original text
+ if lines.is_empty() {
+ lines.push(text.to_string());
+ }
+
+ Ok(lines)
+}
+
+fn draw_evolutions(
+ ctx: &cairo::Context,
+ map: &Map,
+ config: &Configuration,
+) -> Result<(), RenderError> {
+ use std::collections::HashMap;
+ use std::f64::consts::PI;
+
+ // Build component map (top-left corner positions)
+ let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ component_map.insert(component.label.to_lowercase(), (x, y));
+ }
+
+ ctx.save()?;
+ set_color(ctx, &config.theme.colors.evolution);
+ ctx.set_line_width(config.theme.sizes.line_width * 2.0);
+ ctx.set_dash(&[10.0], 0.0);
+
+ for evolution in &map.evolutions {
+ if let Some(&(origin_x, origin_y)) = component_map.get(&evolution.component.to_lowercase())
+ {
+ // Calculate destination with clamping
+ let evolution_offset = percent_to_pixel(evolution.value, config.theme.sizes.map_width);
+ let dest_x = (origin_x + evolution_offset)
+ .max(0.0)
+ .min(config.theme.sizes.map_width);
+ let dest_y = origin_y;
+
+ // Determine direction
+ let multiplier = if dest_x > origin_x { 1.0 } else { -1.0 };
+
+ // Calculate offsets from top-left corner to center the line on the vertex
+ let offset_origin_x = origin_x
+ + multiplier * (config.theme.sizes.vertex_size / 2.0)
+ + config.theme.sizes.vertex_size / 2.0;
+ let offset_origin_y = origin_y + config.theme.sizes.vertex_size / 2.0;
+ let offset_dest_x = dest_x - multiplier * (config.theme.sizes.vertex_size / 2.0)
+ + config.theme.sizes.vertex_size / 2.0;
+ let offset_dest_y = dest_y + config.theme.sizes.vertex_size / 2.0;
+
+ // Draw dashed line
+ ctx.move_to(offset_origin_x, offset_origin_y);
+ ctx.line_to(offset_dest_x, offset_dest_y);
+ ctx.stroke()?;
+
+ // Draw arrowhead at destination only
+ let upper_angle = -PI / 4.0;
+ let lower_angle = PI / 4.0;
+ let arrowhead_size = config.theme.sizes.arrowhead_size;
+
+ ctx.move_to(offset_dest_x, offset_dest_y);
+ ctx.line_to(
+ offset_dest_x - multiplier * arrowhead_size * upper_angle.cos(),
+ offset_dest_y - multiplier * arrowhead_size * upper_angle.sin(),
+ );
+ ctx.stroke()?;
+
+ ctx.move_to(offset_dest_x, offset_dest_y);
+ ctx.line_to(
+ offset_dest_x - multiplier * arrowhead_size * lower_angle.cos(),
+ offset_dest_y - multiplier * arrowhead_size * lower_angle.sin(),
+ );
+ ctx.stroke()?;
+ }
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+fn draw_notes(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
+ let padding = 5.0;
+ let max_width = 400.0;
+
+ ctx.save()?;
+ ctx.select_font_face(
+ "sans-serif",
+ cairo::FontSlant::Normal,
+ cairo::FontWeight::Normal,
+ );
+ ctx.set_font_size(config.theme.fonts.note);
+
+ for note in &map.notes {
+ let x = percent_to_pixel(note.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(note.coordinates.1, config.theme.sizes.map_height);
+
+ // Split text by newlines (handle both \\n escape and actual newlines)
+ let text = note.text.replace("\\n", "\n");
+ let lines: Vec<&str> = text.split('\n').collect();
+
+ // Calculate box dimensions
+ let mut widest_line = 0.0;
+ for line in &lines {
+ let extents = ctx.text_extents(line)?;
+ if extents.width() > widest_line {
+ widest_line = extents.width();
+ }
+ }
+
+ let box_width = f64::min(max_width, widest_line + padding * 2.0);
+ let box_height = lines.len() as f64 * config.theme.line_heights.note + padding * 2.0;
+
+ // Draw background
+ set_color(ctx, &config.theme.colors.background);
+ ctx.rectangle(x, y, box_width, box_height);
+ ctx.fill()?;
+
+ // Draw border
+ set_color(ctx, &config.theme.colors.vertex);
+ ctx.set_line_width(config.theme.sizes.line_width);
+ ctx.rectangle(x, y, box_width, box_height);
+ ctx.stroke()?;
+
+ // Draw text
+ set_color(ctx, &config.theme.colors.label);
+ for (i, line) in lines.iter().enumerate() {
+ ctx.move_to(
+ x + padding,
+ y + padding + i as f64 * config.theme.line_heights.note + config.theme.fonts.note,
+ );
+ ctx.show_text(line)?;
+ }
+ }
+
+ ctx.restore()?;
+ Ok(())
+}
+
+/// Gets the pixel positions of evolution stages
+fn get_stage_positions(map: &Map, config: &Configuration) -> [f64; 3] {
+ use wmap_parser::Stage;
+
+ let default_stages = [25.0, 50.0, 75.0];
+ let mut stages = default_stages;
+
+ // Override with map-specific stages if provided
+ for stage_data in &map.stages {
+ let index = match stage_data.stage {
+ Stage::I => 0,
+ Stage::Ii => 1,
+ Stage::Iii => 2,
+ Stage::Iv => continue, // Stage IV doesn't have a divider line
+ };
+ stages[index] = stage_data.value; // Value is already a percentage (0-100)
+ }
+
+ [
+ percent_to_pixel(stages[0], config.theme.sizes.map_width),
+ percent_to_pixel(stages[1], config.theme.sizes.map_width),
+ percent_to_pixel(stages[2], config.theme.sizes.map_width),
+ ]
+}
diff --git a/src/shapes.rs b/src/shapes.rs
new file mode 100644
index 0000000..2b8db2c
--- /dev/null
+++ b/src/shapes.rs
@@ -0,0 +1,95 @@
+use wmap_parser::Shape;
+
+/// Draws a vertex shape at the given position
+pub fn draw_vertex(
+ ctx: &cairo::Context,
+ shape: &Shape,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+) -> Result<(), cairo::Error> {
+ match shape {
+ Shape::Circle => draw_circle(ctx, x, y, width, height),
+ Shape::Square => draw_square(ctx, x, y, width, height),
+ Shape::Triangle => draw_triangle(ctx, x, y, width, height),
+ Shape::X => draw_x(ctx, x, y, width, height),
+ }
+}
+
+fn draw_circle(
+ ctx: &cairo::Context,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+) -> Result<(), cairo::Error> {
+ ctx.save()?;
+ ctx.translate(x + width / 2.0, y + height / 2.0);
+ ctx.scale(width / 2.0, height / 2.0);
+ ctx.arc(0.0, 0.0, 1.0, 0.0, 2.0 * std::f64::consts::PI);
+ ctx.restore()?;
+ ctx.fill()?;
+ Ok(())
+}
+
+fn draw_square(
+ ctx: &cairo::Context,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+) -> Result<(), cairo::Error> {
+ ctx.rectangle(x, y, width, height);
+ ctx.fill()?;
+ Ok(())
+}
+
+fn draw_triangle(
+ ctx: &cairo::Context,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+) -> Result<(), cairo::Error> {
+ ctx.move_to(x + width / 2.0, y);
+ ctx.line_to(x + width, y + height);
+ ctx.line_to(x, y + height);
+ ctx.close_path();
+ ctx.fill()?;
+ Ok(())
+}
+
+fn draw_x(
+ ctx: &cairo::Context,
+ x: f64,
+ y: f64,
+ width: f64,
+ height: f64,
+) -> Result<(), cairo::Error> {
+ let line_width = 2.0;
+ ctx.set_line_width(line_width);
+ ctx.move_to(x, y);
+ ctx.line_to(x + width, y + height);
+ ctx.move_to(x + width, y);
+ ctx.line_to(x, y + height);
+ ctx.stroke()?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_draw_shapes() {
+ // Just verify the functions don't panic
+ let surface = cairo::ImageSurface::create(cairo::Format::Rgb24, 100, 100).unwrap();
+ let ctx = cairo::Context::new(&surface).unwrap();
+
+ assert!(draw_vertex(&ctx, &Shape::Circle, 0.0, 0.0, 25.0, 25.0).is_ok());
+ assert!(draw_vertex(&ctx, &Shape::Square, 0.0, 0.0, 25.0, 25.0).is_ok());
+ assert!(draw_vertex(&ctx, &Shape::Triangle, 0.0, 0.0, 25.0, 25.0).is_ok());
+ assert!(draw_vertex(&ctx, &Shape::X, 0.0, 0.0, 25.0, 25.0).is_ok());
+ }
+}
diff --git a/src/smart_positioning.rs b/src/smart_positioning.rs
new file mode 100644
index 0000000..2def897
--- /dev/null
+++ b/src/smart_positioning.rs
@@ -0,0 +1,316 @@
+use crate::geometry::{Line, Rect};
+use crate::utils::percent_to_pixel;
+use wmap_parser::{Component, Map};
+
+#[derive(Debug, Clone)]
+pub struct LabelPosition {
+ pub x: f64,
+ pub y: f64,
+}
+
+/// Calculate the optimal position for a vertex label to minimize collisions
+pub fn calculate_optimal_label_position(
+ component: &Component,
+ map: &Map,
+ map_width: f64,
+ map_height: f64,
+ vertex_size: f64,
+ ctx: &cairo::Context,
+ font_size: f64,
+) -> Result<LabelPosition, cairo::Error> {
+ let vertex_pixel_x = percent_to_pixel(component.coordinates.0, map_width);
+ let vertex_pixel_y = percent_to_pixel(component.coordinates.1, map_height);
+
+ // Calculate label size
+ ctx.set_font_size(font_size);
+ let extents = ctx.text_extents(&component.label)?;
+ let label_width = extents.width() + 4.0;
+ let label_height = extents.height() + 2.0;
+
+ // Generate candidate positions
+ let candidates = generate_candidate_positions(
+ vertex_pixel_x,
+ vertex_pixel_y,
+ vertex_size,
+ label_width,
+ label_height,
+ );
+
+ // Collect all lines (dependencies, evolutions, axis lines)
+ let all_lines = collect_all_lines(map, map_width, map_height, vertex_size);
+
+ // Collect note rectangles
+ let note_rects = collect_note_rects(map, map_width, map_height, ctx, font_size)?;
+
+ // Check if default position (right) has no collisions
+ let default_position = &candidates[0];
+ let default_rect = Rect::new(
+ default_position.x,
+ default_position.y,
+ label_width,
+ label_height,
+ );
+
+ if count_line_collisions(&default_rect, &all_lines) == 0
+ && count_note_collisions(&default_rect, &note_rects) == 0
+ {
+ return Ok(default_position.clone());
+ }
+
+ // Score all positions and find the best
+ let mut best_position = default_position.clone();
+ let mut best_score = f64::INFINITY;
+
+ for (index, position) in candidates.iter().enumerate() {
+ let label_rect = Rect::new(position.x, position.y, label_width, label_height);
+
+ let collision_count = count_line_collisions(&label_rect, &all_lines);
+ let note_collision_count = count_note_collisions(&label_rect, &note_rects);
+
+ // Distance from label center to vertex center
+ let label_center_x = position.x + label_width / 2.0;
+ let label_center_y = position.y + label_height / 2.0;
+ let vertex_center_x = vertex_pixel_x + vertex_size / 2.0;
+ let vertex_center_y = vertex_pixel_y + vertex_size / 2.0;
+ let distance = ((label_center_x - vertex_center_x).powi(2)
+ + (label_center_y - vertex_center_y).powi(2))
+ .sqrt();
+
+ // Calculate ownership penalty
+ let ownership_penalty = calculate_ownership_penalty(
+ &label_rect,
+ component,
+ &map.components,
+ map_width,
+ map_height,
+ vertex_size,
+ );
+
+ // Score: collisions * 100 + note_collisions * 500 + distance * 0.5 + index * 0.1 + ownership
+ let score = (collision_count as f64) * 100.0
+ + (note_collision_count as f64) * 500.0
+ + distance * 0.5
+ + (index as f64) * 0.1
+ + ownership_penalty;
+
+ if score < best_score {
+ best_score = score;
+ best_position = position.clone();
+ }
+ }
+
+ Ok(best_position)
+}
+
+/// Generate 8 candidate positions around a vertex
+fn generate_candidate_positions(
+ vertex_x: f64,
+ vertex_y: f64,
+ vertex_size: f64,
+ label_width: f64,
+ label_height: f64,
+) -> Vec<LabelPosition> {
+ let padding = 5.0;
+
+ vec![
+ // Right (default)
+ LabelPosition {
+ x: vertex_x + vertex_size + padding,
+ y: vertex_y + (vertex_size - label_height) / 2.0,
+ },
+ // Left
+ LabelPosition {
+ x: vertex_x - label_width - padding,
+ y: vertex_y + (vertex_size - label_height) / 2.0,
+ },
+ // Top
+ LabelPosition {
+ x: vertex_x + (vertex_size - label_width) / 2.0,
+ y: vertex_y - label_height - padding,
+ },
+ // Bottom
+ LabelPosition {
+ x: vertex_x + (vertex_size - label_width) / 2.0,
+ y: vertex_y + vertex_size + padding,
+ },
+ // Top-right
+ LabelPosition {
+ x: vertex_x + vertex_size + padding,
+ y: vertex_y - label_height - padding,
+ },
+ // Top-left
+ LabelPosition {
+ x: vertex_x - label_width - padding,
+ y: vertex_y - label_height - padding,
+ },
+ // Bottom-right
+ LabelPosition {
+ x: vertex_x + vertex_size + padding,
+ y: vertex_y + vertex_size + padding,
+ },
+ // Bottom-left
+ LabelPosition {
+ x: vertex_x - label_width - padding,
+ y: vertex_y + vertex_size + padding,
+ },
+ ]
+}
+
+/// Collect all lines from dependencies, evolutions, and axis lines
+fn collect_all_lines(map: &Map, map_width: f64, map_height: f64, vertex_size: f64) -> Vec<Line> {
+ use std::collections::HashMap;
+
+ let mut lines = Vec::new();
+
+ // Build component position map
+ let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
+ for component in &map.components {
+ let x = percent_to_pixel(component.coordinates.0, map_width);
+ let y = percent_to_pixel(component.coordinates.1, map_height);
+ component_map.insert(component.label.to_lowercase(), (x, y));
+ }
+
+ // Collect dependency lines
+ for dependency in &map.dependencies {
+ if let (Some(&origin), Some(&destination)) = (
+ component_map.get(&dependency.from.to_lowercase()),
+ component_map.get(&dependency.to.to_lowercase()),
+ ) {
+ let slope = (destination.1 - origin.1) / (destination.0 - origin.0);
+ let angle = slope.atan();
+ let multiplier = if slope < 0.0 { -1.0 } else { 1.0 };
+
+ let offset_origin_x = origin.0 + multiplier * (vertex_size / 2.0) * angle.cos();
+ let offset_origin_y = origin.1 + multiplier * (vertex_size / 2.0) * angle.sin();
+ let offset_dest_x = destination.0 - multiplier * (vertex_size / 2.0) * angle.cos();
+ let offset_dest_y = destination.1 - multiplier * (vertex_size / 2.0) * angle.sin();
+
+ let adjusted_origin_x = offset_origin_x + vertex_size / 2.0;
+ let adjusted_origin_y = offset_origin_y + vertex_size / 2.0;
+ let adjusted_dest_x = offset_dest_x + vertex_size / 2.0;
+ let adjusted_dest_y = offset_dest_y + vertex_size / 2.0;
+
+ lines.push(Line::new(
+ (adjusted_origin_x, adjusted_origin_y),
+ (adjusted_dest_x, adjusted_dest_y),
+ ));
+ }
+ }
+
+ // Collect evolution lines
+ for evolution in &map.evolutions {
+ if let Some(&origin) = component_map.get(&evolution.component.to_lowercase()) {
+ let evolution_offset = percent_to_pixel(evolution.value, map_width);
+ let destination_x = (origin.0 + evolution_offset).max(0.0).min(map_width);
+ let destination_y = origin.1;
+
+ let multiplier = if destination_x > origin.0 { 1.0 } else { -1.0 };
+
+ let offset_origin_x = origin.0 + multiplier * (vertex_size / 2.0) + vertex_size / 2.0;
+ let offset_origin_y = origin.1 + vertex_size / 2.0;
+ let offset_dest_x =
+ destination_x - multiplier * (vertex_size / 2.0) + vertex_size / 2.0;
+ let offset_dest_y = destination_y + vertex_size / 2.0;
+
+ lines.push(Line::new(
+ (offset_origin_x, offset_origin_y),
+ (offset_dest_x, offset_dest_y),
+ ));
+ }
+ }
+
+ // Add axis lines
+ lines.push(Line::new((0.0, 0.0), (0.0, map_height))); // Y-axis
+ lines.push(Line::new((0.0, map_height), (map_width, map_height))); // X-axis
+
+ lines
+}
+
+/// Collect rectangles for all notes
+fn collect_note_rects(
+ map: &Map,
+ map_width: f64,
+ map_height: f64,
+ ctx: &cairo::Context,
+ font_size: f64,
+) -> Result<Vec<Rect>, cairo::Error> {
+ let mut rects = Vec::new();
+
+ ctx.set_font_size(font_size);
+
+ for note in &map.notes {
+ let x = percent_to_pixel(note.coordinates.0, map_width);
+ let y = percent_to_pixel(note.coordinates.1, map_height);
+
+ // Calculate note size (simplified - just use text extents)
+ let extents = ctx.text_extents(&note.text)?;
+ let width = extents.width() + 10.0; // padding
+ let height = extents.height() + 10.0; // padding
+
+ rects.push(Rect::new(x, y, width, height));
+ }
+
+ Ok(rects)
+}
+
+/// Count how many lines intersect with a label rectangle
+fn count_line_collisions(label_rect: &Rect, lines: &[Line]) -> usize {
+ lines
+ .iter()
+ .filter(|line| crate::geometry::line_intersects_rect(line, label_rect))
+ .count()
+}
+
+/// Count how many note rectangles overlap with a label rectangle
+fn count_note_collisions(label_rect: &Rect, note_rects: &[Rect]) -> usize {
+ note_rects
+ .iter()
+ .filter(|note_rect| crate::geometry::rect_intersects_rect(label_rect, note_rect))
+ .count()
+}
+
+/// Calculate ownership penalty if label is closer to another vertex
+fn calculate_ownership_penalty(
+ label_rect: &Rect,
+ own_component: &Component,
+ all_components: &[Component],
+ map_width: f64,
+ map_height: f64,
+ vertex_size: f64,
+) -> f64 {
+ if all_components.is_empty() {
+ return 0.0;
+ }
+
+ let label_center_x = label_rect.x + label_rect.width / 2.0;
+ let label_center_y = label_rect.y + label_rect.height / 2.0;
+
+ let own_vertex_x = percent_to_pixel(own_component.coordinates.0, map_width);
+ let own_vertex_y = percent_to_pixel(own_component.coordinates.1, map_height);
+ let own_center_x = own_vertex_x + vertex_size / 2.0;
+ let own_center_y = own_vertex_y + vertex_size / 2.0;
+
+ let distance_to_own =
+ ((label_center_x - own_center_x).powi(2) + (label_center_y - own_center_y).powi(2)).sqrt();
+
+ for other_component in all_components {
+ if other_component.label == own_component.label {
+ continue;
+ }
+
+ let other_vertex_x = percent_to_pixel(other_component.coordinates.0, map_width);
+ let other_vertex_y = percent_to_pixel(other_component.coordinates.1, map_height);
+ let other_center_x = other_vertex_x + vertex_size / 2.0;
+ let other_center_y = other_vertex_y + vertex_size / 2.0;
+
+ let distance_to_other = ((label_center_x - other_center_x).powi(2)
+ + (label_center_y - other_center_y).powi(2))
+ .sqrt();
+
+ if distance_to_other < distance_to_own {
+ return 50.0;
+ }
+ }
+
+ 0.0
+}
diff --git a/src/stage_type.rs b/src/stage_type.rs
new file mode 100644
index 0000000..f3ff056
--- /dev/null
+++ b/src/stage_type.rs
@@ -0,0 +1,199 @@
+/// Stage type used to analyze different aspects of a Wardley map
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum StageType {
+ #[default]
+ Activities,
+ Behavior,
+ Certainty,
+ Comparison,
+ Cynefin,
+ Data,
+ DecisionDrivers,
+ Efficiency,
+ Failure,
+ FocusOfValue,
+ Knowledge,
+ KnowledgeManagement,
+ Market,
+ MarketAction,
+ MarketPerception,
+ PerceptionInIndustry,
+ Practice,
+ PublicationTypes,
+ Ubiquity,
+ Understanding,
+ UserPerception,
+ EvolutionStage,
+}
+
+impl StageType {
+ /// Returns the name of this stage type
+ pub fn name(&self) -> &'static str {
+ match self {
+ Self::Activities => "Activities",
+ Self::Behavior => "Behavior",
+ Self::Certainty => "Certainty",
+ Self::Comparison => "Comparison",
+ Self::Cynefin => "Cynefin",
+ Self::Data => "Data",
+ Self::DecisionDrivers => "Decision Drivers",
+ Self::Efficiency => "Efficiency",
+ Self::Failure => "Failure",
+ Self::FocusOfValue => "Focus Of Value",
+ Self::Knowledge => "Knowledge",
+ Self::KnowledgeManagement => "Knowledge Management",
+ Self::Market => "Market",
+ Self::MarketAction => "Market Action",
+ Self::MarketPerception => "Market Perception",
+ Self::PerceptionInIndustry => "Perception In Industry",
+ Self::Practice => "Practice",
+ Self::PublicationTypes => "Publication Types",
+ Self::Ubiquity => "Ubiquity",
+ Self::Understanding => "Understanding",
+ Self::UserPerception => "User Perception",
+ Self::EvolutionStage => "Evolution Stage",
+ }
+ }
+
+ /// Returns the four evolution stage labels for this stage type
+ pub fn stages(&self) -> [&'static str; 4] {
+ match self {
+ Self::Activities => [
+ "Genesis",
+ "Custom",
+ "Product (+rental)",
+ "Commodity (+utility)",
+ ],
+ Self::Behavior => [
+ "Uncertain when to use",
+ "Learning when to use",
+ "Learning through use",
+ "Known / common usage",
+ ],
+ Self::Certainty => [
+ "Poorly Understood / exploring the unknown",
+ "Rapid Increase In Learning / discovery becomes refining",
+ "Rapid increase in use / increasing fit for purpose",
+ "Commonly understood (in terms of use)",
+ ],
+ Self::Comparison => [
+ "Constantly changing / a differential / unstable",
+ "Learning from others / testing the water / some evidential support",
+ "Competing models / feature difference / evidential support",
+ "Essential / any advantage is operational / accepted norm",
+ ],
+ Self::Cynefin => ["Chaotic", "Complex", "Complicated", "Clear"],
+ Self::Data => ["Unmodelled", "Divergent", "Convergent", "Modelled"],
+ Self::DecisionDrivers => [
+ "Heritage / culture",
+ "Analyses & synthesis",
+ "Analyses & synthesis",
+ "Previous Experience",
+ ],
+ Self::Efficiency => [
+ "Reducing the cost of change (experimentation)",
+ "Reducing cost of waste (Learning)",
+ "Reducing cost of waste (Learning)",
+ "Reducing cost of deviation (Volume)",
+ ],
+ Self::Failure => [
+ "High / tolerated / assumed to be wrong",
+ "Moderate / unsurprising if wrong but disappointed",
+ "Not tolerated / focus on constant improvement / assumed to be in the right direction / resistance to changing the model",
+ "Surprised by failure / focus on operational efficiency",
+ ],
+ Self::FocusOfValue => [
+ "High future worth but immediate investment",
+ "Seeking ways to profit and a ROI / seeking confirmation of value",
+ "High profitability per unit / a valuable model / a feeling of understanding / focus on exploitation",
+ "High volume / reducing margin / important but invisible / an essential component of something more complex",
+ ],
+ Self::Knowledge => ["Concept", "Hypothesis", "Theory", "Accepted"],
+ Self::KnowledgeManagement => [
+ "Uncertain",
+ "Learning on use / focused on testing prediction",
+ "Learning on operation / using prediction / verification",
+ "Known / accepted",
+ ],
+ Self::Market => [
+ "Undefined Market",
+ "Forming Market / an array of competing forms and models of understanding",
+ "Growing Market / consolidation to a few competing but more accepted forms",
+ "Mature Market / stabilised to an accepted form",
+ ],
+ Self::MarketAction => [
+ "Gambling / driven by gut",
+ "Exploring a \"found\" value",
+ "Market analysis / listening to customers",
+ "Metric driven / build what is needed",
+ ],
+ Self::MarketPerception => [
+ "Chaotic (non-linear) / domain of the \"crazy\"",
+ "Domain of \"experts\"",
+ "Increasing expectation of use / domain of \"professionals\"",
+ "Ordered (appearance of being linear) / trivial / formula to be applied",
+ ],
+ Self::PerceptionInIndustry => [
+ "Future source of competitive advantage / unpredictable / unknown",
+ "Seen as a scompetitive advantage / a differential / ROI / case examples",
+ "Advantage through implementation / features / this model is better than that",
+ "Cost of doing business / accepted / specific defined models",
+ ],
+ Self::Practice => ["Novel", "Emerging", "Good", "Best"],
+ Self::PublicationTypes => [
+ "Describe the wonder of the thing / the discovery of some marvel / a new land / an unknown frontier",
+ "Focused on build / construct / awareness and learning / many models of explanation / no accepted forms / a wild west",
+ "Maintenance / operations / installation / comparison between competing forms / feature analysis",
+ "Focused on use / increasingly an accepted, almost invisible component",
+ ],
+ Self::Ubiquity => [
+ "Rare",
+ "Slowly Increasing",
+ "Rapidly Increasing",
+ "Widespread in the applicable market / ecosystem",
+ ],
+ Self::Understanding => [
+ "Poorly Understood / unpredictable",
+ "Increasing understanding / development of measures",
+ "Increasing education / constant refinement of needs / measures",
+ "Believed to be well defined / stable / measurable",
+ ],
+ Self::UserPerception => [
+ "Different / confusing / exciting / surprising / dangerous",
+ "Leading edge / emerging / unceirtanty over results",
+ "Increasingly common / disappointed if not used or available / feeling left behind",
+ "Standard / expected / feeling of shock if not used",
+ ],
+ Self::EvolutionStage => ["Stage I", "Stage II", "Stage III", "Stage IV"],
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_stage_type_names() {
+ assert_eq!(StageType::Activities.name(), "Activities");
+ assert_eq!(StageType::Behavior.name(), "Behavior");
+ assert_eq!(StageType::Cynefin.name(), "Cynefin");
+ }
+
+ #[test]
+ fn test_stage_type_stages() {
+ let activities = StageType::Activities.stages();
+ assert_eq!(activities[0], "Genesis");
+ assert_eq!(activities[1], "Custom");
+ assert_eq!(activities[2], "Product (+rental)");
+ assert_eq!(activities[3], "Commodity (+utility)");
+
+ let cynefin = StageType::Cynefin.stages();
+ assert_eq!(cynefin, ["Chaotic", "Complex", "Complicated", "Clear"]);
+ }
+
+ #[test]
+ fn test_default_stage_type() {
+ assert_eq!(StageType::default(), StageType::Activities);
+ }
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..b6738d4
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,53 @@
+/// Converts a percentage (0-100) to pixels based on the dimension
+#[inline]
+pub fn percent_to_pixel(percentage: f64, dimension: f64) -> f64 {
+ (percentage * dimension) / 100.0
+}
+
+/// Parses a hex color string to RGB components (0.0-1.0)
+pub fn parse_color(color: &str) -> (f64, f64, f64) {
+ let color = color.trim_start_matches('#');
+ if color.len() != 6 {
+ return (0.0, 0.0, 0.0); // Default to black on error
+ }
+
+ let r = u8::from_str_radix(&color[0..2], 16).unwrap_or(0) as f64 / 255.0;
+ let g = u8::from_str_radix(&color[2..4], 16).unwrap_or(0) as f64 / 255.0;
+ let b = u8::from_str_radix(&color[4..6], 16).unwrap_or(0) as f64 / 255.0;
+
+ (r, g, b)
+}
+
+/// Sets the cairo context color from a hex string
+pub fn set_color(ctx: &cairo::Context, color: &str) {
+ let (r, g, b) = parse_color(color);
+ ctx.set_source_rgb(r, g, b);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_percent_to_pixel() {
+ assert_eq!(percent_to_pixel(0.0, 100.0), 0.0);
+ assert_eq!(percent_to_pixel(50.0, 100.0), 50.0);
+ assert_eq!(percent_to_pixel(100.0, 100.0), 100.0);
+ assert_eq!(percent_to_pixel(25.0, 1300.0), 325.0);
+ }
+
+ #[test]
+ fn test_parse_color() {
+ // Test white
+ assert_eq!(parse_color("#FFFFFF"), (1.0, 1.0, 1.0));
+
+ // Test black
+ assert_eq!(parse_color("#000000"), (0.0, 0.0, 0.0));
+
+ // Test custom color (#0F261F)
+ let (r, g, b) = parse_color("#0F261F");
+ assert!((r - 0.058823).abs() < 0.001); // 15/255
+ assert!((g - 0.149019).abs() < 0.001); // 38/255
+ assert!((b - 0.121568).abs() < 0.001); // 31/255
+ }
+}
diff --git a/tests/fixtures/simple.wmap b/tests/fixtures/simple.wmap
new file mode 100644
index 0000000..6c83047
--- /dev/null
+++ b/tests/fixtures/simple.wmap
@@ -0,0 +1,13 @@
+Tea (90, 50)
+Cup (80, 40)
+Kettle (70, 30) [square]
+Water (60, 20) [triangle]
+
+Tea -> Cup
+Cup -> Kettle
+Kettle -> Water
+
+[Note] (50, 50) This is a simple tea supply chain
+[Group] Tea, Cup, Kettle
+[Inertia] Kettle
+[Evolution] Tea + 10
diff --git a/tests/integration_test.rs b/tests/integration_test.rs
new file mode 100644
index 0000000..6814429
--- /dev/null
+++ b/tests/integration_test.rs
@@ -0,0 +1,63 @@
+use wmap_renderer::{render_to_png, render_to_svg, Configuration, StageType};
+
+#[test]
+fn test_render_simple_map_to_png() {
+ let source = include_str!("fixtures/simple.wmap");
+ let map = wmap_parser::parse(source);
+ let config = Configuration::default();
+
+ let result = render_to_png(&map, StageType::default(), &config);
+ assert!(result.is_ok());
+
+ let png_data = result.unwrap();
+ assert!(!png_data.is_empty());
+
+ // Verify it's a valid PNG (starts with PNG signature)
+ assert_eq!(&png_data[0..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
+}
+
+#[test]
+fn test_render_simple_map_to_svg() {
+ let source = include_str!("fixtures/simple.wmap");
+ let map = wmap_parser::parse(source);
+ let config = Configuration::default();
+
+ let result = render_to_svg(&map, StageType::default(), &config);
+ assert!(result.is_ok());
+
+ let svg_data = result.unwrap();
+ assert!(!svg_data.is_empty());
+ assert!(svg_data.contains("<svg"));
+}
+
+#[test]
+fn test_render_with_all_shapes() {
+ let source = "Circle (50, 50) [circle]\nSquare (60, 60) [square]\nTriangle (70, 70) [triangle]\nX (80, 80) [x]";
+ let map = wmap_parser::parse(source);
+ let config = Configuration::default();
+
+ let result = render_to_png(&map, StageType::default(), &config);
+ assert!(result.is_ok());
+}
+
+#[test]
+fn test_render_with_different_stage_types() {
+ let source = "Component (50, 50)";
+ let map = wmap_parser::parse(source);
+ let config = Configuration::default();
+
+ // Test a few different stage types
+ assert!(render_to_png(&map, StageType::Activities, &config).is_ok());
+ assert!(render_to_png(&map, StageType::Cynefin, &config).is_ok());
+ assert!(render_to_png(&map, StageType::Practice, &config).is_ok());
+}
+
+#[test]
+fn test_render_empty_map() {
+ let source = "";
+ let map = wmap_parser::parse(source);
+ let config = Configuration::default();
+
+ let result = render_to_png(&map, StageType::default(), &config);
+ assert!(result.is_ok());
+}