From f14cc7f21eb55d090537f08ab3290494f0fbe910 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 6 Nov 2026 18:47:21 -0800 Subject: [PATCH] linesweeper: keep a copy of the crates.io 0.5.0 package in vendor/ Add the linesweeper 0.5.0 package to vendor/ with no file changed, so that a fix to it can reach the Craft apps through this repository without waiting for a linesweeper release. The GitHub repository its manifest names is archived, and its README says development moved off GitHub. Leave out the test font (fonts live in craft-fonts), the logo artwork, the snapshot images and the package's repository tooling. SOURCE.json lists each with its hash. Add a workspace exclude, a [patch.crates-io] entry, SOURCE.json with the archive hash and per-file hashes, NOTICE and ATTRIBUTION.md rows, and the package's tests in the backend test script. craft-prepress's flatten feature now builds against the copy, so Cargo.lock drops the package's registry source and checksum. --- ATTRIBUTION.md | 1 + Cargo.lock | 2 - Cargo.toml | 3 +- NOTICE | 8 + scripts/test-pdf-backends.sh | 3 + vendor/linesweeper/.cargo_vcs_info.json | 6 + vendor/linesweeper/.gitignore | 5 + vendor/linesweeper/CHANGELOG.md | 43 + vendor/linesweeper/Cargo.lock | 1255 ++++++++ vendor/linesweeper/Cargo.toml | 194 ++ vendor/linesweeper/Cargo.toml.orig | 99 + vendor/linesweeper/LICENSE-APACHE | 202 ++ vendor/linesweeper/LICENSE-MIT | 25 + vendor/linesweeper/README.craft.md | 53 + vendor/linesweeper/README.md | 30 + vendor/linesweeper/SOURCE.json | 129 + vendor/linesweeper/benches/linebender_logo.rs | 70 + vendor/linesweeper/benches/painted_dreams.rs | 26 + vendor/linesweeper/benches/synthetic.rs | 65 + vendor/linesweeper/docs/sweep.typ | 960 ++++++ vendor/linesweeper/examples/boolean_op.rs | 342 +++ vendor/linesweeper/examples/bus.rs | 144 + vendor/linesweeper/examples/curve_metrics.rs | 130 + vendor/linesweeper/examples/visualize_op.rs | 130 + .../linesweeper/examples/winding-numbers.rs | 108 + .../proptest-regressions/sweep/sweep_line.txt | 8 + .../sweep/weak_ordering.txt | 7 + .../proptest-regressions/topology.txt | 11 + vendor/linesweeper/src/arbitrary.rs | 134 + vendor/linesweeper/src/curve/line.rs | 159 + vendor/linesweeper/src/curve/mod.rs | 2032 +++++++++++++ vendor/linesweeper/src/curve/split_quad.rs | 57 + vendor/linesweeper/src/curve/transverse.rs | 189 ++ vendor/linesweeper/src/generators.rs | 147 + vendor/linesweeper/src/geom.rs | 542 ++++ vendor/linesweeper/src/lib.rs | 336 +++ vendor/linesweeper/src/num.rs | 132 + vendor/linesweeper/src/order.rs | 130 + vendor/linesweeper/src/perturbation.rs | 200 ++ vendor/linesweeper/src/position.rs | 561 ++++ vendor/linesweeper/src/segments.rs | 482 +++ ...linesweeper__tests__empty_horizontals.snap | 39 + .../linesweeper__tests__two_squares.snap | 35 + ...linesweeper__topology__tests__diamond.snap | 225 ++ ...esweeper__topology__tests__inner_loop.snap | 596 ++++ ...topology__tests__merge_monotonic_segs.snap | 313 ++ ..._tests__merge_monotonic_segs_with_cut.snap | 320 ++ ...eper__topology__tests__nested_squares.snap | 501 +++ .../linesweeper__topology__tests__square.snap | 225 ++ ...__topology__tests__square_and_diamond.snap | 508 ++++ ...ology__tests__square_with_double_back.snap | 352 +++ ...r__topology__tests__squares_with_gaps.snap | 931 ++++++ vendor/linesweeper/src/sweep/mod.rs | 33 + vendor/linesweeper/src/sweep/output_event.rs | 105 + vendor/linesweeper/src/sweep/range.rs | 445 +++ ..._line__tests__conservative_line_order.snap | 136 + ...ine__tests__regression_position_state.snap | 361 +++ ...per__sweep__sweep_line__tests__square.snap | 84 + ...sweep_line__tests__square_and_diamond.snap | 292 ++ ...sweep__sweep_line__tests__two_squares.snap | 202 ++ vendor/linesweeper/src/sweep/sweep_line.rs | 2102 +++++++++++++ vendor/linesweeper/src/topology.rs | 2688 +++++++++++++++++ vendor/linesweeper/src/treevec.rs | 955 ++++++ vendor/linesweeper/src/typed_vec.rs | 129 + vendor/linesweeper/tests/fonts.rs | 56 + vendor/linesweeper/tests/paperjs.rs | 222 ++ vendor/linesweeper/tests/regression.rs | 46 + .../tests/regression/awesome_0.txt | 2 + .../tests/regression/graphite_0.txt | 2 + .../tests/regression/graphite_1.txt | 2 + .../tests/regression/graphite_2.txt | 2 + .../tests/regression/graphite_3.txt | 2 + .../tests/regression/graphite_4.txt | 2 + .../tests/regression/graphite_5.txt | 2 + .../tests/regression/graphite_6.txt | 2 + .../tests/regression/graphite_7.txt | 2 + .../linesweeper/tests/regression/sasha_0.txt | 2 + vendor/linesweeper/tests/snapshots.rs | 567 ++++ 78 files changed, 21645 insertions(+), 3 deletions(-) create mode 100644 vendor/linesweeper/.cargo_vcs_info.json create mode 100644 vendor/linesweeper/.gitignore create mode 100644 vendor/linesweeper/CHANGELOG.md create mode 100644 vendor/linesweeper/Cargo.lock create mode 100644 vendor/linesweeper/Cargo.toml create mode 100644 vendor/linesweeper/Cargo.toml.orig create mode 100644 vendor/linesweeper/LICENSE-APACHE create mode 100644 vendor/linesweeper/LICENSE-MIT create mode 100644 vendor/linesweeper/README.craft.md create mode 100644 vendor/linesweeper/README.md create mode 100644 vendor/linesweeper/SOURCE.json create mode 100644 vendor/linesweeper/benches/linebender_logo.rs create mode 100644 vendor/linesweeper/benches/painted_dreams.rs create mode 100644 vendor/linesweeper/benches/synthetic.rs create mode 100644 vendor/linesweeper/docs/sweep.typ create mode 100644 vendor/linesweeper/examples/boolean_op.rs create mode 100644 vendor/linesweeper/examples/bus.rs create mode 100644 vendor/linesweeper/examples/curve_metrics.rs create mode 100644 vendor/linesweeper/examples/visualize_op.rs create mode 100644 vendor/linesweeper/examples/winding-numbers.rs create mode 100644 vendor/linesweeper/proptest-regressions/sweep/sweep_line.txt create mode 100644 vendor/linesweeper/proptest-regressions/sweep/weak_ordering.txt create mode 100644 vendor/linesweeper/proptest-regressions/topology.txt create mode 100644 vendor/linesweeper/src/arbitrary.rs create mode 100644 vendor/linesweeper/src/curve/line.rs create mode 100644 vendor/linesweeper/src/curve/mod.rs create mode 100644 vendor/linesweeper/src/curve/split_quad.rs create mode 100644 vendor/linesweeper/src/curve/transverse.rs create mode 100644 vendor/linesweeper/src/generators.rs create mode 100644 vendor/linesweeper/src/geom.rs create mode 100644 vendor/linesweeper/src/lib.rs create mode 100644 vendor/linesweeper/src/num.rs create mode 100644 vendor/linesweeper/src/order.rs create mode 100644 vendor/linesweeper/src/perturbation.rs create mode 100644 vendor/linesweeper/src/position.rs create mode 100644 vendor/linesweeper/src/segments.rs create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__tests__empty_horizontals.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__tests__two_squares.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__diamond.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__inner_loop.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs_with_cut.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__nested_squares.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_and_diamond.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_with_double_back.snap create mode 100644 vendor/linesweeper/src/snapshots/linesweeper__topology__tests__squares_with_gaps.snap create mode 100644 vendor/linesweeper/src/sweep/mod.rs create mode 100644 vendor/linesweeper/src/sweep/output_event.rs create mode 100644 vendor/linesweeper/src/sweep/range.rs create mode 100644 vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__conservative_line_order.snap create mode 100644 vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__regression_position_state.snap create mode 100644 vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square.snap create mode 100644 vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square_and_diamond.snap create mode 100644 vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__two_squares.snap create mode 100644 vendor/linesweeper/src/sweep/sweep_line.rs create mode 100644 vendor/linesweeper/src/topology.rs create mode 100644 vendor/linesweeper/src/treevec.rs create mode 100644 vendor/linesweeper/src/typed_vec.rs create mode 100644 vendor/linesweeper/tests/fonts.rs create mode 100644 vendor/linesweeper/tests/paperjs.rs create mode 100644 vendor/linesweeper/tests/regression.rs create mode 100644 vendor/linesweeper/tests/regression/awesome_0.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_0.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_1.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_2.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_3.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_4.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_5.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_6.txt create mode 100644 vendor/linesweeper/tests/regression/graphite_7.txt create mode 100644 vendor/linesweeper/tests/regression/sasha_0.txt create mode 100644 vendor/linesweeper/tests/snapshots.rs diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 30386eac..0e85749f 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -12,6 +12,7 @@ author, source and license. Library code and original assets are under the repos | `crates/font-store/index/indexes/google-fonts.json.gz` (unmodified) | craft-font-index contributors; generated from the METADATA.pb files of [google/fonts](https://github.com/google/fonts) at `45e3d5269e4a17d823f6d84470bc084dc5ae46f6` | [aiongg/craft-font-index@5a9a7f8](https://github.com/aiongg/craft-font-index/tree/5a9a7f8038c8f3e2e1583a79c92a816e8e71b34f) | Apache-2.0 | | `crates/font-store/index/indexes/noto.json.gz` (unmodified) | craft-font-index contributors; generated from [notofonts/notofonts.github.io](https://github.com/notofonts/notofonts.github.io) at `93fac8abebe6c7fed0c5b7d2456d8fa484fa146c` and [noto-cjk](https://github.com/notofonts/noto-cjk) releases | [aiongg/craft-font-index@5a9a7f8](https://github.com/aiongg/craft-font-index/tree/5a9a7f8038c8f3e2e1583a79c92a816e8e71b34f) | Apache-2.0 | | `crates/cms/tests/data/FOGRA39.txt` (unmodified) | Fogra | [Fogra characterisation data](https://fogra.org/en/downloads/work-tools/characterisation-data), `FOGRA39_to_FOGRA60_v2.zip` | Fogra terms: free use and redistribution, unmodified, with Fogra identified as the source; "FOGRA39" identifies the reference data only and implies no certification, approval or endorsement by Fogra (full text in [NOTICE](NOTICE)) | +| `vendor/linesweeper/tests/regression/`, `vendor/linesweeper/proptest-regressions/`, the `.snap` files under `vendor/linesweeper/src/` and the path data in `vendor/linesweeper/benches/painted_dreams.rs` (all unmodified; hashes in `vendor/linesweeper/SOURCE.json`) | As published in the package, whose manifest names Joe Neeman as author and records no other source | [linesweeper 0.5.0](https://crates.io/crates/linesweeper/0.5.0) | MIT OR Apache-2.0 (the package's `license` field) | | `crates/i18n/data/collation.postcard` | Unicode, Inc. (ICU 78.1 collation data, CLDR 48.2.1); generated by `crates/i18n/data/generate-collation.sh` | [ICU](https://github.com/unicode-org/icu/releases/tag/release-78.1rc) | Unicode-3.0 | | `crates/text/assets/hyphen/en-us.dic` | Grady Ward (Moby Hyphenator II), converted by DesignCraft | [Project Gutenberg eBook #3204](https://www.gutenberg.org/ebooks/3204) | Public domain | | `crates/proof/data/en-words.dic` | Grady Ward (Moby Hyphenator II word list); break points dropped by craft-proof contributors with `crates/proof/examples/en_words.rs` | [Project Gutenberg eBook #3204](https://www.gutenberg.org/ebooks/3204), via craft-i18n `language-data/en-us/en-us.dic` | Public domain | diff --git a/Cargo.lock b/Cargo.lock index 9cb7a068..66344c38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3127,8 +3127,6 @@ checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" [[package]] name = "linesweeper" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25df1b0c5432893f41bba817150e323703ee5f6d974f3a6808ac7fd7f046d18" dependencies = [ "arrayvec", "kurbo 0.13.1", diff --git a/Cargo.toml b/Cargo.toml index 9c7b8ef2..f1c5adcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "3" members = ["crates/*"] -exclude = ["vendor/krilla", "vendor/hayro", "vendor/hayro-interpret", "vendor/hayro-syntax", "vendor/hayro-jbig2", "vendor/hayro-cmap", "vendor/hayro-postscript"] +exclude = ["vendor/krilla", "vendor/hayro", "vendor/hayro-interpret", "vendor/hayro-syntax", "vendor/hayro-jbig2", "vendor/hayro-cmap", "vendor/hayro-postscript", "vendor/linesweeper"] [workspace.package] version = "0.1.0" @@ -83,6 +83,7 @@ hayro-syntax = { path = "vendor/hayro-syntax" } hayro-jbig2 = { path = "vendor/hayro-jbig2" } hayro-cmap = { path = "vendor/hayro-cmap" } hayro-postscript = { path = "vendor/hayro-postscript" } +linesweeper = { path = "vendor/linesweeper" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/NOTICE b/NOTICE index 1073f48d..bd88d43e 100644 --- a/NOTICE +++ b/NOTICE @@ -215,6 +215,14 @@ Their licences and per-file hashes are retained in vendor/hayro-interpret/assets vendor/hayro-cmap/assets, licenses/pdf-backend and ATTRIBUTION.md. These are existing reader dependency assets; applications retain their own document-font selection and licensing policy. +linesweeper + +linesweeper 0.5.0 (https://crates.io/crates/linesweeper/0.5.0), MIT OR Apache-2.0, is stored in +vendor/linesweeper with its upstream license files and no file changed. Its manifest names Joe Neeman +as author, and its LICENSE-MIT reads "Copyright (c) 2018 Raph Levien". See +vendor/linesweeper/README.craft.md and vendor/linesweeper/SOURCE.json for provenance and the files +left out. + Platform lifecycle source provenance The optional crates/platform services adapt the preserved local platform candidate diff --git a/scripts/test-pdf-backends.sh b/scripts/test-pdf-backends.sh index cc31a7fa..d91192a4 100644 --- a/scripts/test-pdf-backends.sh +++ b/scripts/test-pdf-backends.sh @@ -16,3 +16,6 @@ for backend in hayro-syntax hayro-interpret hayro hayro-jbig2 hayro-cmap hayro-p done cargo test --manifest-path "$test_root/hayro-syntax/Cargo.toml" --doc "${patches[@]}" cargo test --manifest-path "$test_root/hayro-syntax/Cargo.toml" --lib --no-default-features --features std "${patches[@]}" +cp -R "$root/vendor/linesweeper" "$test_root/linesweeper" +cargo test --manifest-path "$test_root/linesweeper/Cargo.toml" --locked --lib --test regression +cargo test --manifest-path "$test_root/linesweeper/Cargo.toml" --locked --doc diff --git a/vendor/linesweeper/.cargo_vcs_info.json b/vendor/linesweeper/.cargo_vcs_info.json new file mode 100644 index 00000000..1f0e6dd3 --- /dev/null +++ b/vendor/linesweeper/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "824b0791a750f7b27b8edcbb6c6244d0e629875e" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/vendor/linesweeper/.gitignore b/vendor/linesweeper/.gitignore new file mode 100644 index 00000000..30b8ffd1 --- /dev/null +++ b/vendor/linesweeper/.gitignore @@ -0,0 +1,5 @@ +/target +docs/sweep.pdf +result +ignore +.config/selfci/local.yaml diff --git a/vendor/linesweeper/CHANGELOG.md b/vendor/linesweeper/CHANGELOG.md new file mode 100644 index 00000000..c8dfc1e2 --- /dev/null +++ b/vendor/linesweeper/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +## 0.5.0 - 2026-10-05 + +### Changed + +- Boolean operations now attempt to preserve quadratic segments: they will no + longer be automatically converted into cubics (issue 2774a82). + +### Fixed + +- Fixed a panic with polylines that have degenerate segments (issue da2b71b). +- Fixed a panic with intersections exactly at the end of segments (issue f8732c7). +- Fixed a panic (in slow-asserts mode only) from an ordering violation (issue df03063). +- Fixed an infinite loop (issue a140a5f). + +### Added + +- Added some convenience methods to `Contours` (patch d4a09be). + +## 0.4.0 - 2026-06-14 + +0.4.0 is the first release with a changelog entry; for previous releases, we +didn't have enough users to worry about it. + +### Changed + +- There is a new "approximate" output mode, which is now the default. Previous + versions of linesweeper provided strong correctness guarantees, but those + guarantees made some cases slow and caused more subdivisions than necessary. + Also, I think that those stronger guarantees are probably not needed for most + applications -- other boolean ops libraries don't provide them as far as I + know. So linesweeper now provides weaker guarantees by default. For the old + behaviour, see `Topology::contours_correct`. +- The winding direction conventions have changed; output curves have been + reversed, so that they agree with `kurbo`'s conventions. +- The `BinaryWindingNumber` API has changed; it now uses the `OneOfTwo` enum to + distinguish the two sets, instead of a `bool`. + +### Added + +- The new `Topology::has_normal_contours` and `Topology::is_disjoint_tags` allow + for detecting some no-op (or approximate no-op) situations. diff --git a/vendor/linesweeper/Cargo.lock b/vendor/linesweeper/Cargo.lock new file mode 100644 index 00000000..2e0cae69 --- /dev/null +++ b/vendor/linesweeper/Cargo.lock @@ -0,0 +1,1255 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arbtest" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3be567977128c0f71ad1462d9624ccda712193d124e944252f0c5789a06d46" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[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 = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gungraun" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b247b47ec86130ed355045982783d4666f32045e397a4547bbb6793cd53e940f" +dependencies = [ + "bincode", + "derive_more", + "gungraun-macros", + "gungraun-runner", +] + +[[package]] +name = "gungraun-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b605e561ccca36d68ebacd387751f2d58cf65202b781bb4a9029951dd3a66d" +dependencies = [ + "derive_more", + "proc-macro-error2", + "proc-macro2", + "quote", + "rustc_version", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "gungraun-runner" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ae6c9fe670d3d77f5576be41568ebf1733e5873c6ca85ff3b85848a31ac76" +dependencies = [ + "serde", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "i_float" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775f9961a8d2f879725da8aff789bb20a3ddf297473e0c90af75e69313919490" +dependencies = [ + "serde", +] + +[[package]] +name = "i_key_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" + +[[package]] +name = "i_overlay" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc99d863511ee3f7f8d7375c7f477314bd5a36bc409bb6c2ac2037939bb8391" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27dbe9e5238d6b9c694c08415bf00fb370b089949bd818ab01f41f8927b8774c" +dependencies = [ + "i_float", + "serde", +] + +[[package]] +name = "i_tree" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "insta" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "ron", + "serde", + "similar", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "kurbo" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +dependencies = [ + "arrayvec", + "euclid", + "serde", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libtest-mimic" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + +[[package]] +name = "linesweeper" +version = "0.5.0" +dependencies = [ + "anyhow", + "arbitrary", + "arbtest", + "arrayvec", + "assert_matches", + "clap", + "color", + "glam", + "glob", + "gungraun", + "i_overlay", + "insta", + "kurbo", + "libtest-mimic", + "polycool", + "proptest", + "rustc-hash", + "serde", + "serde_json", + "skrifa", + "smallvec", + "svg", + "tiny-skia", + "usvg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[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 = "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.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce8e2ca6b24313587a03ca61bb74c384e2a815bd90cf2866cfc9f5fb7a11fa0" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[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.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skrifa" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe6666ab11018ab91ff7b03f1a3b9fdbecfb610848436fefa5ce50343d3d913" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "svg" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "xmlwriter", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[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-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 = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/vendor/linesweeper/Cargo.toml b/vendor/linesweeper/Cargo.toml new file mode 100644 index 00000000..841a0e50 --- /dev/null +++ b/vendor/linesweeper/Cargo.toml @@ -0,0 +1,194 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "linesweeper" +version = "0.5.0" +authors = ["Joe Neeman "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Robust sweep-line algorithm and two-dimensional boolean ops" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/jneem/linesweeper" + +[features] +arbitrary = ["dep:arbitrary"] +debug-svg = ["dep:svg"] +generators = [] +slow-asserts = [] + +[lib] +name = "linesweeper" +path = "src/lib.rs" +bench = false + +[[example]] +name = "boolean_op" +path = "examples/boolean_op.rs" +required-features = ["generators"] + +[[example]] +name = "bus" +path = "examples/bus.rs" + +[[example]] +name = "curve_metrics" +path = "examples/curve_metrics.rs" + +[[example]] +name = "visualize_op" +path = "examples/visualize_op.rs" + +[[example]] +name = "winding-numbers" +path = "examples/winding-numbers.rs" + +[[test]] +name = "fonts" +path = "tests/fonts.rs" +harness = false + +[[test]] +name = "paperjs" +path = "tests/paperjs.rs" +harness = false + +[[test]] +name = "regression" +path = "tests/regression.rs" +harness = false + +[[test]] +name = "snapshots" +path = "tests/snapshots.rs" +harness = false + +[[bench]] +name = "linebender_logo" +path = "benches/linebender_logo.rs" +harness = false + +[[bench]] +name = "painted_dreams" +path = "benches/painted_dreams.rs" +harness = false + +[[bench]] +name = "synthetic" +path = "benches/synthetic.rs" +harness = false +required-features = ["generators"] + +[dependencies.arbitrary] +version = "1.4.1" +features = ["derive"] +optional = true + +[dependencies.arrayvec] +version = "0.7.6" +features = ["serde"] + +[dependencies.kurbo] +version = "0.13.0" +features = ["serde"] + +[dependencies.polycool] +version = "0.4.0" + +[dependencies.rustc-hash] +version = "2.1.1" + +[dependencies.smallvec] +version = "1.15.1" + +[dependencies.svg] +version = "0.18" +optional = true +default-features = false + +[dev-dependencies.anyhow] +version = "1.0.80" + +[dev-dependencies.arbitrary] +version = "1.4.1" +features = ["derive"] + +[dev-dependencies.arbtest] +version = "0.3.2" + +[dev-dependencies.assert_matches] +version = "1.5.0" + +[dev-dependencies.clap] +version = "4.5.1" +features = ["derive"] + +[dev-dependencies.color] +version = "0.3.2" + +[dev-dependencies.glam] +version = "0.29.0" + +[dev-dependencies.glob] +version = "0.3.2" + +[dev-dependencies.gungraun] +version = "0.17.0" + +[dev-dependencies.i_overlay] +version = "1.9.4" + +[dev-dependencies.insta] +version = "1.41.1" +features = [ + "serde", + "ron", +] + +[dev-dependencies.libtest-mimic] +version = "0.8.1" + +[dev-dependencies.proptest] +version = "1.4.0" + +[dev-dependencies.serde] +version = "1.0.216" +features = ["derive"] + +[dev-dependencies.serde_json] +version = "1.0" + +[dev-dependencies.skrifa] +version = "0.31.0" + +[dev-dependencies.svg] +version = "0.18" +default-features = false + +[dev-dependencies.tiny-skia] +version = "0.11.4" + +[dev-dependencies.usvg] +version = "0.46.0" +default-features = false + +[profile.bench] +debug = 2 +strip = false + +[profile.dev.package."*"] +opt-level = 2 diff --git a/vendor/linesweeper/Cargo.toml.orig b/vendor/linesweeper/Cargo.toml.orig new file mode 100644 index 00000000..ebd7cb79 --- /dev/null +++ b/vendor/linesweeper/Cargo.toml.orig @@ -0,0 +1,99 @@ +[package] +name = "linesweeper" +version = "0.5.0" +edition = "2024" +license = "MIT OR Apache-2.0" +authors = ["Joe Neeman "] +description = "Robust sweep-line algorithm and two-dimensional boolean ops" +repository = "https://github.com/jneem/linesweeper" + +[workspace] +members = ["fuzz", "util"] + +[workspace.dependencies] +kurbo = "0.13.0" +skrifa = "0.31.0" +svg = { version = "0.18", default-features = false } +usvg = { version = "0.46.0", default-features = false } + +[features] +arbitrary = ["dep:arbitrary"] +debug-svg = ["dep:svg"] +slow-asserts = [] +generators = [] + +[dependencies] +arbitrary = { version = "1.4.1", features = ["derive"], optional = true } +arrayvec = { version = "0.7.6", features = ["serde"] } +kurbo = { workspace = true, features = ["serde"] } +polycool = "0.4.0" +rustc-hash = "2.1.1" +smallvec = "1.15.1" +svg = { workspace = true, optional = true } + +[dev-dependencies] +anyhow = "1.0.80" +arbitrary = { version = "1.4.1", features = ["derive"] } +arbtest = "0.3.2" +assert_matches = "1.5.0" +clap = { version = "4.5.1", features = ["derive"] } +color = "0.3.2" +glam = "0.29.0" +glob = "0.3.2" +gungraun = "0.17.0" +i_overlay = "1.9.4" +insta = { version = "1.41.1", features = ["serde", "ron"] } +kompari = { git = "https://github.com/linebender/kompari" } +libtest-mimic = "0.8.1" +linesweeper_util = { path = "util" } +path-bool = { git = "https://github.com/GraphiteEditor/Graphite" } +proptest = "1.4.0" +serde = { version = "1.0.216", features = ["derive"] } +serde_json = "1.0" +skrifa.workspace = true +svg.workspace = true +tiny-skia = "0.11.4" +usvg.workspace = true + +[profile.dev.package."*"] +opt-level = 2 + +[profile.bench] +debug = true +strip = false + +[lib] +bench = false + +[[bench]] +name = "linebender_logo" +harness = false + +[[bench]] +name = "synthetic" +harness = false +required-features = ["generators"] + +[[bench]] +name = "painted_dreams" +harness = false + +[[example]] +name = "boolean_op" +required-features = ["generators"] + +[[test]] +name = "regression" +harness = false + +[[test]] +name = "snapshots" +harness = false + +[[test]] +name = "fonts" +harness = false + +[[test]] +name = "paperjs" +harness = false diff --git a/vendor/linesweeper/LICENSE-APACHE b/vendor/linesweeper/LICENSE-APACHE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/linesweeper/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/linesweeper/LICENSE-MIT b/vendor/linesweeper/LICENSE-MIT new file mode 100644 index 00000000..f3d84348 --- /dev/null +++ b/vendor/linesweeper/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Raph Levien + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/linesweeper/README.craft.md b/vendor/linesweeper/README.craft.md new file mode 100644 index 00000000..37ba499d --- /dev/null +++ b/vendor/linesweeper/README.craft.md @@ -0,0 +1,53 @@ +# Local linesweeper 0.5.0 copy + +This is the crates.io linesweeper 0.5.0 package (MIT OR Apache-2.0) with some files left out and no +file changed. `.cargo_vcs_info.json` names the commit it was packaged from, +`824b0791a750f7b27b8edcbb6c6244d0e629875e`. That commit is not in the GitHub repository the manifest +names, which is archived; its README says development moved off GitHub. + +Every file here except this one and `SOURCE.json` is byte for byte the file in the `.crate` archive. +`SOURCE.json` records the hash of the `.crate` download (`source_archive_sha256`) and, for every file +kept, the hash in the package (`source_sha256`) and the hash here (`sha256`). `upstream_repository` is +null because no public repository this copy can name holds `upstream_revision`. `local_patches` is +empty. + +`omitted_files` lists the 43 files of the package left out here, with their hashes: + +- `tests/fonts/Inconsolata-Regular.ttf`: font files live in craft-fonts. +- `docs/assets/logo.svg` and `examples/linebender.svg`: the package gives no license for this artwork + apart from its `license` field. +- `tests/snapshots/`: 12 SVG and 20 PNG files. +- `.config/`, `.helix/`, `.typos.toml`, `.gitmodules`, `flake.nix`, `flake.lock` and `nix/`: the + package's repository tooling. + +Only the targets listed under Tests read the font, the logos and the snapshot files, and those targets +do not compile from the published package. + +## Using it + +The directory is excluded from the workspace. With the root manifest's `[patch.crates-io]` entry, +`craft-prepress` builds its `flatten` feature against this copy. Cargo ignores a dependency's patches, +so a consuming workspace adds its own entry: + +```toml +[patch.crates-io] +linesweeper = { git = "https://github.com/storytold/craft-libs", rev = "" } +``` + +## Tests + +`scripts/test-pdf-backends.sh` runs the package's unit, regression and doc tests. The `craft-prepress` +tests with the `flatten` feature run against this copy. `pdf.yml`'s path filter does not list this +directory, so a pull request that changes only files here starts no workflow. Run the tests from this +directory: + +```sh +cargo test --locked --lib --test regression +cargo test --locked --doc +``` + +`Cargo.toml` leaves out the `kompari`, `linesweeper_util` and `path-bool` dev-dependencies, which +`Cargo.toml.orig` gives as git or path dependencies. The `fonts` and `snapshots` tests, the +`winding-numbers` and `boolean_op` examples and the `linebender_logo` bench do not compile here. + +Cargo writes to `target/` in this directory, which the root `.gitignore` excludes. diff --git a/vendor/linesweeper/README.md b/vendor/linesweeper/README.md new file mode 100644 index 00000000..d2529940 --- /dev/null +++ b/vendor/linesweeper/README.md @@ -0,0 +1,30 @@ +# linesweeper: a robust sweep-line algorithm + +[![Latest published version.](https://img.shields.io/crates/v/linesweeper.svg)](https://crates.io/crates/linesweeper) +[![Documentation build status.](https://img.shields.io/docsrs/linesweeper.svg)](https://docs.rs/linesweeper) + +This rust crate implements a "robust" version of the Bentley-Ottmann sweep-line +algorithm, and uses it to provide various two-dimensional geometric primitives +like boolean operations on sets bounded by Bézier paths. +It is currently in an early beta state. + +![Image demonstrating set operations applied to the linebender logo](docs/assets/logo.svg) + +## Basic usage + +To compute binary operations between curves, use the `binary_op` function, which +takes in two paths and computes some binary operation on them: + +```rust +use kurbo::Shape; +use linesweeper::{binary_op, FillRule, BinaryOp}; + +let square = kurbo::Rect::from_center_size((0.0, 0.0), (2.0, 2.0)).to_path(1e-6); +let circle = kurbo::Circle::new((0.0, 0.0), 1.2).to_path(1e-6); +binary_op(&square, &circle, FillRule::EvenOdd, BinaryOp::Intersection); +``` + +## Advanced usage + +For more advanced usage (like custom n-ary operations) see `Topology`, or +the example in `examples/bus.rs`. diff --git a/vendor/linesweeper/SOURCE.json b/vendor/linesweeper/SOURCE.json new file mode 100644 index 00000000..9c71e51a --- /dev/null +++ b/vendor/linesweeper/SOURCE.json @@ -0,0 +1,129 @@ +{ + "crate": "linesweeper", + "source_repository": "https://crates.io/api/v1/crates/linesweeper/0.5.0/download", + "source_revision": "0.5.0", + "source_directory": "linesweeper-0.5.0", + "source_archive_sha256": "a25df1b0c5432893f41bba817150e323703ee5f6d974f3a6808ac7fd7f046d18", + "upstream_repository": null, + "upstream_revision": "824b0791a750f7b27b8edcbb6c6244d0e629875e", + "upstream_directory": "", + "local_patches": [], + "files": [ + { "path": ".cargo_vcs_info.json", "source_sha256": "47fba6a9dbdf445a5c97f783c9be62093035f9d5e5cdc2cf3216ad8259bcd8fc", "source_git_blob": null, "sha256": "47fba6a9dbdf445a5c97f783c9be62093035f9d5e5cdc2cf3216ad8259bcd8fc" }, + { "path": ".gitignore", "source_sha256": "88b59092499ae05234330eb7d0b01e953e969caf05261530e3765eef02536cf0", "source_git_blob": null, "sha256": "88b59092499ae05234330eb7d0b01e953e969caf05261530e3765eef02536cf0" }, + { "path": "CHANGELOG.md", "source_sha256": "70df2144c3ab6858e3857e159590116f7859f3b077ac60fd4a2f510707894dc7", "source_git_blob": null, "sha256": "70df2144c3ab6858e3857e159590116f7859f3b077ac60fd4a2f510707894dc7" }, + { "path": "Cargo.lock", "source_sha256": "42d3aea0c577080ff5a23e3213a05a9ca9f0437584ac3f75aeb6d29941417f0c", "source_git_blob": null, "sha256": "42d3aea0c577080ff5a23e3213a05a9ca9f0437584ac3f75aeb6d29941417f0c" }, + { "path": "Cargo.toml", "source_sha256": "2f925d646204059bb3b4e980704b04e9accc5184d9dd24e3ef9ed1751ac6095b", "source_git_blob": null, "sha256": "2f925d646204059bb3b4e980704b04e9accc5184d9dd24e3ef9ed1751ac6095b" }, + { "path": "Cargo.toml.orig", "source_sha256": "9ce4a22c92543eb8a2caa9e59ec1e8933c8975f12bad6fef731a0c0fe9c34ced", "source_git_blob": null, "sha256": "9ce4a22c92543eb8a2caa9e59ec1e8933c8975f12bad6fef731a0c0fe9c34ced" }, + { "path": "LICENSE-APACHE", "source_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", "source_git_blob": null, "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30" }, + { "path": "LICENSE-MIT", "source_sha256": "23c23145f6eac25c28cdcc03d38506a5712a9a8cffb5bd5301457f6173ea696e", "source_git_blob": null, "sha256": "23c23145f6eac25c28cdcc03d38506a5712a9a8cffb5bd5301457f6173ea696e" }, + { "path": "README.md", "source_sha256": "cb35a926cacf52c4d423ec192c9c78e5a1f73057a3658f1b9c47f43d2718cb9e", "source_git_blob": null, "sha256": "cb35a926cacf52c4d423ec192c9c78e5a1f73057a3658f1b9c47f43d2718cb9e" }, + { "path": "benches/linebender_logo.rs", "source_sha256": "d10867f43439d37f0e1da5d30eaed9ab3bfbe655ed47212f65dc20ab4db65cb3", "source_git_blob": null, "sha256": "d10867f43439d37f0e1da5d30eaed9ab3bfbe655ed47212f65dc20ab4db65cb3" }, + { "path": "benches/painted_dreams.rs", "source_sha256": "073008db93840d380710e13ed3ae38e08cb823f10c12e5f838dac1ccf37eaecf", "source_git_blob": null, "sha256": "073008db93840d380710e13ed3ae38e08cb823f10c12e5f838dac1ccf37eaecf" }, + { "path": "benches/synthetic.rs", "source_sha256": "7ce59bef2e1c4f28b00cb89feb15bc71ca4b098a8d27e7b02ea699aca9bcc810", "source_git_blob": null, "sha256": "7ce59bef2e1c4f28b00cb89feb15bc71ca4b098a8d27e7b02ea699aca9bcc810" }, + { "path": "docs/sweep.typ", "source_sha256": "4766082c8672a7bb4356b5581043489c13b4e3fb8931ba3a5f0a473fab0172f3", "source_git_blob": null, "sha256": "4766082c8672a7bb4356b5581043489c13b4e3fb8931ba3a5f0a473fab0172f3" }, + { "path": "examples/boolean_op.rs", "source_sha256": "3b38a1ccda7e736cce4b191a3a45ab4824c4c365190e3eb637b2fb39b5996c9f", "source_git_blob": null, "sha256": "3b38a1ccda7e736cce4b191a3a45ab4824c4c365190e3eb637b2fb39b5996c9f" }, + { "path": "examples/bus.rs", "source_sha256": "e206a08be73899f564188052c8ecc15526b54606e52ee2935a64ce6b5723da33", "source_git_blob": null, "sha256": "e206a08be73899f564188052c8ecc15526b54606e52ee2935a64ce6b5723da33" }, + { "path": "examples/curve_metrics.rs", "source_sha256": "0db6a5327fd2119a03a9a66624fea3e04b6bbb519655667ad30487aee6c787d2", "source_git_blob": null, "sha256": "0db6a5327fd2119a03a9a66624fea3e04b6bbb519655667ad30487aee6c787d2" }, + { "path": "examples/visualize_op.rs", "source_sha256": "a5210ed389ceb1e9c43b952b992e29e65065d129d2b2f4be62fffc747376bf79", "source_git_blob": null, "sha256": "a5210ed389ceb1e9c43b952b992e29e65065d129d2b2f4be62fffc747376bf79" }, + { "path": "examples/winding-numbers.rs", "source_sha256": "4a756c908564703b165257001e73d2f7235476cd536580091f3a7a70d2eab242", "source_git_blob": null, "sha256": "4a756c908564703b165257001e73d2f7235476cd536580091f3a7a70d2eab242" }, + { "path": "proptest-regressions/sweep/sweep_line.txt", "source_sha256": "f5311edf1c0c4d1574bb7b24ed1d1c66106d9949214014d0a8479f685938dc98", "source_git_blob": null, "sha256": "f5311edf1c0c4d1574bb7b24ed1d1c66106d9949214014d0a8479f685938dc98" }, + { "path": "proptest-regressions/sweep/weak_ordering.txt", "source_sha256": "b5b183ab470399c15246457d8a1a35cafb49fee120d6777dfdd563bfdbadda52", "source_git_blob": null, "sha256": "b5b183ab470399c15246457d8a1a35cafb49fee120d6777dfdd563bfdbadda52" }, + { "path": "proptest-regressions/topology.txt", "source_sha256": "49ff0b5b370f082984276d5312f03b1fc96ef0dd218f9efd7b084b11abbb9d19", "source_git_blob": null, "sha256": "49ff0b5b370f082984276d5312f03b1fc96ef0dd218f9efd7b084b11abbb9d19" }, + { "path": "src/arbitrary.rs", "source_sha256": "9c3753a40cfea2528a546295d39e0513537db5a3411a931fccea063873d95750", "source_git_blob": null, "sha256": "9c3753a40cfea2528a546295d39e0513537db5a3411a931fccea063873d95750" }, + { "path": "src/curve/line.rs", "source_sha256": "07aebead4b0050410a3f9f28b66062b6016c115df58e2b2669470263cd352f3e", "source_git_blob": null, "sha256": "07aebead4b0050410a3f9f28b66062b6016c115df58e2b2669470263cd352f3e" }, + { "path": "src/curve/mod.rs", "source_sha256": "36a8dbe96ac1d3bec307dc63b504206ec7fdfc11d10799fbb1a8990c29b8c5f2", "source_git_blob": null, "sha256": "36a8dbe96ac1d3bec307dc63b504206ec7fdfc11d10799fbb1a8990c29b8c5f2" }, + { "path": "src/curve/split_quad.rs", "source_sha256": "b8aae4ce7c16fa623724f667e1058130a9c7eed693b5b01e8b9e16632b8ec45f", "source_git_blob": null, "sha256": "b8aae4ce7c16fa623724f667e1058130a9c7eed693b5b01e8b9e16632b8ec45f" }, + { "path": "src/curve/transverse.rs", "source_sha256": "be9c695531f4cb0fe73af13e78040b51b9476c193d84562e701aadd07b3fd06e", "source_git_blob": null, "sha256": "be9c695531f4cb0fe73af13e78040b51b9476c193d84562e701aadd07b3fd06e" }, + { "path": "src/generators.rs", "source_sha256": "cc9f2441546db9204219294c354449809dda77dffad06fec8a30f45df01e6fef", "source_git_blob": null, "sha256": "cc9f2441546db9204219294c354449809dda77dffad06fec8a30f45df01e6fef" }, + { "path": "src/geom.rs", "source_sha256": "9878ffa6d4a13fa2c9149741c137205ca01acb626a5b9f86a3db75cf91b63a08", "source_git_blob": null, "sha256": "9878ffa6d4a13fa2c9149741c137205ca01acb626a5b9f86a3db75cf91b63a08" }, + { "path": "src/lib.rs", "source_sha256": "761c153c3b5c449569ff85c571344b9252ea64f7f0b0424fb4819041b03d13c6", "source_git_blob": null, "sha256": "761c153c3b5c449569ff85c571344b9252ea64f7f0b0424fb4819041b03d13c6" }, + { "path": "src/num.rs", "source_sha256": "39ff85da50c500ce91cf6dea81aedc0dcca1db5e7d0506696dde2da118e93a2a", "source_git_blob": null, "sha256": "39ff85da50c500ce91cf6dea81aedc0dcca1db5e7d0506696dde2da118e93a2a" }, + { "path": "src/order.rs", "source_sha256": "7e5b04eae8af134abb032e995f1d3c2ac473822923dfee8d463d9ade6ffeb596", "source_git_blob": null, "sha256": "7e5b04eae8af134abb032e995f1d3c2ac473822923dfee8d463d9ade6ffeb596" }, + { "path": "src/perturbation.rs", "source_sha256": "fa274a822bbdf65f8e53e403eef78034dd87964b3f1d3134a064df4ec8cef9c5", "source_git_blob": null, "sha256": "fa274a822bbdf65f8e53e403eef78034dd87964b3f1d3134a064df4ec8cef9c5" }, + { "path": "src/position.rs", "source_sha256": "dfa70840805ecccf5b7eb6122921e46ca60c24cb7b3c17f3b55ffc2549368ab0", "source_git_blob": null, "sha256": "dfa70840805ecccf5b7eb6122921e46ca60c24cb7b3c17f3b55ffc2549368ab0" }, + { "path": "src/segments.rs", "source_sha256": "379f9455120492d237cb5a62b3fc06da018a72e2eb75bbfcd61b4cce501d9bbc", "source_git_blob": null, "sha256": "379f9455120492d237cb5a62b3fc06da018a72e2eb75bbfcd61b4cce501d9bbc" }, + { "path": "src/snapshots/linesweeper__tests__empty_horizontals.snap", "source_sha256": "e319881853e889badf6524f19af4cd6f112e8dd71bdc516826f4463134a82244", "source_git_blob": null, "sha256": "e319881853e889badf6524f19af4cd6f112e8dd71bdc516826f4463134a82244" }, + { "path": "src/snapshots/linesweeper__tests__two_squares.snap", "source_sha256": "01961e0311a5af3b8f13eb52a2a7112560c47cf86771c04e40475dc6f15a0846", "source_git_blob": null, "sha256": "01961e0311a5af3b8f13eb52a2a7112560c47cf86771c04e40475dc6f15a0846" }, + { "path": "src/snapshots/linesweeper__topology__tests__diamond.snap", "source_sha256": "cfaefd620a0a3d1376861bca09cd7c658ef00a32635b88f1b259a9b39cd15502", "source_git_blob": null, "sha256": "cfaefd620a0a3d1376861bca09cd7c658ef00a32635b88f1b259a9b39cd15502" }, + { "path": "src/snapshots/linesweeper__topology__tests__inner_loop.snap", "source_sha256": "d6259594f87cc91bf015e9086df94f000c265164d6d361beda4ef0e422947d91", "source_git_blob": null, "sha256": "d6259594f87cc91bf015e9086df94f000c265164d6d361beda4ef0e422947d91" }, + { "path": "src/snapshots/linesweeper__topology__tests__merge_monotonic_segs.snap", "source_sha256": "871db61c9bf44a5d50cd63f3682c72ac19b1ae5d4752d911ac75a2e94c9f8cf5", "source_git_blob": null, "sha256": "871db61c9bf44a5d50cd63f3682c72ac19b1ae5d4752d911ac75a2e94c9f8cf5" }, + { "path": "src/snapshots/linesweeper__topology__tests__merge_monotonic_segs_with_cut.snap", "source_sha256": "0c67eb18012af31d9d5a46ce16e49f3033a4a7bd986bdc41d14cb3d4ce7adf39", "source_git_blob": null, "sha256": "0c67eb18012af31d9d5a46ce16e49f3033a4a7bd986bdc41d14cb3d4ce7adf39" }, + { "path": "src/snapshots/linesweeper__topology__tests__nested_squares.snap", "source_sha256": "212e53e762e633ae8316ce1ba16d94c4f6608573dac6ec3325bf7a9e592c6f8e", "source_git_blob": null, "sha256": "212e53e762e633ae8316ce1ba16d94c4f6608573dac6ec3325bf7a9e592c6f8e" }, + { "path": "src/snapshots/linesweeper__topology__tests__square.snap", "source_sha256": "c0835f86603b9f3aabcb0da22c1dcf597c6575ce9ca72d5ae95bf125f40020e8", "source_git_blob": null, "sha256": "c0835f86603b9f3aabcb0da22c1dcf597c6575ce9ca72d5ae95bf125f40020e8" }, + { "path": "src/snapshots/linesweeper__topology__tests__square_and_diamond.snap", "source_sha256": "6dffc3dc182e133222481d562de20b5e9e39d0ee343088c42f29868d97d1b304", "source_git_blob": null, "sha256": "6dffc3dc182e133222481d562de20b5e9e39d0ee343088c42f29868d97d1b304" }, + { "path": "src/snapshots/linesweeper__topology__tests__square_with_double_back.snap", "source_sha256": "b6709e07eb96d1148269caa9ea39663bb432524e892fffe10f784fa44edb29e0", "source_git_blob": null, "sha256": "b6709e07eb96d1148269caa9ea39663bb432524e892fffe10f784fa44edb29e0" }, + { "path": "src/snapshots/linesweeper__topology__tests__squares_with_gaps.snap", "source_sha256": "52a5e09ecd081f7ace976692a5d5e3549210441f78ad1dc25cd98a4d9d5dceb6", "source_git_blob": null, "sha256": "52a5e09ecd081f7ace976692a5d5e3549210441f78ad1dc25cd98a4d9d5dceb6" }, + { "path": "src/sweep/mod.rs", "source_sha256": "467f783e605be9c5959b10adc3ff53966ca0e95599b3b509bfacb154e82b9257", "source_git_blob": null, "sha256": "467f783e605be9c5959b10adc3ff53966ca0e95599b3b509bfacb154e82b9257" }, + { "path": "src/sweep/output_event.rs", "source_sha256": "220dc6847871ffcd0f782122b55db0a47522866892fb0fb5dd4255cea975fc83", "source_git_blob": null, "sha256": "220dc6847871ffcd0f782122b55db0a47522866892fb0fb5dd4255cea975fc83" }, + { "path": "src/sweep/range.rs", "source_sha256": "924274de47783179f52cea99b370297e8c8d5f05500df4a6ff31e9b927dadd06", "source_git_blob": null, "sha256": "924274de47783179f52cea99b370297e8c8d5f05500df4a6ff31e9b927dadd06" }, + { "path": "src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__conservative_line_order.snap", "source_sha256": "236d020f646edc3ab199888fd52a0e7d9939bbb1d8b13b6fed52ce508c0e6667", "source_git_blob": null, "sha256": "236d020f646edc3ab199888fd52a0e7d9939bbb1d8b13b6fed52ce508c0e6667" }, + { "path": "src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__regression_position_state.snap", "source_sha256": "be9535fe81604b59e369e6e2c61a67e0203429166dd23e51cd90c644fa3e09db", "source_git_blob": null, "sha256": "be9535fe81604b59e369e6e2c61a67e0203429166dd23e51cd90c644fa3e09db" }, + { "path": "src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square.snap", "source_sha256": "b35c3e6a38d5ecdd8acf315ba0b0bd6485cd8b40193fdd76bd446bdfdb6eda77", "source_git_blob": null, "sha256": "b35c3e6a38d5ecdd8acf315ba0b0bd6485cd8b40193fdd76bd446bdfdb6eda77" }, + { "path": "src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square_and_diamond.snap", "source_sha256": "3f01a76fb7ff4aef0fd55fd869602d4a2b69fff1bd65632f406b275b5d6af600", "source_git_blob": null, "sha256": "3f01a76fb7ff4aef0fd55fd869602d4a2b69fff1bd65632f406b275b5d6af600" }, + { "path": "src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__two_squares.snap", "source_sha256": "79e3c95e563a66c0ce244125470a106a18dfe29f276b732defd305406c873c7e", "source_git_blob": null, "sha256": "79e3c95e563a66c0ce244125470a106a18dfe29f276b732defd305406c873c7e" }, + { "path": "src/sweep/sweep_line.rs", "source_sha256": "822b261f682911ea0c0370eaf17d9201a1e639b315359dcb6a3b877a229816e3", "source_git_blob": null, "sha256": "822b261f682911ea0c0370eaf17d9201a1e639b315359dcb6a3b877a229816e3" }, + { "path": "src/topology.rs", "source_sha256": "9c9b48bc3853884425127e2d64e8ed957ba0d27e1696e2c457e95282e52d6a5b", "source_git_blob": null, "sha256": "9c9b48bc3853884425127e2d64e8ed957ba0d27e1696e2c457e95282e52d6a5b" }, + { "path": "src/treevec.rs", "source_sha256": "d88b9fbf02fa9f318560e1d483d465555f2b696ae12c00f8e4f5b71ea6779445", "source_git_blob": null, "sha256": "d88b9fbf02fa9f318560e1d483d465555f2b696ae12c00f8e4f5b71ea6779445" }, + { "path": "src/typed_vec.rs", "source_sha256": "3e9a2675b8a40d8fc4df0575bd9d0d16b5604d18e9b78638dd5a612b28fc4c30", "source_git_blob": null, "sha256": "3e9a2675b8a40d8fc4df0575bd9d0d16b5604d18e9b78638dd5a612b28fc4c30" }, + { "path": "tests/fonts.rs", "source_sha256": "081604ebf560a19ff0427efd9a17eaaa0a3b46ad0bf58323fd6e21b6c0764400", "source_git_blob": null, "sha256": "081604ebf560a19ff0427efd9a17eaaa0a3b46ad0bf58323fd6e21b6c0764400" }, + { "path": "tests/paperjs.rs", "source_sha256": "6ff2a3178f13cde3d7304101ae592ae32b4b2734069f5fc56a532a4883d9f782", "source_git_blob": null, "sha256": "6ff2a3178f13cde3d7304101ae592ae32b4b2734069f5fc56a532a4883d9f782" }, + { "path": "tests/regression.rs", "source_sha256": "b2341934aa001ba26c688a1ca3a7ee55d29105d67b6036bb05bb367b69976fce", "source_git_blob": null, "sha256": "b2341934aa001ba26c688a1ca3a7ee55d29105d67b6036bb05bb367b69976fce" }, + { "path": "tests/regression/awesome_0.txt", "source_sha256": "6f7dfbb7ca62463843c058617d557553fdc8d8b3add512bee8f813b37a256611", "source_git_blob": null, "sha256": "6f7dfbb7ca62463843c058617d557553fdc8d8b3add512bee8f813b37a256611" }, + { "path": "tests/regression/graphite_0.txt", "source_sha256": "f5d9e94ae6fab7d29f8a3aa0ccd9d96fc9b709c0ee3fbae8b000bec8c33628b9", "source_git_blob": null, "sha256": "f5d9e94ae6fab7d29f8a3aa0ccd9d96fc9b709c0ee3fbae8b000bec8c33628b9" }, + { "path": "tests/regression/graphite_1.txt", "source_sha256": "96f147b6fc95a71600f60541cc687598e04e9d810dd8a3c57b0f5d8220a72823", "source_git_blob": null, "sha256": "96f147b6fc95a71600f60541cc687598e04e9d810dd8a3c57b0f5d8220a72823" }, + { "path": "tests/regression/graphite_2.txt", "source_sha256": "7c9e32b68efe68bf238ca9f42a5666cc1b792e8f8bfece9a7c128c08734f4072", "source_git_blob": null, "sha256": "7c9e32b68efe68bf238ca9f42a5666cc1b792e8f8bfece9a7c128c08734f4072" }, + { "path": "tests/regression/graphite_3.txt", "source_sha256": "46f66d5d9f753386df2f163dfcda8b40e727350f623e4e2fb309a094912c82ce", "source_git_blob": null, "sha256": "46f66d5d9f753386df2f163dfcda8b40e727350f623e4e2fb309a094912c82ce" }, + { "path": "tests/regression/graphite_4.txt", "source_sha256": "84f07e36e3af11f8946ce00890cf396dca15744c195d51ea173f48f354732245", "source_git_blob": null, "sha256": "84f07e36e3af11f8946ce00890cf396dca15744c195d51ea173f48f354732245" }, + { "path": "tests/regression/graphite_5.txt", "source_sha256": "299d27228f1d22fd1c8d0be895357766a948165def542d54ebc19933361b277a", "source_git_blob": null, "sha256": "299d27228f1d22fd1c8d0be895357766a948165def542d54ebc19933361b277a" }, + { "path": "tests/regression/graphite_6.txt", "source_sha256": "0be4c7579159d43f9a9dc04bbd3341b5ac5fc3e49f3518b278dda23506069301", "source_git_blob": null, "sha256": "0be4c7579159d43f9a9dc04bbd3341b5ac5fc3e49f3518b278dda23506069301" }, + { "path": "tests/regression/graphite_7.txt", "source_sha256": "da02afc16a0336680a65508b4f6a13df5a9af04f05a49c66bd1cfd6975827719", "source_git_blob": null, "sha256": "da02afc16a0336680a65508b4f6a13df5a9af04f05a49c66bd1cfd6975827719" }, + { "path": "tests/regression/sasha_0.txt", "source_sha256": "b4eb70030ce7f8eae68f074e2ce93edb60ec1e6f879a3d8238d58447a9961948", "source_git_blob": null, "sha256": "b4eb70030ce7f8eae68f074e2ce93edb60ec1e6f879a3d8238d58447a9961948" }, + { "path": "tests/snapshots.rs", "source_sha256": "634fdebb7d7c7c70a1b47431a9407be640ea19d6cec79f200555bf2cd2b2dde0", "source_git_blob": null, "sha256": "634fdebb7d7c7c70a1b47431a9407be640ea19d6cec79f200555bf2cd2b2dde0" } + ], + "omitted_files": [ + { "path": ".config/selfci/ci.yaml", "source_sha256": "3941bf98a6cffce943f7a041ab520626f853090f1e8bd0e2c3df2672c5fa064f" }, + { "path": ".gitmodules", "source_sha256": "704763bd0f97af4af5f5fad27e9a4a6d6d1fe77c375cf6d5f917dd089e069fb3" }, + { "path": ".helix/languages.toml", "source_sha256": "194817d23a58f1b399040b223e9da175433bb21ab20785a011c44524475598c6" }, + { "path": ".typos.toml", "source_sha256": "8cba77a39a9e49ba79635a7da8aa0592072806590aa28c27194b9ba5d53ed6fb" }, + { "path": "docs/assets/logo.svg", "source_sha256": "79857f5989a6310e6990e41e171dbb8ec2acfc6d6b28383d2ef4c844bbc758a4" }, + { "path": "examples/linebender.svg", "source_sha256": "3fe69ac9aeeb62011157e96ea4185e016574b24489606010b97dc7eab0920679" }, + { "path": "flake.lock", "source_sha256": "244d67f7aae447190e6c15fdfc9132c190f97568f2c67dee1d9a2e06982c9b1f" }, + { "path": "flake.nix", "source_sha256": "43c8c31bb919717276309ae7e3d7e329dbe641002669177d6f42fcc7ebdf24d5" }, + { "path": "nix/gungraun-runner.nix", "source_sha256": "72701531fbd34ee859b2e467f2c7e47ee5f4ed3a2c938c07ba66fe5911c37537" }, + { "path": "nix/kompari.nix", "source_sha256": "e22dbf5db27c79f423855571f11cc47dd90defe17d8e32423571cf7da3956174" }, + { "path": "tests/fonts/Inconsolata-Regular.ttf", "source_sha256": "0f11ac40d1618e1aa1adb7322a14980a0b0c943421124022073f83236d15f0d5" }, + { "path": "tests/snapshots/inputs/output/merge_back.svg", "source_sha256": "2a41a52604734f49b89b04e5895e02d31554bfce4dce84519553316663b07169" }, + { "path": "tests/snapshots/inputs/position/close_ranges.svg", "source_sha256": "57634495e5922d24e3dde387f3cdf0d741a5724ecc55ff53da2713e11049df38" }, + { "path": "tests/snapshots/inputs/position/do_not_cancel.svg", "source_sha256": "21d05393ced07150213bfa4a8ab459e95fdd23724b4691cd75e33aa53e03f44c" }, + { "path": "tests/snapshots/inputs/position/more_overlapping_close_ranges.svg", "source_sha256": "dd71994d18fe9a664a863f8d69e770fe9c0a939bb74e6990b8f12ce946fb0c67" }, + { "path": "tests/snapshots/inputs/position/near_horizontal_approximation.svg", "source_sha256": "38f1a168754fee189248bf18eef0826b6b12c4af26a346a08df75390d23c56be" }, + { "path": "tests/snapshots/inputs/position/overlapping_close_ranges.svg", "source_sha256": "ec6fd75e4f42a48f460705d601daa4294062753dbb7fb62b682ea05fd071f623" }, + { "path": "tests/snapshots/inputs/position/transverse.svg", "source_sha256": "1e55f07b3fd6aa738bb5c412f833bdd041d6a6259a5f08635d6278301746687b" }, + { "path": "tests/snapshots/inputs/position/transverse_triangle.svg", "source_sha256": "22e09ffc98035e81b081c03ff978734b09baeca3f24efb29319c7f2f229f3c76" }, + { "path": "tests/snapshots/inputs/sweep/just_cross.svg", "source_sha256": "f0fbd780f2272be21080f3570b6c915c5da172ae39efbc86afd30d9d8713e20e" }, + { "path": "tests/snapshots/inputs/sweep/just_touch.svg", "source_sha256": "3d6116a9849c9e5b6881a868d24fa617c772f9ffdeddb4328fa2b5024306c42c" }, + { "path": "tests/snapshots/inputs/sweep/square_diamond_cross.svg", "source_sha256": "aacfe86de2509680eddfba260c8c3782e1e6980ffde321e628106257975e6ee3" }, + { "path": "tests/snapshots/inputs/sweep/square_diamond_touch.svg", "source_sha256": "eefe49aa759bc8dcc811c46b6c5b36ebbed2e6efaba14c0c513265d76abd7f1f" }, + { "path": "tests/snapshots/snapshots/correct/output/merge_back.png", "source_sha256": "edf5d1fe510003e2d2099351c35f6af709d54457b3087f3dd6803df5d78482bf" }, + { "path": "tests/snapshots/snapshots/correct/position/close_ranges.png", "source_sha256": "902613b28f47e8619df72f389190d5b9673ba1197a817988b9a0afb52e901a83" }, + { "path": "tests/snapshots/snapshots/correct/position/do_not_cancel.png", "source_sha256": "ce8ceb7bc8ebb812440bcdc8f1100b4062b74d469b741dfa2fcf67261b9c7ee3" }, + { "path": "tests/snapshots/snapshots/correct/position/more_overlapping_close_ranges.png", "source_sha256": "3d7b5d648a3d6591687f4161419cd26ef88d92677d5abee357ce1e6d81d748ad" }, + { "path": "tests/snapshots/snapshots/correct/position/near_horizontal_approximation.png", "source_sha256": "21f275cd6a16ab81c4f297c8c2bcf1e1ed60d49ac8cee44299ef7e5944fd14c6" }, + { "path": "tests/snapshots/snapshots/correct/position/overlapping_close_ranges.png", "source_sha256": "4bf65eea3df7e4d609adf1b60f7c37335bdbdfaaeca08c10e50002bacdf49b16" }, + { "path": "tests/snapshots/snapshots/correct/position/transverse.png", "source_sha256": "8a49c69b088b56ae0df17f5e2f2bd2e063d11cbf50e21e34fbd75fd6c58e22aa" }, + { "path": "tests/snapshots/snapshots/correct/position/transverse_triangle.png", "source_sha256": "4a5a1afc404308f7a1cbeb7e8ed72ace41c892f6847669c5ff7f78c422088428" }, + { "path": "tests/snapshots/snapshots/output/merge_back.png", "source_sha256": "071563db38f07c93bb9f70cb45a3976d140bc188751d07a17c344ebfb1f57b49" }, + { "path": "tests/snapshots/snapshots/position/close_ranges.png", "source_sha256": "8f41f087d9779932a3be26be29e2912eb174b78cb859ecf99f2e9756cc810620" }, + { "path": "tests/snapshots/snapshots/position/do_not_cancel.png", "source_sha256": "2366066007ee5b26d075f5f3107432d0f6d0eb81089b75604df2880a31d725de" }, + { "path": "tests/snapshots/snapshots/position/more_overlapping_close_ranges.png", "source_sha256": "d77b06c175765dd4d29f28106c56a7aaa7feb582a0d88468d04d26ea24184e17" }, + { "path": "tests/snapshots/snapshots/position/near_horizontal_approximation.png", "source_sha256": "855311f5cdaff8cc02f6e44c4bf77b0b1d69d77d1a31f9623976680b9a9111e4" }, + { "path": "tests/snapshots/snapshots/position/overlapping_close_ranges.png", "source_sha256": "14dd185567f7732a4e518a0f3714ff9cd1831fc2e042eafc110b85e0ca3b4f1b" }, + { "path": "tests/snapshots/snapshots/position/transverse.png", "source_sha256": "0f564234c7dc5a25cdb05e1848445d2ca46b20b18732390f0960de421784a1bc" }, + { "path": "tests/snapshots/snapshots/position/transverse_triangle.png", "source_sha256": "a1e98a59561505d8497548f28ed31913b23908d7bde4bf06b533009e317fc924" }, + { "path": "tests/snapshots/snapshots/sweep/just_cross.png", "source_sha256": "f66ee5cbad2129bead4a35a63924f7c016b9a6c21f3c659118821294f27f8879" }, + { "path": "tests/snapshots/snapshots/sweep/just_touch.png", "source_sha256": "a33d963ceb32e7d3f1cffc013564303b96a1e02c68f51041687d7831ed0359b3" }, + { "path": "tests/snapshots/snapshots/sweep/square_diamond_cross.png", "source_sha256": "63e097c08279cbb0efad3813641e5a70924af16a69ac9893ca1727f256c6f762" }, + { "path": "tests/snapshots/snapshots/sweep/square_diamond_touch.png", "source_sha256": "8d0b7f9adbba5f7c35745466357b814dc11e46b0c0662bbdfec229bd4bc1e0b5" } + ] +} diff --git a/vendor/linesweeper/benches/linebender_logo.rs b/vendor/linesweeper/benches/linebender_logo.rs new file mode 100644 index 00000000..19a19483 --- /dev/null +++ b/vendor/linesweeper/benches/linebender_logo.rs @@ -0,0 +1,70 @@ +use gungraun::{library_benchmark, library_benchmark_group, main}; +use std::hint::black_box; + +use kurbo::BezPath; +use linesweeper::{BinaryOp, FillRule, Segments, binary_op, topology::Topology}; +use linesweeper_util::svg_to_bezpaths; + +fn linebender_path() -> BezPath { + let input = include_str!("../examples/linebender.svg"); + let tree = usvg::Tree::from_str(input, &usvg::Options::default()).unwrap(); + svg_to_bezpaths(&tree) + .into_iter() + .flatten() + .collect::() +} + +fn linebender_paths() -> (BezPath, BezPath) { + let input = include_str!("../examples/linebender.svg"); + let tree = usvg::Tree::from_str(input, &usvg::Options::default()).unwrap(); + let bezpaths = svg_to_bezpaths(&tree); + + let first_path = bezpaths.first().unwrap().clone(); + let second_path = bezpaths[1..] + .iter() + .flat_map(|b| b.elements().iter().cloned()) + .collect(); + (first_path, second_path) +} + +fn linebender_segments() -> Segments { + let mut segs = Segments::default(); + segs.add_bez_path(&linebender_path()).unwrap(); + segs +} + +#[library_benchmark] +#[bench::bench(setup = linebender_segments)] +fn just_the_sweep(segs: Segments) { + // If we don't collect the events, it seems like nothing gets measured (even + // sprinkling black_box around). + let mut events = Vec::new(); + linesweeper::sweep::sweep(&segs, 0.01, |_, ev| { + events.push(ev); + }); + black_box(events); +} + +#[library_benchmark] +#[bench::bench(setup = linebender_path)] +fn build_topology(path: BezPath) { + black_box(Topology::from_paths_binary(&path, &BezPath::new(), 0.01)).unwrap(); +} + +#[library_benchmark] +#[bench::bench(setup = linebender_paths)] +fn xor_linesweeper((first_path, second_path): (BezPath, BezPath)) { + black_box(binary_op( + &first_path, + &second_path, + FillRule::EvenOdd, + BinaryOp::Xor, + )) + .unwrap(); +} + +library_benchmark_group!( + name = bench_linebender_logo; + benchmarks = just_the_sweep, build_topology, xor_linesweeper, +); +main!(library_benchmark_groups = bench_linebender_logo); diff --git a/vendor/linesweeper/benches/painted_dreams.rs b/vendor/linesweeper/benches/painted_dreams.rs new file mode 100644 index 00000000..49cea0b4 --- /dev/null +++ b/vendor/linesweeper/benches/painted_dreams.rs @@ -0,0 +1,26 @@ +use gungraun::{library_benchmark, library_benchmark_group, main}; +use kurbo::BezPath; + +use linesweeper::{BinaryOp, FillRule, binary_op_with_eps}; + +fn painted_dreams_paths(eps: f64) -> (BezPath, BezPath, f64) { + let path_a = BezPath::from_svg("M0,340C161.737914,383.575765 107.564182,490.730587 273,476 C419,463 481.741198,514.692273 481.333333,768 C481.333333,768 -0,768 -0,768 C-0,768 0,340 0,340 Z").unwrap(); + let path_b = BezPath::from_svg( + "M458.370270,572.165771C428.525848,486.720093 368.618805,467.485992 273,476 C107.564178,490.730591 161.737915,383.575775 0,340 C0,340 0,689 0,689 C56,700 106.513901,779.342590 188,694.666687 C306.607422,571.416260 372.033966,552.205139 458.370270,572.165771 Z", + ).unwrap(); + (path_a, path_b, eps) +} + +#[library_benchmark] +#[bench::inaccurate(setup = painted_dreams_paths, args = (1e-4))] +#[bench::accurate(setup = painted_dreams_paths, args = (1e-6))] +fn painted_dreams((a, b, eps): (BezPath, BezPath, f64)) { + binary_op_with_eps(&a, &b, FillRule::EvenOdd, BinaryOp::Union, eps).unwrap(); +} + +library_benchmark_group!( + name = bench_painted_dreams; + benchmarks = painted_dreams, +); + +main!(library_benchmark_groups = bench_painted_dreams); diff --git a/vendor/linesweeper/benches/synthetic.rs b/vendor/linesweeper/benches/synthetic.rs new file mode 100644 index 00000000..ddaabd95 --- /dev/null +++ b/vendor/linesweeper/benches/synthetic.rs @@ -0,0 +1,65 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; + +use kurbo::BezPath; +use linesweeper::{ + BinaryOp, FillRule, Point, binary_op, + generators::{checkerboard, slanted_checkerboard, slanties}, +}; + +fn to_bez(points: &[Vec]) -> BezPath { + fn one(points: &[Point]) -> BezPath { + let mut points = points.iter(); + let p = points.next().unwrap(); + let mut ret = BezPath::default(); + ret.move_to(p.to_kurbo()); + for q in points { + ret.line_to(q.to_kurbo()); + } + ret.line_to(p.to_kurbo()); + ret + } + + let mut ret = BezPath::default(); + for ps in points { + ret.extend(one(ps)); + } + ret +} + +fn generate_checkerboard(size: usize) -> (BezPath, BezPath) { + let (contours_even, contours_odd) = checkerboard(size); + (to_bez(&contours_even), to_bez(&contours_odd)) +} + +fn generate_slanted_checkerboard(size: usize) -> (BezPath, BezPath) { + let (contours_even, contours_odd) = slanted_checkerboard(size); + (to_bez(&contours_even), to_bez(&contours_odd)) +} + +fn generate_slanties(size: usize) -> (BezPath, BezPath) { + let (contours_even, contours_odd) = slanties(size); + (to_bez(&contours_even), to_bez(&contours_odd)) +} + +#[library_benchmark] +#[bench::small_checkerboard(setup = generate_checkerboard, args = (10))] +#[bench::medium_checkerboard(setup = generate_checkerboard, args = (20))] +#[bench::large_checkerboard(setup = generate_checkerboard, args = (50))] +#[bench::small_slanted_checkerboard(setup = generate_slanted_checkerboard, args = (10))] +#[bench::medium_slanted_checkerboard(setup = generate_slanted_checkerboard, args = (20))] +#[bench::large_slanted_checkerboard(setup = generate_slanted_checkerboard, args = (50))] +#[bench::small_slanties(setup = generate_slanties, args = (10))] +#[bench::medium_slanties(setup = generate_slanties, args = (20))] +#[bench::large_slanties(setup = generate_slanties, args = (50))] +fn xor((a, b): (BezPath, BezPath)) { + black_box(binary_op(&a, &b, FillRule::EvenOdd, BinaryOp::Xor)).unwrap(); +} + +library_benchmark_group!( + name = bench_synthetic; + benchmarks = xor, +); + +main!(library_benchmark_groups = bench_synthetic); diff --git a/vendor/linesweeper/docs/sweep.typ b/vendor/linesweeper/docs/sweep.typ new file mode 100644 index 00000000..0f83ac1c --- /dev/null +++ b/vendor/linesweeper/docs/sweep.typ @@ -0,0 +1,960 @@ +#import "@preview/ctheorems:1.1.3": * +#import "@preview/cetz:0.4.0" +#import "@preview/lovelace:0.3.0": * +#set par(justify: true) +#set math.equation(numbering: "(1)") + +#show: thmrules +#let lemma = thmbox("lemma", "Lemma") +#let def = thmbox("lemma", "Definition") +#let proof = thmproof("proof", "Proof") +#let remark = thmbox("lemma", "Remark") + +#let inexact(term) = { + block(inset: 16pt, + block( + inset: 16pt, + stroke: 1pt + gray, + radius: 12pt, + text( + size: 10pt, + [Note for inexact arithmetic: #term] + ) + ) + ) +} + +// TODO: figure out how to get rid of the ":" in the caption +#let invariant(term) = { +figure( + block(inset: 16pt, + text( + size: 10pt, + [#term] + ) + ), + kind: "invariant", + supplement: "invariant", + caption: "" +) +} + +#let fl = text([fl]) + +We'll be talking about sweep line algorithms, where the sweep line is horizontal and increasing in $y$. +Therefore, every line segment "starts" at the coordinate with smaller $y$ and "ends" at the coordinate +with larger $y$ (we'll assume for now that there are no horizontal segments). We'll parametrize each +line segment as a function of $y$. So if $alpha: [y_0 (alpha), y_1 (alpha)] arrow bb(R)$ is a line segment +then $alpha(y)$ is the $x$ coordinate at $y$-coordinate $y in [y_0 (alpha), y_1 (alpha)]$. +We write $theta_alpha$ for the angle that $alpha$ makes with the positive horizontal axis. +Let's have a picture. (In the discussion, it won't matter whether positive $y$ points up or down, but in the +pictures we'll adopt the graphics convention of having positive $y$ point down.) + +#figure( +cetz.canvas({ + import cetz.draw: * + + line((1, 3), (0, 0), name: "a") + line((-4, 3), (4, 3), stroke: (dash: "dotted")) + line((-4, 0), (4, 0), stroke: (dash: "dotted")) + content((-4, 3), $y_0(alpha)$, anchor: "east") + content((-4, 0), $y_1(alpha)$, anchor: "east") + content((0.6, 1.5), $alpha$, anchor: "west") + + cetz.angle.angle("a.start", "a.end", (4, 3), label: $theta_alpha$, label-radius: 0.8) +}), + +caption: "A segment and its angle" +) + +We'll be dealing with inexact arithmetic, so let's define some "error bars" on our line segments. +For an error parameter $epsilon > 0$, offsetting from $alpha$ by $plus.minus epsilon$ in the perpendicular-to-$alpha$ direction +is the same as offsetting by $alpha plus.minus epsilon / (|sin theta_alpha|)$ in the horizontal direction. +Roughly speaking, the "error bars" on $alpha$ amount to adding this horizontal error. But we'll be slightly +more accurate around the corners, by truncating these error bars to the horizontal extents of $alpha$. Precisely, we define + +$ +alpha_(+,epsilon)(y) = min(alpha(y) + epsilon / (|sin theta_alpha|), max(alpha(y_0), alpha(y_1)) + epsilon) \ +alpha_(-,epsilon)(y) = max(alpha(y) - epsilon / (|sin theta_alpha|), min(alpha(y_0), alpha(y_1)) - epsilon) \ +$ + +In pictures, the gray shaded region is the region between $alpha_(-,epsilon)$ and $alpha_(+,epsilon)$: +The point of the scaling by $|sin theta_alpha|$ is to make this an approximation of an $epsilon$-neighborhood (in the +perpendicular direction) of the line segment. The truncation near the corners ensures that if $x$ is between +$alpha_(-,epsilon)(y)$ and $alpha_(+,epsilon)(y)$ then it is within $sqrt(2) epsilon$ of $alpha$. + +#figure( +cetz.canvas({ + import cetz.draw: * + + line((0.5, 3), (-0.3, 0.6), (-0.3, 0), (0.5, 0), (1.3, 2.4), (1.3, 3), close: true, fill: gray, stroke: 0pt) + line((0.5, 3), (-0.3, 0.6), (-0.3, 0), stroke: ( dash: "dashed" )) + line((0.5, 0), (1.3, 2.4), (1.3, 3), stroke: ( dash: "dashed" )) + + line((1, 3), (0, 0), name: "a") + line((-4, 3), (4, 3), stroke: (dash: "dotted")) + line((-4, 0), (4, 0), stroke: (dash: "dotted")) + content((-4, 3), $y_0(alpha)$, anchor: "east") + content((-4, 0), $y_1(alpha)$, anchor: "east") + content((0.6, 1.5), $alpha$, anchor: "west") + + content((0.8, 0.5), $alpha_(+,epsilon)$, anchor: "west") + content((0.2, 2.4), $alpha_(-,epsilon)$, anchor: "east") +}), +caption: "A segment and its error bars" +) + + +Define a relation $prec_(y,epsilon)$ on line segments whose domain contains $y$, where +$alpha prec_(y,epsilon) beta$ if $alpha_(+,epsilon)(y) < beta_(-,epsilon)(y)$. +Intuitively, $alpha prec_(y,epsilon) beta$ if $alpha$ is definitely to the left of $beta$ +at height $y$: $alpha$ is far enough to the left that their error bars don't overlap. +Clearly, for a given $y$ and $epsilon$ there are three mutually exclusive possibilities: either +$alpha prec_(y,epsilon) beta$ or $beta prec_(y,epsilon) alpha$ or neither of the two holds. We'll denote +this third possibility by $alpha approx_(y,epsilon) beta$, and we write +$alpha prec.tilde_(y,epsilon) beta$ for "$alpha prec_(y,epsilon) beta$ or $alpha approx_(y,epsilon) beta$." + +Here are a few basic properties of our definitions: +#lemma[ +1. For any $y$ and any $epsilon > 0$, $prec_(y,epsilon)$ is transitive: + if $alpha prec_(y,epsilon) beta$ + and $beta prec_(y,epsilon) gamma$ then $alpha prec_(y,epsilon) gamma$. (However, $prec.tilde_(y,epsilon)$ is not transitive.) +2. For any $y$ and any $epsilon > 0$, + if $alpha prec_(y,epsilon) beta$ + and $beta prec.tilde_(y,epsilon) gamma$ then $alpha prec.tilde_(y,epsilon) gamma$. +3. For any $y$ and any $epsilon > 0$, + if $alpha prec.tilde_(y,epsilon) beta$ + and $beta prec_(y,epsilon) gamma$ then $alpha prec.tilde_(y,epsilon) gamma$. +4. For any $y$, the relation $prec_(y,epsilon)$ is monotone in $epsilon$, in that if $alpha prec_(y,epsilon) beta$ then $alpha prec_(y,eta) beta$ for + any $eta in (0, epsilon)$. +] + +Since $epsilon$ for us will usually be fixed, we will often drop it from the notation, and write $alpha_-$ and $alpha_+$ instead +of $alpha_(-,epsilon)$ and $alpha_(+,epsilon)$. + +#def[ + Suppose $alpha$ and $beta$ are two segments whose domain contains $y$. We say that *$(alpha, beta)$ + $epsilon$-cross by $y$* if $y$ belongs to both domains and $alpha succ_(y,epsilon) beta$. + We say that *$(alpha, beta)$ $epsilon$-cross* if they $epsilon$-cross by $min(y_1 (alpha), y_1 (beta))$. + + When $epsilon$ is zero, we leave it out: we say that $(alpha, beta)$ cross by $y$ if they $0$-cross by $y$. +] + +Note that the definition of $epsilon$-crossing is not symmetric: $(alpha, beta)$ $epsilon$-crossing is +not the same as $(beta, alpha)$ $epsilon$-crossing. We will usually talk about $(alpha, beta)$ $epsilon$-crossing +in the context that $alpha$ starts off to the left of $beta$, and in this context "$(alpha, beta)$ $epsilon$-cross" means +that at some height before the end of $alpha$ and $beta$, $alpha$ has definitely crossed to the right of $beta$. + +== Partially ordered sweep-lines + +At our first pass, we won't try to detect intersections at all. Instead, we'll produce +a continuum of sweep-lines (constant except at a finite number of points) that *approximately* +track the horizontal order of the segments. + +#def[ +The ordered collection $(alpha^1, ..., alpha^m)$ of line segments is #emph[$epsilon$-ordered at $y$] +if each $alpha^i$ has $y$ in its domain and $alpha^i prec.tilde_(y,epsilon) alpha^j$ for all $1 <= i < j <= m$. +] + +Our algorithm will produce a family of sweep-lines that are $epsilon$-ordered at every $y$ +(and also the sweep-lines will be #emph[complete] in the sense that the sweep-line at $y$ will contain all line segments whose +domain contains $y$). This seems weaker than finding all the intersections (for example, because if you +find all intersections you can use them to produce a completely ordered family of sweep-lines), but +in fact they're more-or-less equivalent: given weakly-ordered sweep-lines, you can perturb the lines so +that your weak order becomes the real order of the perturbed lines. + +#lemma[ +If $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered at $y$ then there exist $x^1 <= ... <= x^m$ such that +$alpha_(-,epsilon)^i (y) <= x^i <= alpha_(+,epsilon)^i (y)$ for all $i$. +] + +#proof[ +Define $x^i = max_(j <= i) alpha_(-,epsilon)^j (y)$. +Since we've included $j = i$, this clearly satisfies $x^i >= alpha_(-,epsilon)^j (y)$. +For the other inequality, the ordering condition implies that $alpha^j_(-,epsilon) (y) <= alpha^i_(+,epsilon) (y)$ +for every $j <= i$. Therefore this inequality still holds for the maximum over these $j$. +] + +One consequence of our approximate approach is that we need to do a little extra bookkeeping to maintain +the continuity of the input paths: when one segment exits and its path-neighbor enters, we need to remember +that they are connected because the approximate sweep-line might not keep them together. We'll ignore this +bookkeeping for now; +the goal here is to get into detail about the sweep-line invariants, and prove that we can maintain them. + +== The sweep-line invariants + +We're going to have a sweep-line that depends on $y$. When we need to emphasize this, we'll use the +cumbersome but explicit notation +$(alpha_y^1, ..., alpha_y^(m_y))$. +In addition to the sweep-line, we'll maintain a queue of "events." The events are ordered by $y$-position +and they are similar to the classical Bentley-Ottmann events so we'll skimp on the details here. There +is an "enter" event, an "exit" event, and an "intersection" event. + +Our sweep-line will maintain two invariants: ++ At every $y$, the sweep-line is $epsilon$-ordered at $y$. (We'll call this the "order" invariant.) ++ For every $y$ and every $1 <= i < j <= m_y$, if $alpha_y^i$ and $alpha_y^j$ $epsilon$-cross + then the event queue contains an event between $i$ and $j$, + and at least one of these events occurs before the $epsilon$-crossing height, or at $y$. + (We'll call this the "crossing" invariant.) + +(When we say the event queue contains an event between $i$ and $j$, we mean that either there's +an exit event for some $alpha^k$ with $i < k < j$ or there's an intersection event for some $alpha^k$ and $alpha^ell$ +with $i <= k < ell <= j$.) + +Hopefully the first invariant is already well-motivated, so let's discuss the second. +To naively ensure that we find +all intersections, we could adopt a stronger crossing invariant that requires all pairs of intersecting segments +in the sweep-line to immediately put an intersection event in the queue. This would be inefficient to maintain because +it would require testing all pairs of segments. The main Bentley-Ottmann observation is that it's enough to have intersection +events for all sweep-line-adjacent pairs, because any pair of lines will have to become sweep-line adjacent strictly +before they intersect. We can't rely only on sweep-line adjacency because of the $epsilon$ fudge, but our "crossing" +event essentially encodes the same property. Note that if $j = i+1$ and $y$ is just before the $epsilon$-crossing height and +there are no other segments nearby, then the mysterious $j'$ must be either $i$ or $j$ (because there is nothing in between) +and the mysterious queue event must be the crossing of $i$ and $j$ (it couldn't be an exit event, because we assumed they +cross and they must cross before they exit). In other cases, the crossing event ensures that even if we haven't +recorded the upcoming crossing of $alpha^i$ and $alpha^j$, something will happen in between them that will give us +a chance to test their crossing. + +== Sweeping the sweep line + +The first observation is that the sweep line invariants are maintained as we increase $y$ continuously up +to the next event: ++ For the order invariant, first note that there is an event whenever $y$ leaves the domain of a segment, so $y$ remains in all domains until the next event. + Moreover, if at any $y' > y$ the ordering breaks, two line segments must have $epsilon$-crossed one another by $y'$. + The third invariant guarantees that there's an event before this happens, so by the contra-positive until an event happens the ordering + constraint is maintained. ++ The crossing invariant is maintained because the set of things to check (i.e. the set of line segments that $epsilon$-cross + one another after $y$) only shrinks as $y$ increases. + +== Interaction with adjacent segments + +In vanilla Bentley-Ottmann, each segment gets compared to its two sweep-line neighbors; they can either intersect or not intersect. +When numerical errors are taken into account, we may need to compare to +more segments. + +#figure( +cetz.canvas({ + import cetz.draw: * + + let calc_eps(x0, x1) = 0.5 * calc.sqrt(1 + calc.pow(calc.abs(x1 - x0) / 2, 2)) + + let mkline(x0, x1) = { + let eps = calc_eps(x0, x1) + line((x0, 1), (x1, -1)) + line((x0 + eps, 1), (x1 + eps, -1), stroke: ( thickness: 0.2pt, dash: "dashed" )) + line((x0 - eps, 1), (x1 - eps, -1), stroke: ( thickness: 0.2pt, dash: "dashed" )) + } + + let a0 = -8 + let a1 = 8 + let b0 = -2 + let b1 = 2 + let c0 = 0.5 + let c1 = 0.5 + + content((a0, 1), $alpha^1$, anchor: "south", padding: 5pt) + content((b0, 1), $alpha^2$, anchor: "south", padding: 5pt) + content((c0, 1), $alpha^3$, anchor: "south", padding: 5pt) + + mkline(a0, a1) + mkline(b0, b1) + mkline(c0, c1) + + let a_eps = calc_eps(a0, a1) + let b_eps = calc_eps(b0, b1) + let c_eps = calc_eps(c0, c1) + let cross12 = 1 - 2*(b0 + b_eps - a0 + a_eps) / (b0 - a0 - (b1 - a1)) + let cross13 = 1 - 2*(c0 + c_eps - a0 + a_eps) / (c0 - a0 - (c1 - a1)) + + line((-10, cross13), (10, cross13), stroke: ( thickness: 0.2pt )) + line((-10, cross12), (10, cross12), stroke: ( thickness: 0.2pt )) +}), +caption: [Three segments all mixed up. The upper horizontal line is the height at which $(alpha^1, alpha^3)$ $epsilon$-cross, +and the lower horizontal line is the height at which $(alpha^1, alpha^2)$ $epsilon$-cross.] +) + +For example, in @mixed-up-segments imagine that $alpha^1$ and $alpha^2$ +are present in the sweep line, and we just added $alpha^3$. If we compare +$alpha^3$ to $alpha^2$, we'll see that the $epsilon$-cross and so we'll add an +intersection event between them sometime betfore they $epsilon$-cross. But if +that's all we do then we're in trouble, because by the time we come around to +looking at that intersection event $alpha^1$ may have already $epsilon$-crossed +$alpha^3$, breaking the ordering invariant. Note that $alpha^2$ doesn't +$epsilon$-cross $alpha^3$ at all in @mixed-up-segments, so there's no guarantee of +an intersection event between those two. + +Fix $y$ and $epsilon$, and +suppose we have a collection of lines $(alpha^i, ..., alpha^n)$ that satisfy the ordering invariant +at $y$, and suppose also that $(alpha^(i+1), ..., alpha^n)$ +satisfy the crossing invariant at $y$. +To make the whole collection satisfy both invariants, we run the following algorithm. +We call this an *intersection scan to the right*. + +#figure( + pseudocode-list([ + + *for* $j = i+1$ up to $n$ + + #line-label() let $w^j$ be the smallest height of any event between $i$ and $j$ + + + #line-label() *if* $(alpha^i, alpha^j_(+,epsilon))$ cross by $w^j$ + + choose $z$ before the crossing, such that $alpha^i approx_(z,epsilon) alpha^j$ + + insert an intersection event for $(alpha^i, alpha^j)$ at $z$ + + + #line-label() *if* $alpha^i (z) <= alpha^j_-(z)$ for all $z in [y, w^j]$ + + *break* + ]), + caption: "Intersection scan to the right" +) + +#inexact[ +The test at @protect can be seen as an early-stopping optimization, and is not necessary for correctness. +In particular, an approximation with no false positives +is also fine. +] + +#lemma[ +Suppose that $(alpha^i, ..., alpha^n)$ satisfy the ordering invariant +at $y$, and suppose also that $(alpha^(i+1), ..., alpha^n)$ +satisfy the crossing invariant at $y$. +After running an intersection scan to the right, +$(alpha^i, ..., alpha^n)$ satisfy the crossing invariant at $y$. + +In fact, $(alpha^i, ..., alpha^n)$ satisfy a slightly stronger crossing invariant at $y$: for every $j > i$, +if $(alpha^i, alpha^j_(+,epsilon))$ cross then the event queue contains an event between $i$ and $j$, and before + the crossing height. +] + +(The special thing about the stronger crossing invariant is that it asks whether +$(alpha^i, alpha^j_(+,epsilon))$ cross, where the usual crossing invariant asks +whether +$(alpha^i_(-,epsilon), alpha^j_(+,epsilon))$ cross.) + +#proof[ + It suffices to check the stronger crossing invariant. + So take some $k > i$ + that $(alpha^i, alpha^k_(+,epsilon))$ cross. We consider two cases: whether or not the loop terminated + before reaching $k$. + + - Suppose the loop terminated at some $j < k$. If $(alpha^i, alpha^k_(+,epsilon))$ cross + after $w^j$, then the definition of $w^j$ ensures that there is an event between $i$ and $j$ (and therefore between + $i$ and $k$) before the crossing. On the other hand, the termination condition ensures that + $alpha^i (z) <= alpha^j_-(z)$ until $w^j$, and so if $(alpha^i, alpha^k_(+,epsilon))$ cross before $w^j$ then + also $(alpha^j, alpha^k)$ cross before that. In this case, the crossing invariant for $(alpha^(i+1), ..., alpha^n)$ + implies the existence of an event between $j$ and $k$ (and therefore between $i$ and $k$) before the crossing. + - If the loop included the case $j = k$, we break into two more cases: + - If $(alpha^i, alpha^k_(+,epsilon))$ cross by $w^j$, then the algorithm inserted a witnessing event between $i$ and $j$. + - Otherwise, the definition of $w^j$ ensures that there is an event between $i$ and $j$ before the crossing. +] + +#remark[ +We can tweak the algorithm a little to try and reduce the number of comparisons. +For a start, if we add an intersection event between $i$ and $j$ then we can +set $w^j$ to $z$, the height of that new event (because $z$ is the new smallest +height of any event between $i$ and $j$). +Therefore, if we broaden the test @crossing-test to check whether $(alpha^i, +alpha^j_-)$ cross, and we choose $z$ to be before the crossing of $(alpha^i, +alpha^j_-)$ then we can skip the test at @protect and terminate straight away. +Thus, there is no loop at all: we only need to consider $j = i + 1$. + +One potential issue with this is finding a height $z$ with $alpha^i approx_(z,epsilon) alpha^j$ +that's before $(alpha^i, alpha^j_-)$ cross; in other words, we want a height after $(alpha^i_+, alpha^j_-)$ +cross but before $(alpha^i, alpha^j_-)$ cross. If $alpha^j$ is almost horizontal, this window might be very +small (or maybe even not representable with our restricted precision). +It may still be worthwhile to have a fast path with a single comparison, and a slow path with a loop. +] + +As you might have already guessed, we can also intersection scan to the left; it's pretty much a reflection +of the other one. + +#figure( + pseudocode-list([ + + *for* $j = i$ down to $1$ + + let $w^j$ be the smallest height of any event between $j$ and $i$ + + + *if* $(alpha^j_(-,epsilon), alpha^i)$ cross by $w^j$ + + choose $z$ before the crossing, such that $alpha^j approx_(z,epsilon) alpha^i$ + + insert an intersection event for $(alpha^j, alpha^i)$ at $z$ + + + *if* $alpha^j_+ (z) <= alpha^i (z)$ for all $z in [y, w^j]$ + + *break* + ]), + caption: "Intersection scan to the left" +) + +We'll skip the proof of the following lemma, because it's basically the same as the last one. + +#lemma[ +Suppose that $(alpha^1, ..., alpha^i)$ satisfy the ordering invariant +at $y$, and suppose also that $(alpha^1, ..., alpha^(i+1))$ +satisfy the crossing invariant at $y$. +After running an intersection scan to the left, +$(alpha^1, ..., alpha^(i+1))$ satisfy a slightly stronger crossing invariant at $y$: for every $j <= i$, +if $(alpha^j_(-,epsilon), alpha^(i+1))$ cross then the event queue contains an event between $j$ and $i+1$, and before + the crossing height. +] + +The purpose of the stronger crossing invariant comes in the next lemma, which deals with scanning in both directions +and allows the insertion of a segment in the middle of a sweep-line. + +#lemma[ +Suppose that $(alpha^1, ..., alpha^n)$ satisfy the ordering invariant at $y$, and suppose that +$(alpha^1, ..., alpha^i)$ and $(alpha^(i+1), ..., alpha^n)$ satisfy the crossing invariant at $y$. +Let $beta$ be another segment and assume that $(alpha^1, ..., alpha^i, beta, alpha^(i+1), ... alpha^n)$ +satisfy the ordering invariant at $y$. After running an intersection scan to the left and an intersection +scan to the right from $beta$, +$(alpha^1, ..., alpha^i, beta, alpha^(i+1), ... alpha^n)$ satisfies the crossing invariant at $y$. +] + +#proof[ +@lem-intersection-scan implies that $(beta, alpha^(i+1), dots, alpha^n)$ satisfies the crossing invariant, +and +@lem-intersection-scan-left implies that $(alpha^1, ..., alpha^i, beta)$ does also. It only remains +to consider interactions between a segment before $beta$ and one after. So fix $j <= i < k$ and suppose +that $(alpha^j, alpha^k)$ $epsilon$-cross. If they $epsilon$-cross after $y_1(beta)$ then $beta$ exit +event witnesses the crossing invariant, so assume that $(alpha^j, alpha^k)$ $epsilon$-cross by $y_1(beta)$. +Then $(alpha^j_-, alpha^k_+)$ cross by $y_1(beta)$, and so one of them crosses $beta$ before $(alpha^j, alpha^k)$ $epsilon$-cross. +If $(alpha^j_-, beta)$ cross then @lem-intersection-scan-left implies that there is an event between $alpha^j$ and $beta$ (and +therefore between $alpha^j$) and $alpha^k$ before the crossing height; otherwise, $(beta, alpha^k_+)$ cross +and so @lem-intersection-scan provides the required event. +] + +One last observation in this section, that follows trivially from the algorithm: + +#lemma[ +If an intersection scan inserts an intersection event for $(alpha, beta)$ +then the intersection event's height $z$ satisfies $alpha approx_(z,epsilon) beta$. +] + +== An "enter" event + +When inserting a new segment into the current sweep-line, we first choose its sweep-line position using +a binary search on its horizontal coordinate. Let's write $(alpha^1, dots, alpha^m)$ for the sweep-line +before inserting the new segment, and let's call the new segment $beta$. First, we observe that +there is a place to insert the new segment while preserving the ordering invariant. + +#lemma[ +Suppose $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered at $y$, and let $i$ be the largest $j$ for which +$alpha^j prec_(y,epsilon) beta$. Then +$(alpha^1, ..., alpha^i, beta, alpha^(i+1), ..., alpha^m)$ is $epsilon$-ordered at $y$. +(Here, we can allow the corner cases $i = 0$ and $i = m$ by declaring that +"$alpha^0$" is a vertical line at $x = -infinity$ and "$alpha^(m+1)$" is a vertical line at $x = infinity$). +] + +#proof[ +Since $(alpha^1, ..., alpha^m)$ was $epsilon$-ordered at $y$, it suffices to compare beta with all $alpha^j$. +For $i + 1 <= j <= m$, our choice of $i$ immediately implies that $alpha^j succ.tilde_(y,epsilon) beta$. +So consider $1 <= j <= i$. Since $(alpha^1, ..., alpha^m)$ is $epsilon$-ordered, $alpha^j prec.tilde_(y,epsilon) alpha^i$. +Since $alpha^i prec_(y,epsilon) beta$, @lem-basic-order-properties implies that $alpha^j prec.tilde_(y,epsilon) beta$. +] + +#inexact[ +@lem-insert-preserving-order guarantees the existence of an insertion point, but it doesn't say how to +find it efficiently. +But consider a predicate $f(alpha^j)$ that returns true whenever $alpha^j prec_(y,epsilon) beta$, and +false whenever $alpha^j_+(y) > beta_+(y)$. Running a binary search with this predicate will find some $i$ +for which $f(alpha^i)$ is true and $f(alpha^(i+1))$ is false. By scanning to the right from there, we can +find the largest such $i$. + +This $i$ is at least as large as the $i$ in @lem-insert-preserving-order, so +to check that it's a valid insertion point we only need to check that it isn't too large. So if $1 <= j <= i$ then +$alpha^j prec.tilde_(y,epsilon) alpha^i$ and so $alpha^j_-(y) <= alpha^i_+(y)$. On the other hand, $f(alpha^i)$ +was true and so $alpha^i_+ <= beta_+(y)$. Putting these together shows that $alpha^j prec.tilde_(y,epsilon) beta$. + +This algorithm can be implemented with approximate arithmetic, and its running time is logarithmic in the +total length of the sweep line, plus linear in the number of elements that are very close to $beta$. +] + +@lem-insert-preserving-order implies that we can insert a new segment while preserving the ordering invariant. By +@lem-intersection-scan-bidirectional, running an intersection scan restores the crossing invariant. +Thus, we can insert a new segment while preserving the sweep-line invariants. + +== An "exit" event + +When a segments exits the sweep-line, the ordering invariant clearly doesn't break. +Regarding the crossing invariant, it can only break because of $epsilon$-crossing pairs whose +crossing invariant was witnessed by the exit event that was just processed. +To restore the crossing invariant, we need to enqueue some new intersection events. + +Let $(alpha^1, ..., alpha^m)$ be the sweep-line after removing the just-exited segment +which, we assume, used to live between $alpha^i$ and $alpha^(i+1)$. Note that both +$(alpha^1, ..., alpha^i)$ and $(alpha^(i+1), ..., alpha^n)$ satisfy the crossing invariant. +By @lem-intersection-scan-bidirectional, running an intersection scan from $alpha^i$ in both +directions restores the crossing invariant. (Technically, this isn't covered by the statement +of @lem-intersection-scan-bidirectional -- which involves a new segment $beta$ -- but the proof +is basically the same.) + +== An "intersection" event + +Suppose our sweep-line is $(alpha^1, ..., alpha^m)$ and we've just encountered an intersection event for $(alpha^i, alpha^j)$ +at height $y$. +If $i > j$ then they've already been swapped in our sweep-line, so we don't need to swap them again. If $i < j$, we need to +swap them. According to @lem-valid-intersection-events, $alpha^j prec.tilde_(y,epsilon) alpha^i$. It seems reasonable, therefore, +to reorder the sweep line by putting $alpha^i$ after $alpha^j$, like + +$ +alpha^1, ..., alpha^(i-1), alpha^(i+1), ..., alpha^j, alpha^i, alpha^(j+1), ... alpha^n. +$ + +The issue with this is that $prec.tilde_(y,epsilon)$ is that it's changed the order of pairs other than $alpha^i$ and $alpha^j$: +for every $i < k < j$, the ordering between $alpha^i$ and $alpha^k$ has been swapped. If $prec.tilde$ were transitive, this would +be fine. But it isn't. + +To fix this issue, we allow $alpha^i$ to "push" some of the intermediate segments along with it. It's a bit tedious to write (and read) +this precisely, so hopefully this description is enough: for each $k$ between $i$ and $j$, if $alpha^i prec_(y,epsilon) alpha^k$ then +we also move $alpha^k$ after $alpha^j$. Also, we preserve the relative order of all segments moved in this way. +To see that this "pushing" maintains the ordering invariants, note that by definition it preserves the ordering for all comparisons +with $alpha^i$: if some $alpha^ell$ was pushed along with $alpha^i$ then their relative orders haven't changed; and if $alpha^ell$ +wasn't pushed then $alpha^ell prec.tilde_(y,epsilon) alpha^i$ and the new order is fine. + +What about other pairs? If $alpha^k$ and $alpha^ell$ +changed orders then one of them ($alpha^ell$, say) was pushed and the other wasn't. Then $alpha^i prec_(y,epsilon) alpha^ell$ +by our choice of the pushed segments, and $alpha^k prec.tilde_(y,epsilon) alpha^i$ by the previous paragraph. Putting these +together, @lem-basic-order-properties implies that $alpha^k prec.tilde_(y,epsilon) alpha^ell$ and so the new order is ok. + +#inexact[ +We might not be able to tell exactly which segments $alpha^k$ between $alpha^i$ and $alpha^j$ satisfy +$alpha^i prec_(y,epsilon) alpha^k$. Fortunately, we can push a few too many segments while still maintaining +correctness: it suffices to +include all segments $alpha^k succ_(y,epsilon) alpha^i$, while also ensuring that we only include $alpha^k$ for which +$alpha^k_+(y) >= alpha^i_+(y)$. +] + +Finally, we need to maintain the crossing invariant. We can probably be more efficient about this, but one +way to be correct is to treat the various swappings as a bunch of deletions from the sweep-line followed by +a bunch of insertions into the sweep-line. We have already shown that running an intersection scan +after each insertion and deletion correctly maintains the crossing invariant, so we can just do that. + += Correctness of the output + +We've described how to turn a bunch of segments into a continuum of sweep-lines, but we probably actually wanted +to find the segments' intersection points. Let's define exactly what that means and how to get there. +We'll assume that we start with one or more polylines that may or may not be closed. Equivalently, each segment +comes with an orientation and (optionally) a "following" segment whose starting point is the ending point of the +current segment. +Our goal is to subdivide all segments at all intersection points, meaning that for each segment we'll produce +a polyline; we'll call the segments in each such polyline "output segments," to distinguish them from +the original segments. We require three properties: + +- "approximation": for each input segment, every vertex in its output polyline must be within $epsilon$ of the original segment. + Also the first and last vertices in the output polyline must be within $epsilon$ of the start and end of the segment, respectively. +- "continuity": if a segment has a following segment, then the last vertex in its output polyline must coincide + with the first vertex of the following segment's output polyline. +- "completeness": output segments intersect only at their endpoints, unless they are identical. + +In the solution we describe, we'll achieve both continuity and the second half +of approximation by insisting that output polylines have exactly the same start- +and endpoints as their input segment. This stronger condition makes the algorithm +simpler, but it also subjectively can hurt output "quality" by requiring more segments. + +== The "dense" version + +Suppose we've already found a weakly-ordered sweep line at every height. We say +a height is "important" if the sweep line changed at that height, either because +some segments entered or exited, or because the order changed. The naive version +of our subdivision algorithm will subdivide every segment at every important +height. Note that because the sweep line changes at a discrete set of heights, +at every important height $y$ we actually have two weakly-ordered sweep line: +the "old" one from infinitesmally before $y$ height and the "new" one from $y$ +onwards. Because everything is continuous and the weak ordering conditions are +closed, both the old and new sweep lines satisfy the weak ordering condi0.2tions +at $y$. + +The algorithm goes like this: at every important height we use +@lem-horizontal-realization to assign horizontal positions to segments in the +old sweep line; we subdivide each segment at the resulting coordinate. Then we +use @lem-horizontal-realization again to assign horizontal positions to segments +in the new sweep line. If a segment gets assigned two different horizontal positions +by the old and new sweep lines, we add a horizontal segment between them. Also, +if a segment starts or ends at the current height and its assigned position is not the +same as its starting or ending position, we fix that up with another horizontal segment. +Finally, we subdivide all horizontal segments as necessary to ensure that they only +intersect at endpoints. + +I think it's pretty clear that this algorithm is correct. The approximation property +holds (with $sqrt 2 epsilon$ instead of $epsilon$, but that doesn't matter) because +@lem-horizontal-realization always puts a new point within its segment's upper and lower bounds, +and the bounds are chosen to be within $sqrt 2 epsilon$ of the segment; see @fig-error-bars. +As mentioned above, the "continuity" property and the other half of "approximation" follow +because we insisted on including the segment endpoints exactly. + +For the "completeness" property, let's focus on the bit between the important heights +(because everything gets subdivided at the important heights, and so maintaining the +completeness property there is just a matter of splitting into enough horizontal segments). +Between two important heights, the sweep line is constant, so the new sweep line +at the old height is the same as the old sweep line at the new height. Thus, all output +subsegments have the same order when leaving the old sweep line as they do when entering +the new sweep line. Therefore any two of them that intersect between the two important +heights must be identical. + +== The "sparse" version + +We'd like to avoid subdividing every segment at every important height. This basically +involves detecting which segments need to be divided and ignoring the rest. It's implemented +but not yet written up (TODO). + +== A more detailed insertion algorithm + +It took me a few tries to get the approximate insertion algorithm working, so +I figured it was worth writing up a few more details. First of all, a slight +tweak of @lem-insert-preserving-order shows that there is a position $i$ such +that $alpha^j_- (y) <= beta(y)$ for all $j <= i$ and $alpha^j_+ (y) >= beta(y)$ +for all $j > i$. We will look for this $i$, but with some slight slack in our +arithmetic. The slack will be small enough to guarantee we find a position that +satisfies the weaker properties of @lem-insert-preserving-order. + +First, we consider a predicate $p(j)$ that returns `false` whenever $alpha^j_+ +(y) > beta(y)$. We put no conditions on the values of $j$ for which $p(j)$ +returns `true`; a tighter predicate will give a more efficient algorithm, but +doesn't affect correctness. Let $i_-$ be such that $p(i_-) = #true$ and $p(i_- ++ 1) = #false$ (where we allow $i_- = -1$, and handle the boundary cases by +declaring that $p(-1) = #true$ and $p(m+1) = #false)$. Note that $i_-$ can be +found with a binary search. + +The definition of $p$ ensures that $alpha^(i_-)_+(y) <= beta(y)$. Under the +assumption that $(alpha^1, ..., alpha^m)$ is weakly ordered, it follows that +for every $j <= i_-$, $alpha^j_-(y) <= beta(y)$. That is, the $i$ that we are +looking for is larger than or equal to $i_-$. + +The next step is a linear scan up from $i_-$. Let $q(j)$ be a predicate +that returns `true` whenever $alpha^j_- (y) < beta^j (y)$ and `false` whenever +$alpha^j_- (y) > beta^j_+ (y)$. Take $i >= i_-$ to be the index just before $q$ +first returns false; we will insert $beta$ after $alpha^i$. + +Let's check that this insertion position is valid: for every $j <= i$ we have +either $j <= i_-$ or $i_- < j <= i$. In the first case, we already observed +that $alpha^j_-(y) <= beta(y)$; in the second case, $q(j)$ returned `true` and +so $alpha^j_-(y) <= beta^j^+ (y)$. In either case, $alpha^j prec.tilde beta$. +On the other hand, $q(i+i)$ being `false` implies that $alpha^(i+1)_- (y) >= +beta(y)$. This implies that for all $j >= i+1$, $alpha^j_+ (y) >= beta(y)$ and +so $alpha^j succ.tilde_y beta$. + += Accuracy analysis + +We're going to implement some algorithms in floating point. Suppose $p$ is the number of mantissa bits +of the floating-point type, and let $fl(t)$ denote the rounding function. Let $epsilon_0 = 2^(-1-p)$; +this is the relative error of the rounding function for all normal numbers: for a real number $x$, +as long as $fl(x)$ is finite and non-subnormal, +$ +fl(x) = x plus.minus epsilon_0 |x|. +$ +Now, if the result of floating point addition or subtraction is subnormal then it is exact; therefore, +for any floating-point $x$ and $y$, if $x - y$ and $x + y$ don't overflow then after rounding they +have relative error at most $epsilon_0$. + +#lemma[ + If $y_0 <= y <= y_1$ are floating-point numbers and $epsilon_0 <= 1/2$ then computing $(y - y_0) / (y_1 - y_0)$ + the naive way gives an absolute error of + at most $3 epsilon_0 + 4 epsilon_0^2$. +] + +#proof[ +The assumption $y_0 <= y <= y_1$ ensures that $y - y_0$ and $y_1 - y_0$ do not overflow. Therefore + +$ +fl(y - y_0) &= (y - y_0) (1 plus.minus epsilon_0) \ +fl(y + y_0) &= (y + y_0) (1 plus.minus epsilon_0) +$ +and so +$ +fl(y - y_0) / +fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) times + (1 plus.minus epsilon_0) / (1 plus.minus epsilon_0). +$ +For the upper bound, +$ + (1 + epsilon_0) / (1 - epsilon_0) = (1 + epsilon_0) (1 + epsilon_0 + epsilon_0^2 / (1 - epsilon_0)) += 1 + 2 epsilon_0 + 2 / (1 - epsilon_0) epsilon_0^2 <= 1 + 2 epsilon_0 + 4 epsilon_0^2. +$ +For the lower bound, +$ + (1 - epsilon_0) / (1 + epsilon_0) = 1 - epsilon_0 - (epsilon_0 (1-epsilon_0)) / (1+epsilon_0) >= 1 - 2 epsilon_0. +$ +Putting these together, +$ +fl(y - y_0) / +fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) (1 plus.minus (2 epsilon_0 + 4 epsilon_0^2)). +$ +Since $0 <= (y - y_0) / (y_1 - y_0) <= 1$, this relative error is also an absolute error: +$ +fl(y - y_0) / +fl(y_1 - y_0) = (y - y_0) / (y_1 - y_0) plus.minus (2 epsilon_0 + 4 epsilon_0^2). +$ +The final rounding step adds another absolute error of at most $epsilon_0$. +] + +#lemma[ +Suppose that $x_0$, $x_1$, and $M$ are floating-point numbers with $|x_0|, |x_1| <= M/2$. +Let $0 <= t <= 1$ be a real number, and let $0 <= hat(t) <= 1$ be a floating point number +with $|t - hat(t)| <= epsilon$. Then computing $x_0 + t (x_1 - x_0)$ the obvious way gives an +absolute error of at most $(3 epsilon_0 + epsilon) M$. +] + +#proof[ +Let's first consider the error introduced by approximating $t (x_1 - x_0)$ with $hat(t) fl(x_1 - x_0)$: +$ +hat(t) fl(x_1 - x_0) = hat(t) (x_1 - x_0)(1 plus.minus epsilon_0) = hat(t) (x_1 - x_0) plus.minus epsilon_0 M, +$ +where the second approximation follows because $0 <= hat(t) <= 1$ and $|x_1 - x_0| <= M$. +Since $hat(t) = t plus.minus epsilon$, we continue with +$ +hat(t) fl(x_1 - x_0) += (t plus.minus epsilon) (x_1 - x_0) plus.minus epsilon_0 M, += t (x_1 - x_0) plus.minus (epsilon_0 + epsilon) M. +$ +Rounding the left hand side introduces a relative error of at most $epsilon_0$, which is an absolute error of at most $M epsilon_0$. Therefore, +$ +fl(hat(t) fl(x_1 - x_0)) += t (x_1 - x_0) plus.minus (2 epsilon_0 + epsilon) M. +$ + +Finally, we subtract both sides from $x_0$ and round one more time. This last rounding introduces a relative error of at most $epsilon_0$, +which again leads to an absolute error of at most $2 epsilon_0 M$ (and I think maybe the 2 is unnecessary, but let's not worry). +] + +Next, let's analyze the computation of the $y$ intersection point. + +#lemma[ +Suppose we have two segments $alpha$ and $beta$. Set $y_0 = y_0(alpha) or y_0(beta)$ and $y_1 = y_1(alpha) and y_1(beta)$ +(these can be computed exactly). Suppose that we can compute $alpha(y)$ and $beta(y)$ to additive accuracy $epsilon$, +and assume that $beta(y_0) - alpha(y_0) >= 0$ and +$ +alpha(y_1) - beta(y_1) > 16/3 (epsilon + 2 epsilon_0 M). +$ +Define $t = (alpha(y_1) - beta(y_1)) / (alpha(y_1) - beta(y_1) + beta(y_0) - alpha(y_0)$ and set $y = y_0 + t(y_1 - y_0)$. The +natural way to compute $y$ produces an approximation $hat(y)$ satisfying +$ +|alpha(hat(y)) - beta(hat(y))| <= 4(epsilon + 2 epsilon_0 M) (1 + max(1/m_alpha, 1/m_beta)), +$ +where $m_alpha$ and $m_beta$ are the slopes of $alpha$ and $beta$. +] + +Basically, this says we can find a good approximate crossing height as long as the two segments cross by at least $16/3 (epsilon + 2 epsilon_0 M)$. + +#proof[ +First, note that at least one of $alpha(y_0)$ or $beta(y_0)$ is calculated exactly; similarly for $alpha(y_1)$ and $beta(y_1)$. +Therefore the absolute error in calculating $alpha(y_1) - beta(y_1)$ is at most $epsilon + epsilon_0 M$ (where the $epsilon_0 M$ comes +from rounding after the summation) and the absolute error in calculating +$(alpha(y_1) - beta(y_1)) + (beta(y_0) - alpha(y_0))$ is at most $2 epsilon + 3 epsilon_0 M$. +These errors aren't independent, though, since both terms involve a computation of $alpha(y_1) - beta(y_1)$. +Specifically, let $c = alpha(y_1) - beta(y_1)$ and let $d = (alpha(y_1) - beta(y_1)) + (beta(y_0) - alpha(y_0))$; suppose +our calculation of $c$ has error $epsilon_c$ and our calculation of $d$ has error $epsilon_d$. Then + +$ +|epsilon_c - epsilon_d| <= epsilon + 2 epsilon_0 M, +$ + +because this is the *additional* error introduced into $d$ by computing $beta(y_0) - alpha(y_0)$ and adding it to the other term. + +Now we consider the quotient + +$ +(c + epsilon_c) / (d + epsilon_d) +&= c / (d (1 + epsilon_d/d)) + epsilon_c / (d (1 + epsilon_d/d)) \ +&= c / d (1 - epsilon_d/d + (epsilon_d/d)^2 / (1 + epsilon_d/d)) + epsilon_c / d (1 - (epsilon_d/d)/(1 + epsilon_d / d)). +$ + +We've assumed that $1 + epsilon_d/d >= 1/2$, and so the error +$ +abs((c + epsilon_c) / (d + epsilon_d) - c/d) +$ +is at most +$ +& abs(c / d ( - epsilon_d/d + (epsilon_d/d)^2 / (1 + epsilon_d/d)) + epsilon_c / d (1 - (epsilon_d/d)/(1 + epsilon_d / d))) \ +& <= abs(- c / d epsilon_d/d + epsilon_c / d ) + 2 c/d (epsilon_d/d)^2 + 2 abs(epsilon_c epsilon_d) / d^2 \ +& <= c/d abs(epsilon_c/d - epsilon_d/d) + (1 - c/d) abs(epsilon_c/d) + 2 (epsilon_d/d)^2 + 2 abs(epsilon_c epsilon_d) / d^2, +$ +where in the last line we recall that $0 <= c/d <= 1$. Now, recall from @eq-error-cancellation and the paragraph before it +that both $abs(epsilon_c)$ and $abs(epsilon_c - epsilon_d)$ are bounded by $epsilon + 2 epsilon_0 M$, and that +$abs(epsilon_d)$ is bounded by $2 epsilon + 3 epsilon_0 M$. Therefore, our computation of $c/d$ (which, recall, we called $t$ above) has absolute error of at most + +$ +(epsilon + 2 epsilon_0 M)/d + 4 (2 epsilon + 3 epsilon_0 M)^2 / d^2. +$ + +Let's call this expression $epsilon_t$. + +By @lem-convex-combination-error, the computation of $y = y_0 + t (y_1 - y_0)$ has absolute error at most $(2 epsilon_0 + epsilon_t) |y_1 - y_0| + epsilon_0 M$. +Let's see what this means for the horizontal accuracy of $alpha(y)$ and $beta(y)$. We can write +$ +alpha(y) = alpha(y_0) + (y - y_0) / (y_1 - y_0) (alpha(y_1) - alpha(y_0)), +$ +and similarly for $beta(y)$. Combining these and recalling our definitions of $c$ and $d$, +$ +alpha(y) - beta(y) = -c + (y - y_0) / (y_1 - y_0) d. +$ +For the true value of $y$, this is zero of course. With our error bound of $(2 epsilon_0 + epsilon_t) |y_1 - y_0| + epsilon_0 M$ on our +approximation of $y$ (let's call it $hat(y)$), +$ +abs(alpha(hat(y)) - beta(hat(y))) <= (2 epsilon_0 + epsilon_t) d + (epsilon_0 M) / (y_1 - y_0) d +<= epsilon_t d + 3 epsilon_0 (M d) / (y_1 - y_0). +$ + +If we assume that $16 (epsilon + 2 epsilon_0 M) / d <= 3$ then $epsilon_t d <= 4 epsilon + 8 epsilon_0 M$. +Since $d/(y_1 - y_1)$ is the difference of the inverse-slopes of $alpha$ and $beta$, it's bounded by twice +the maximum of these inverse-slopes. +] + +Putting all this together (and handling the "chamfers" yet): +#lemma[ +Assume all line segments are inside the square $[-M/2, M/2]^2$, +and take $delta = 64 epsilon_0 M$. If $epsilon_0 <= 1/4$ then + +- We can compute $x$ coordinates of line segments to additive accuracy $delta/8$. +- If $alpha$ $(3 delta)/4$-crosses $beta$, we can compute a crossing height $hat(y)$ such + that + $ + |alpha(hat(y)) - beta(hat(y))| <= (9 delta) /16 (1 + max(1/m_alpha, 1/m_beta)). + $ +] + +#proof[ +We compute $x$ coordinates by first computing $t = (y - y_0) / (y_1 - y_0)$ (with error at most $3 epsilon_0 + 4 epsilon_0^2$ by +@lem-ratio-error). Then we apply @lem-convex-combination-error with $epsilon = 3 epsilon_0 + 4 epsilon_0^2$ to see that we +can compute the horizontal coordinate with error at most $(6 epsilon_0 + 4 epsilon_0^2)M$. If $epsilon_0 <= 1/4$ as we assumed, +this is at most $7 epsilon_0 M <= delta/8$. This proves the first claim. + +For the second claim, we apply @lem-intersection-height-error with $epsilon = (6 epsilon_0 + 4 epsilon_0^2) M$. +With this $epsilon$, $epsilon + 2 epsilon_0 M) <= 9 epsilon_0 M$ and so $16/3 (epsilon + 2 epsilon_0 M) <= 48 epsilon_0 M = (3 delta)/4$. +Thus, if $alpha$ $(3 delta)/4$-crosses $beta$ then the assumptions of @lem-intersection-height-error are satisfied +(and in the conclusion, $4 (epsilon + 2 epsilon_0) M <= 36 epsilon_0 M = (9 delta) /16 $. +] + += Curves etc. + +Let's move on from lines and talk about curves. Each curve is of the form $alpha: [0, 1] -> bb(R)^2$, which we'll sometimes +in components as $alpha_0: [0, 1] -> bb(R)$ and $alpha_1: [0, 1] -> bb(R)$. We'll assume that each $alpha_0$ is a strictly +increasing function. Write $S = [-1, 1] times [-1, 1]$ for the unit square in $bb(R)^2$. For a curve $alpha$ and a set $B subset bb(R)^2$, we write +$alpha + B$ for the set + +$ +alpha + B = {(x, y) in bb(R)^2 : exists t in [0, 1] "with" (x, y) - alpha(t) in B} +$ + +In other words, $alpha + B$ is the Minkowski sum between $B$ and the locus of $alpha$. For any set $B subset bb(R)^2$, +let + +$ +B_y = {x in bb(R) : (x, y) in B}. +$ + +For example, here is a picture of $(alpha + epsilon B)_y$: (TODO) + +== The curve orders + +When moving to curves, it seems useful to work with more abstract orders. For fixed $epsilon$ and $y$, we'll define +three of them: the "weak pre-order," the "strong order," and the "weak order." + +#let weakprelt = sym.lt.tri +#let weakpreleq = sym.lt.eq.tri +#let notweakprelt = sym.lt.tri.not +#let weakpreeq = sym.eq.dots +#let weaklt = sym.lt +#let stronglt = sym.lt.double +#let weaklt = sym.lt +#let weakeq = sym.tilde + +We begin with the weak pre-order. + +#def[ + For each $epsilon >= 0$ and $y in bb(R)$, let $weakprelt_(y,epsilon)$ be a relation satisfying: + + - if $alpha weakprelt_(y,epsilon) beta$ then $(alpha + epsilon/2 S)_y <= (beta + epsilon/2 S)_y$, + - if $(alpha + epsilon S)_y <= (beta + epsilon S)_y$ then $alpha weakprelt_(y,epsilon) beta$, + - for any $k >= 1$, there are no $alpha_1, ..., alpha_k$ with $alpha_1 weakprelt_(y,epsilon) alpha_2 weakprelt_(y,epsilon) dots.h.c weakprelt_(y,epsilon) alpha_k weakprelt_(y,epsilon) alpha_1$. + + We call this last condition the "no cycles" condition. +] + +Note that we *do not* assume transitivity: $alpha weakprelt_(y,epsilon) beta +weakprelt_(y,epsilon) gamma$ does not imply that $alpha weakprelt_(y,epsilon) +gamma$. We also do not assume anti-symmetry: it's possible to have neither +$alpha weakprelt_(y,epsilon) beta$ nor $beta weakprelt_(y,epsilon) alpha$. In +fact, if $(alpha + epsilon/2)_y$ and $(beta + epsilon/2 S)_y$ overlap, it is +guaranteed that neither of these orderings hold. + +#def[ + If neither $alpha weakprelt_(y,epsilon) beta$ nor $beta weakprelt_(y,epsilon) alpha$, we say that + + $ + alpha weakpreeq_(y,epsilon) beta. + $ + + If either $alpha weakprelt_(y,epsilon) beta$ or + $alpha weakpreeq_(y,epsilon) beta$, we write + $alpha weakpreleq_(y,epsilon) beta$. Equivalently, + $alpha weakpreleq_(y,epsilon) beta$ is the negation of + $beta weakprelt_(y,epsilon) alpha$. +] + +TODO: draw a picture. + +The strong order is similar to the weak pre-order, but with bigger epsilons. +#def[ + For each $epsilon >= 0$ and $y in bb(R)$, let $stronglt_(y,epsilon)$ be a relation satisfying: + + - if $alpha stronglt_(y,epsilon) beta$ then $(alpha + 2 epsilon S)_y <= (beta + 2 epsilon S)_y$, + - if $(alpha + 3 epsilon S)_y <= (beta + 3 epsilon S)_y$ then $alpha stronglt_(y,epsilon) beta$, + - for any $k >= 1$, there are no $alpha_1, ..., alpha_k$ with $alpha_1 stronglt_(y,epsilon) alpha_2 stronglt_(y,epsilon) dots.h.c stronglt_(y,epsilon) alpha_k stronglt_(y,epsilon) alpha_1$. +] + +The definition of the weak order is a little strange at first. The initial idea +is that we want to ensure that our sweep-line is ordered by the weak pre-order: +we are going to try to ensure that if our sweep-line at $y$ is $alpha^1, ..., alpha^n$ +then for all $i < j$, $alpha^i weakpreleq_y alpha^j$. Or in other words, we want to +ensure that whenever $alpha^j$ is definitely to the right of $alpha^i$, it comes after $alpha^i$ +in the sweep-line. + +For sweep-line algorithms to be efficient, we need to avoid too many segment comparisons. +In the classical Bentley-Ottmann algorithm with exact arithmetic this is easy: whenever +we look for intersections against segment $alpha^i$, we compare it with its sweep-line +neighbors to the left and right (so, $alpha^(i-1)$ and $alpha^(i+1)$ in our notation). +Inexact arithmetic will make this a little trickier; we will typically need to look further +than just the immediate neighbors of $alpha^i$. However, we will need *some* mechanism +for early stopping, some way to say that after we have compared $alpha^i$ to, say, $alpha^(i-k)$ +through $alpha^(i-1)$, we can be sure that no other $alpha^j$ (for $j < i - k$) needs +to be checked. The intuitive idea is that if $alpha^(i-k)$ is very far to the left of $alpha^i$ +then we can stop, because then $alpha^(i-k)$ will "shield" $alpha^i$ from segments further +left, in much the same way that its immediate neighbors "shield" $alpha^i$ in the classical +sweep-line algorithm. + +We would like to use the strong order $stronglt_y$ to check whether $alpha^(i-k)$ is "very far +to the left" of $alpha^i$, but we run into trouble with examples like this: + +TODO: picture. + +Here, we clearly see that $alpha$ is to the left of $beta$; in situations like this, if we're +examining $beta$ for potential intersections and we see $alpha$ to its left, we'd really like +to stop looking further left. But $gamma$ is a problem: it's almost horizontal, and the +sweep-line at $y$ is at an ambiguous height, being more than $epsilon / 2$ but less than $epsilon$ below $gamma$. +According to the definition of our weak pre-order, +$alpha weakprelt_y gamma$ and $alpha weakpreeq_y gamma$ are +both allowed, as are both +$beta weakprelt_y gamma$ and $beta weakpreeq_y gamma$. So imagine that $alpha weakpreeq_y gamma$ and $beta weakprelt_y gamma$. +Before adding $beta$, the sweep-line could have been $(gamma, alpha)$. Then we could have tried to add $beta$ after $alpha$ +(because clearly $beta$ is to the right of $alpha$), leading to the sweep-line $(gamma, alpha, beta)$. +Scanning for any intersections with the newly-added $beta$ won't find any because we'll look to the left, see +$alpha$, and stop. The result of all this is the illegal sweep-line $(gamma, alpha, beta)$, in which +$beta weakprelt_y gamma$ but $gamma$ comes first in the sweep-line. + +Basically, it seems hard to have both early stopping (which we want for efficiency) and the +ordering invariants that we need for correctness. Our "fix" is to change the definition of +our ordering (while still keeping it tight enough for the correctness guarantees that we need). +It looks almost like cheating, because we're going to redefine it precisely so that our early +stopping will work. + +#def[ + Let $cal(S)_y$ be the set of all segments whose vertical range contains $y$. Say that $alpha weaklt_y beta$ + if $alpha weakprelt_y beta$ and there do not exist $alpha', beta' in cal(S)_y$ with $beta weakpreleq_y beta' stronglt alpha' weakpreleq_y alpha$. +] + +In other words, we define $weaklt$ to be the same as $weakprelt$, but if there's ever a situation where the (not-yet-specified) +early stopping algorithm could miss a wrongly ordered pair $alpha weakprelt beta$ by witnessing a gap between $beta'$ and $alpha'$, +then we weaken the $weakprelt$ order and declare instead that $alpha weakeq beta$ instead of $alpha weaklt beta$. +To see how this solves our tricky example above, note that when instantiating @def-weak-order with $alpha = beta$, $beta = gamma$, +$beta' = alpha$, and $alpha' = beta$, we see that there *do* exist $alpha', beta' in cal(S)_y$ with the required properties, +and so in fact $beta$ and $gamma$ are not strictly ordered in the weak order $weaklt$ even though they were strictly ordered +in the weak pre-order $weakprelt$. In particular, the sweep-line $(gamma, alpha, beta)$ is legal with respect to the weak order $weaklt$. + +You might object to @def-weak-order because of how hilariously inefficient it will be to implement. The +clever part, though, is that we won't need to implement it. Our algorithm will make its decisions based +only on the weak pre-order $weakprelt$ and the strong order $stronglt$. The inefficient weak order $weaklt$ +will only be used in the correctness proof. diff --git a/vendor/linesweeper/examples/boolean_op.rs b/vendor/linesweeper/examples/boolean_op.rs new file mode 100644 index 00000000..58214183 --- /dev/null +++ b/vendor/linesweeper/examples/boolean_op.rs @@ -0,0 +1,342 @@ +use std::path::PathBuf; + +use anyhow::anyhow; +use clap::{Args, Parser}; +use kurbo::BezPath; +use skrifa::{FontRef, MetadataProvider}; +use svg::Document; + +use linesweeper::{ + Point, generators, + topology::{BinaryWindingNumber, Topology}, +}; +use linesweeper_util::{outline_to_bezpath, svg_to_bezpaths}; + +#[derive(Copy, Clone, Debug)] +enum Op { + Union, + Intersection, + Xor, + Difference, + ReverseDifference, +} + +#[derive(Copy, Clone, Debug, clap::ValueEnum)] +enum Example { + Checkerboard, + SlantedCheckerboard, + Slanties, + Star, +} + +#[derive(Parser)] +struct Cli { + #[arg(long)] + output: PathBuf, + + #[command(flatten)] + input: Input, + + #[arg(long)] + non_zero: bool, + + #[arg(long)] + path_bool: bool, + + #[arg(long)] + epsilon: Option, +} + +#[derive(Args, Debug)] +#[group(required = true, multiple = false)] +struct Input { + input: Option, + + #[arg(long)] + example: Option, + + #[arg(long)] + char: Option>, +} + +fn points_to_bez(ps: Vec) -> BezPath { + let mut ps = ps.into_iter(); + let mut ret = BezPath::new(); + if let Some(p) = ps.next() { + ret.move_to((p.x, p.y)); + } + for p in ps { + ret.line_to((p.x, p.y)); + } + ret.close_path(); + ret +} + +fn contours_to_bezs((cs0, cs1): (Vec>, Vec>)) -> (BezPath, BezPath) { + ( + cs0.into_iter().flat_map(points_to_bez).collect(), + cs1.into_iter().flat_map(points_to_bez).collect(), + ) +} + +fn get_contours(input: &Input) -> anyhow::Result<(BezPath, BezPath)> { + match (&input.input, &input.example, &input.char) { + (Some(path), None, None) => { + let input = std::fs::read_to_string(path)?; + let tree = usvg::Tree::from_str(&input, &usvg::Options::default())?; + let mut contours = svg_to_bezpaths(&tree).into_iter(); + let first = contours.next().unwrap(); + let rest = contours.flatten().collect(); + Ok((first, rest)) + } + (None, Some(example), None) => match example { + Example::Checkerboard => Ok(contours_to_bezs(generators::checkerboard(10))), + Example::SlantedCheckerboard => { + Ok(contours_to_bezs(generators::slanted_checkerboard(10))) + } + Example::Slanties => Ok(contours_to_bezs(generators::slanties(10))), + Example::Star => Ok(generators::star(20)), + }, + (None, None, Some(chars)) => { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let font_path = format!("{ws}/tests/fonts/Inconsolata-Regular.ttf"); + let data = std::fs::read(&font_path).unwrap(); + let font_ref = FontRef::new(&data).unwrap(); + let charmap = font_ref.charmap(); + let paths = chars + .iter() + .map(|c| -> anyhow::Result<_> { + let id = charmap.map(*c).ok_or(anyhow!("{c} not in the charmap"))?; + let outline = font_ref + .outline_glyphs() + .get(id) + .ok_or(anyhow!("missing glyph for {c}"))?; + Ok(outline_to_bezpath(outline)) + }) + .collect::, _>>()?; + + Ok((paths[0].clone(), paths[1..].iter().flatten().collect())) + } + _ => unreachable!(), + } +} + +pub fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + let (shape_a, shape_b) = get_contours(&args.input)?; + + let eps = args.epsilon.unwrap_or(0.1); + let top = Topology::from_paths_binary(&shape_a, &shape_b, eps)?; + let bbox = top.bounding_box(); + let min_x = bbox.min_x(); + let min_y = bbox.min_y(); + let max_x = bbox.max_x(); + let max_y = bbox.max_y(); + let pad = 1.0 + eps; + let one_width = max_x - min_x + 2.0 * pad; + let one_height = max_y - min_y + 2.0 * pad; + let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0; + let mut document = svg::Document::new().set( + "viewBox", + (min_x - pad, min_y - pad, one_width * 3.0, one_height * 2.0), + ); + + // Draw the original document. + for c in [&shape_a, &shape_b] { + let mut data = svg::node::element::path::Data::new(); + for el in c { + let p = |point: kurbo::Point| (point.x, point.y); + data = match el { + kurbo::PathEl::MoveTo(p0) => data.move_to(p(p0)), + kurbo::PathEl::LineTo(p0) => data.line_to(p(p0)), + kurbo::PathEl::QuadTo(p0, p1) => data.quadratic_curve_to((p(p0), p(p1))), + kurbo::PathEl::CurveTo(p0, p1, p2) => data.cubic_curve_to((p(p0), p(p1), p(p2))), + kurbo::PathEl::ClosePath => data.close(), + }; + } + + let path = svg::node::element::Path::new() + .set("stroke", "black") + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("opacity", 0.2) + .set("fill", "none") + .set("d", data); + document = document.add(path); + } + + let add_one = |doc, op, x_off, y_off| { + if args.path_bool { + add_path_bool_op(doc, op, &shape_a, &shape_b, x_off, y_off, stroke_width) + } else { + add_op(doc, op, args.non_zero, &top, x_off, y_off, stroke_width) + } + }; + + document = add_one(document, Op::Union, one_width, 0.0); + document = add_one(document, Op::Intersection, one_width * 2.0, 0.0); + document = add_one(document, Op::Xor, 0.0, one_height); + document = add_one(document, Op::Difference, one_width, one_height); + document = add_one(document, Op::ReverseDifference, one_width * 2.0, one_height); + + svg::save(&args.output, &document)?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn add_op( + mut doc: Document, + op: Op, + non_zero: bool, + top: &Topology, + x_off: f64, + y_off: f64, + stroke_width: f64, +) -> Document { + let contours = top.contours(|w| { + let inside = |winding| { + if non_zero { + winding != 0 + } else { + winding % 2 != 0 + } + }; + + match op { + Op::Union => inside(w.shape_a) || inside(w.shape_b), + Op::Intersection => inside(w.shape_a) && inside(w.shape_b), + Op::Xor => inside(w.shape_a) != inside(w.shape_b), + Op::Difference => inside(w.shape_a) && !inside(w.shape_b), + Op::ReverseDifference => inside(w.shape_b) && !inside(w.shape_a), + } + }); + + let colors = [ + "#005F73", "#0A9396", "#94D2BD", "#E9D8A6", "#EE9B00", "#CA6702", "#BB3E03", "#AE2012", + "#9B2226", + ]; + + let mut color_idx = 0; + for group in contours.grouped() { + let mut data = svg::node::element::path::Data::new(); + + for contour_idx in group { + let path = &contours[contour_idx].path; + for el in path.iter() { + data = match el { + kurbo::PathEl::MoveTo(p) => data.move_to((p.x + x_off, p.y + y_off)), + kurbo::PathEl::LineTo(p) => data.line_to((p.x + x_off, p.y + y_off)), + kurbo::PathEl::QuadTo(p0, p1) => data.quadratic_curve_to(( + (p0.x + x_off, p0.y + y_off), + (p1.x + x_off, p1.y + y_off), + )), + kurbo::PathEl::CurveTo(p0, p1, p2) => data.cubic_curve_to(( + (p0.x + x_off, p0.y + y_off), + (p1.x + x_off, p1.y + y_off), + (p2.x + x_off, p2.y + y_off), + )), + kurbo::PathEl::ClosePath => data.close(), + }; + } + data = data.close(); + } + let path = svg::node::element::Path::new() + .set("d", data) + .set("stroke", "black") + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("fill", colors[color_idx]); + doc = doc.add(path); + color_idx = (color_idx + 1) % colors.len(); + } + doc +} + +fn bezpath_to_path_bool(p: &BezPath) -> Vec { + let mut ret = Vec::new(); + for seg in p.segments() { + let dv = |p: kurbo::Point| glam::DVec2::from((p.x, p.y)); + let seg = match seg { + kurbo::PathSeg::Line(line) => path_bool::PathSegment::Line(dv(line.p0), dv(line.p1)), + kurbo::PathSeg::Quad(q) => { + path_bool::PathSegment::Quadratic(dv(q.p0), dv(q.p1), dv(q.p2)) + } + kurbo::PathSeg::Cubic(c) => { + path_bool::PathSegment::Cubic(dv(c.p0), dv(c.p1), dv(c.p2), dv(c.p3)) + } + }; + ret.push(seg); + } + ret +} + +#[allow(clippy::too_many_arguments)] +fn add_path_bool_op( + mut doc: Document, + op: Op, + shape_a: &BezPath, + shape_b: &BezPath, + x_off: f64, + y_off: f64, + stroke_width: f64, +) -> Document { + let mut shape_a = bezpath_to_path_bool(shape_a); + let mut shape_b = bezpath_to_path_bool(shape_b); + let fill = path_bool::FillRule::EvenOdd; + let op = match op { + Op::Union => path_bool::PathBooleanOperation::Union, + Op::Intersection => path_bool::PathBooleanOperation::Intersection, + Op::Xor => path_bool::PathBooleanOperation::Exclusion, + Op::Difference => path_bool::PathBooleanOperation::Difference, + Op::ReverseDifference => { + std::mem::swap(&mut shape_a, &mut shape_b); + path_bool::PathBooleanOperation::Difference + } + }; + let result = path_bool::path_boolean(&shape_a, fill, &shape_b, fill, op).unwrap(); + + let colors = [ + "#005F73", "#0A9396", "#94D2BD", "#E9D8A6", "#EE9B00", "#CA6702", "#BB3E03", "#AE2012", + "#9B2226", + ]; + + let mut color_idx = 0; + for path in result { + let mut data = svg::node::element::path::Data::new(); + + if path.is_empty() { + continue; + } + let start = path[0].start(); + data = data.move_to((start.x + x_off, start.y + y_off)); + for seg in path { + data = match seg { + path_bool::PathSegment::Line(_, p) => data.line_to((p.x + x_off, p.y + y_off)), + path_bool::PathSegment::Quadratic(_, p0, p1) => data.quadratic_curve_to(( + (p0.x + x_off, p0.y + y_off), + (p1.x + x_off, p1.y + y_off), + )), + path_bool::PathSegment::Cubic(_, p0, p1, p2) => data.cubic_curve_to(( + (p0.x + x_off, p0.y + y_off), + (p1.x + x_off, p1.y + y_off), + (p2.x + x_off, p2.y + y_off), + )), + path_bool::PathSegment::Arc(..) => unimplemented!(), + }; + } + data = data.close(); + let path = svg::node::element::Path::new() + .set("d", data) + .set("stroke", "black") + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("fill", colors[color_idx]); + doc = doc.add(path); + color_idx = (color_idx + 1) % colors.len(); + } + doc +} diff --git a/vendor/linesweeper/examples/bus.rs b/vendor/linesweeper/examples/bus.rs new file mode 100644 index 00000000..88e24602 --- /dev/null +++ b/vendor/linesweeper/examples/bus.rs @@ -0,0 +1,144 @@ +use kurbo::{Affine, BezPath}; + +use linesweeper::topology::{Topology, WindingNumber}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +struct Windings { + main: i32, + cutout: i32, + modifier: i32, +} + +impl std::ops::Add for Windings { + type Output = Windings; + + fn add(self, rhs: Self) -> Self::Output { + Self { + main: self.main + rhs.main, + cutout: self.cutout + rhs.cutout, + modifier: self.modifier + rhs.modifier, + } + } +} + +impl std::ops::AddAssign for Windings { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs + } +} + +impl WindingNumber for Windings { + type Tag = Tag; + + fn single(tag: Self::Tag, positive: bool) -> Self { + let sign = if positive { 1 } else { -1 }; + match tag { + Tag::Main => Self { + main: sign, + ..Default::default() + }, + Tag::Cutout => Self { + cutout: sign, + ..Default::default() + }, + Tag::Modifier => Self { + modifier: sign, + ..Default::default() + }, + } + } + + fn of_tag(&self, tag: Self::Tag) -> Self { + match tag { + Tag::Main => Self { + main: self.main, + ..Default::default() + }, + Tag::Cutout => Self { + cutout: self.cutout, + ..Default::default() + }, + Tag::Modifier => Self { + modifier: self.modifier, + ..Default::default() + }, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum Tag { + Main, + Cutout, + Modifier, +} + +pub fn main() -> anyhow::Result<()> { + let bus = "M0,80 C0,35.8 35.8,0 80,0 L432,0 C476.2,0 512,35.8 512,80 L512,368 C512,394.2 499.4,417.4 480,432 L480,480 C480,497.7 465.7,512 448,512 L416,512 C398.3,512 384,497.7 384,480 L384,448 L128,448 L128,480 C128,497.7 113.7,512 96,512 L64,512 C46.3,512 32,497.7 32,480 L32,432 C12.6,417.4 0,394.2 0,368 L0,80 Z M129.9,152.2 L112,224 L400,224 L382.1,152.2 C378.5,138 365.7,128 351,128 L161,128 C146.3,128 133.5,138 130,152.2 Z M128,320 A32 32 0 1 0 64,320 A32 32 0 1 0 128,320 Z M416,352 A32 32 0 1 0 416,288 A32 32 0 1 0 416,352 Z"; + let cutout = "M320 512V266.8C288.1 221.6 235.5 192 176 192C78.8 192 0 270.8 0 368c0 59.5 29.6 112.1 74.8 144H320z"; + let modifier = "M144 512c79.5 0 144-64.5 144-144s-64.5-144-144-144S0 288.5 0 368s64.5 144 144 144zm67.3-164.7l-72 72c-6.2 6.2-16.4 6.2-22.6 0l-40-40c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L128 385.4l60.7-60.7c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"; + + let bus = BezPath::from_svg(bus).unwrap(); + let cutout = Affine::translate((193.0, 0.0)) * BezPath::from_svg(cutout).unwrap(); + let modifier = Affine::translate((224.0, 0.0)) * BezPath::from_svg(modifier).unwrap(); + + let eps = 1e-5; + let top = Topology::::from_paths( + [ + (&bus, Tag::Main), + (&cutout, Tag::Cutout), + (&modifier, Tag::Modifier), + ], + eps, + ) + .unwrap(); + let bbox = top.bounding_box(); + let min_x = bbox.min_x(); + let min_y = bbox.min_y(); + let max_x = bbox.max_x(); + let max_y = bbox.max_y(); + let pad = 1.0 + eps; + let one_width = max_x - min_x + 2.0 * pad; + let one_height = max_y - min_y + 2.0 * pad; + let mut doc = svg::Document::new().set( + "viewBox", + (min_x - pad, min_y - pad, one_width * 3.0, one_height * 2.0), + ); + + let contours = top.contours(|w| { + let inside = |winding| winding != 0; + + (inside(w.main) && !inside(w.cutout)) || inside(w.modifier) + }); + + for group in contours.grouped() { + let mut data = svg::node::element::path::Data::new(); + + for contour_idx in group { + let path = &contours[contour_idx].path; + for el in path.iter() { + data = match el { + kurbo::PathEl::MoveTo(p) => data.move_to((p.x, p.y)), + kurbo::PathEl::LineTo(p) => data.line_to((p.x, p.y)), + kurbo::PathEl::QuadTo(p0, p1) => { + data.quadratic_curve_to(((p0.x, p0.y), (p1.x, p1.y))) + } + kurbo::PathEl::CurveTo(p0, p1, p2) => { + data.cubic_curve_to(((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y))) + } + kurbo::PathEl::ClosePath => data.close(), + }; + } + } + let path = svg::node::element::Path::new() + .set("d", data) + .set("stroke", "black") + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round"); + doc = doc.add(path); + } + + svg::save("out.svg", &doc)?; + + Ok(()) +} diff --git a/vendor/linesweeper/examples/curve_metrics.rs b/vendor/linesweeper/examples/curve_metrics.rs new file mode 100644 index 00000000..b285911a --- /dev/null +++ b/vendor/linesweeper/examples/curve_metrics.rs @@ -0,0 +1,130 @@ +use color::ColorSpace as _; +use kurbo::{Affine, BezPath, CubicBez, PathSeg, Point}; +use linesweeper::curve::{Order, intersect_cubics, y_subsegment}; + +fn flip(c: CubicBez) -> CubicBez { + Affine::reflect(Point::ZERO, (0.0, 1.0)) * c +} + +fn shift(c: CubicBez, dx: f64) -> CubicBez { + Affine::translate((0.0, dx)) * c +} + +fn horiz_at_midpoint() -> CubicBez { + CubicBez::new((-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)) +} + +fn c() -> CubicBez { + CubicBez::new((1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)) +} + +fn diagonal() -> CubicBez { + CubicBez::new( + (-1.0, -1.0), + (-1.0 / 3.0, -1.0 / 3.0), + (1.0 / 3.0, 1.0 / 3.0), + (1.0, 1.0), + ) +} + +fn vertical() -> CubicBez { + CubicBez::new((0.0, -1.0), (0.0, -1.0 / 3.0), (0.0, 1.0 / 3.0), (0.0, 1.0)) +} + +fn to_svg(c: CubicBez) -> String { + BezPath::from_path_segments([PathSeg::Cubic(c)].into_iter()).to_svg() +} + +fn depth_color(depth: f64) -> String { + let start = 200.0; + let end = 382.0; + let [r, g, b] = color::Srgb::clip(color::Oklch::convert::([ + 0.6, + 0.3, + start + (end - start) * depth as f32, + ])); + let c = |x: f32| (x * 255.0).round() as u8; + format!("#{:02x}{:02x}{:02x}", c(r), c(g), c(b)) +} + +fn compare_curves( + c0: CubicBez, + c1: CubicBez, + eps: f64, + max_depth: u32, +) -> svg::node::element::Group { + let mut g = svg::node::element::Group::new(); + let cmp = intersect_cubics(c0, c1, 0.5 * eps, eps); + + g = g.add( + svg::node::element::Path::new() + .set("d", to_svg(c1)) + .set("fill", "none") + .set("stroke", "black") + .set("stroke-width", 0.01) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round"), + ); + + for (start, end, order) in cmp.iter() { + let color = match order { + Order::Right => "green", + Order::Ish => "red", + Order::Left => "blue", + }; + let c = y_subsegment(c0, start, end); + g = g.add( + svg::node::element::Path::new() + .set("d", to_svg(c)) + .set("fill", "none") + .set("stroke", color) + .set("stroke-width", 0.01) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round"), + ); + } + + let mut start = -1.0; + for m in &cmp.metrics.entries { + if m.depth == 0 { + start = m.end; + continue; + } + let w = (m.depth as f64 / max_depth as f64).clamp(0.01, 1.0); + let h = (m.end - start).max(0.01); + g = g.add( + svg::node::element::Rectangle::new() + .set("width", w) + .set("height", h) + .set("x", 1.5) + .set("y", start) + .set("fill", depth_color(w)), + ); + start = m.end; + } + + g +} + +pub fn main() { + let curves = [ + (diagonal(), flip(diagonal())), + (diagonal(), shift(diagonal(), 0.01)), + (c(), vertical()), + (c(), diagonal()), + (c(), flip(c())), + (c(), shift(c(), 0.01)), + (horiz_at_midpoint(), diagonal()), + (horiz_at_midpoint(), c()), + (horiz_at_midpoint(), flip(horiz_at_midpoint())), + (horiz_at_midpoint(), shift(horiz_at_midpoint(), 0.01)), + ]; + let mut doc = + svg::Document::new().set("viewBox", (0.0, 0.0, 5.5, 1.0 + 2.5 * curves.len() as f64)); + for (i, &(c0, c1)) in curves.iter().enumerate() { + let y = i as f64 * 2.5 + 1.5; + let g = compare_curves(c0, c1, 1e-6, 6).set("transform", format!("translate(0, {y})",)); + doc = doc.add(g); + } + svg::save("out.svg", &doc).unwrap(); +} diff --git a/vendor/linesweeper/examples/visualize_op.rs b/vendor/linesweeper/examples/visualize_op.rs new file mode 100644 index 00000000..ec110069 --- /dev/null +++ b/vendor/linesweeper/examples/visualize_op.rs @@ -0,0 +1,130 @@ +use std::path::PathBuf; + +use anyhow::{anyhow, bail}; +use clap::Parser; +use kurbo::BezPath; +use svg::Document; + +use linesweeper::{ + BinaryOp, FillRule, binary_op, + topology::{Contours, Topology}, +}; + +#[derive(Parser)] +struct Cli { + #[arg(long)] + output: PathBuf, + + #[arg(long)] + input: PathBuf, +} + +pub fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + let input = std::fs::read_to_string(&args.input)?; + let mut input_lines = input.lines(); + let op = input_lines.next().ok_or(anyhow!("no op line"))?; + let op = match op { + "union" => BinaryOp::Union, + "intersection" => BinaryOp::Intersection, + "difference" => BinaryOp::Difference, + "xor" => BinaryOp::Xor, + _ => bail!("unknown op {op}"), + }; + + let shape_a = input_lines.next().ok_or(anyhow!("no first shape"))?; + let shape_b = input_lines.next().ok_or(anyhow!("no second shape"))?; + + for extra_line in input_lines { + if !extra_line.trim().is_empty() { + eprintln!("ignoring extra line: {extra_line}"); + } + } + + let shape_a = BezPath::from_svg(shape_a)?; + let shape_b = BezPath::from_svg(shape_b)?; + + let eps = 1e-5; + let contours = binary_op(&shape_a, &shape_b, FillRule::EvenOdd, op)?; + let top = Topology::from_paths_binary(&shape_a, &shape_b, eps)?; + let bbox = top.bounding_box(); + let min_x = bbox.min_x(); + let min_y = bbox.min_y(); + let max_x = bbox.max_x(); + let max_y = bbox.max_y(); + let pad = 1.0 + eps; + let one_width = max_x - min_x + 2.0 * pad; + let one_height = max_y - min_y + 2.0 * pad; + let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0; + + let mut document = svg::Document::new().set( + "viewBox", + (min_x - pad, min_y - pad, one_width * 2.0, one_height), + ); + + // Draw the original document. + for (c, color) in [(&shape_a, "green"), (&shape_b, "blue")] { + let mut data = svg::node::element::path::Data::new(); + for el in c { + let p = |point: kurbo::Point| (point.x, point.y); + data = match el { + kurbo::PathEl::MoveTo(p0) => data.move_to(p(p0)), + kurbo::PathEl::LineTo(p0) => data.line_to(p(p0)), + kurbo::PathEl::QuadTo(p0, p1) => data.quadratic_curve_to((p(p0), p(p1))), + kurbo::PathEl::CurveTo(p0, p1, p2) => data.cubic_curve_to((p(p0), p(p1), p(p2))), + kurbo::PathEl::ClosePath => data.close(), + }; + } + + let path = svg::node::element::Path::new() + .set("stroke", "black") + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("opacity", 0.5) + .set("fill", color) + .set("d", data); + document = document.add(path); + } + + document = add_op(document, &contours, one_width, stroke_width); + + svg::save(&args.output, &document)?; + + Ok(()) +} + +fn add_op(mut doc: Document, contours: &Contours, x_off: f64, stroke_width: f64) -> Document { + for group in contours.grouped() { + let mut data = svg::node::element::path::Data::new(); + + for contour_idx in group { + let path = &contours[contour_idx].path; + for el in path.iter() { + data = match el { + kurbo::PathEl::MoveTo(p) => data.move_to((p.x + x_off, p.y)), + kurbo::PathEl::LineTo(p) => data.line_to((p.x + x_off, p.y)), + kurbo::PathEl::QuadTo(p0, p1) => { + data.quadratic_curve_to(((p0.x + x_off, p0.y), (p1.x + x_off, p1.y))) + } + kurbo::PathEl::CurveTo(p0, p1, p2) => data.cubic_curve_to(( + (p0.x + x_off, p0.y), + (p1.x + x_off, p1.y), + (p2.x + x_off, p2.y), + )), + kurbo::PathEl::ClosePath => data.close(), + }; + } + data = data.close(); + } + let path = svg::node::element::Path::new() + .set("d", data) + .set("stroke", "black") + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("fill", "pink"); + doc = doc.add(path); + } + doc +} diff --git a/vendor/linesweeper/examples/winding-numbers.rs b/vendor/linesweeper/examples/winding-numbers.rs new file mode 100644 index 00000000..4db6570a --- /dev/null +++ b/vendor/linesweeper/examples/winding-numbers.rs @@ -0,0 +1,108 @@ +use std::path::PathBuf; + +use clap::Parser; + +use linesweeper::topology::Topology; +use linesweeper_util::svg_to_bezpaths; + +#[derive(Parser)] +struct Args { + input: PathBuf, + output: PathBuf, + + #[arg(long)] + epsilon: Option, +} + +pub fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let input = std::fs::read_to_string(&args.input)?; + let tree = usvg::Tree::from_str(&input, &usvg::Options::default())?; + let contours = svg_to_bezpaths(&tree); + + let eps = args.epsilon.unwrap_or(0.1); + let top = + Topology::from_paths_binary(&contours[0], &contours[1..].iter().flatten().collect(), eps)?; + + let bbox = top.bounding_box(); + let min_x = bbox.min_x(); + let min_y = bbox.min_y(); + let max_x = bbox.max_x(); + let max_y = bbox.max_y(); + let pad = 8.0 + eps; + let stroke_width = (max_y - min_y).max(max_x - max_y) / 512.0; + let dot_radius = stroke_width * 1.5; + let mut document = svg::Document::new().set( + "viewBox", + ( + min_x - pad, + min_y - pad, + max_x - min_x + 2.0 * pad, + max_y - min_y + 2.0 * pad, + ), + ); + + let text_size = "8px"; + + for seg in top.segment_indices() { + let p0 = &top.point(seg.first_half()); + let p1 = &top.point(seg.second_half()); + let (x0, y0) = (p0.x, p0.y); + let (x1, y1) = (p1.x, p1.y); + let c = svg::node::element::Circle::new() + .set("r", dot_radius) + .set("cy", y0) + .set("cx", x0) + .set("opacity", 0.5) + .set("fill", "blue"); + document = document.add(c); + + let c = svg::node::element::Circle::new() + .set("r", dot_radius) + .set("cy", y1) + .set("cx", x1) + .set("opacity", 0.5) + .set("fill", "blue"); + document = document.add(c); + + let data = svg::node::element::path::Data::new() + .move_to((x0, y0)) + .line_to((x1, y1)); + let path = svg::node::element::Path::new() + .set("stroke", "black") + .set("stroke-width", stroke_width / 2.0) + .set("stroke-opacity", "0.5") + .set("d", data); + document = document.add(path); + + let nx = y1 - y0; + let ny = x0 - x1; + let norm = ((nx * nx) + (ny * ny)).sqrt(); + + let nx = nx / norm * 3.0 * dot_radius; + let ny = ny / norm * 3.0 * dot_radius; + + let text = svg::node::element::Text::new(format!( + "{:?}", + top.winding_counter_clockwise(seg.first_half()) + )) + .set("font-size", text_size) + .set("text-anchor", "start") + .set("x", (x0 + x1) / 2.0 + nx) + .set("y", (y0 + y1) / 2.0 + ny); + document = document.add(text); + + let text = + svg::node::element::Text::new(format!("{:?}", top.winding_clockwise(seg.first_half()))) + .set("font-size", text_size) + .set("text-anchor", "end") + .set("x", (x0 + x1) / 2.0 - nx) + .set("y", (y0 + y1) / 2.0 - ny); + document = document.add(text); + } + + svg::save(&args.output, &document)?; + + Ok(()) +} diff --git a/vendor/linesweeper/proptest-regressions/sweep/sweep_line.txt b/vendor/linesweeper/proptest-regressions/sweep/sweep_line.txt new file mode 100644 index 00000000..ab29177c --- /dev/null +++ b/vendor/linesweeper/proptest-regressions/sweep/sweep_line.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 87ffcdf96df9f88e13c595fd71e4c6e86eadf365efcec4c7f8f395c57fdd6f20 # shrinks to perturbations = [Subdivision { t: 0.8279203883070297, idx: 11865133298020245315, next: Base { idx: 0 } }] +cc e20fc6214f9e6b2d383bd5fe49c9c888a1b364ca32f9787f6e9427933f0ff66b # shrinks to perturbations = [Point { perturbation: PointPerturbation { x: Ulp(0), y: Ulp(1) }, idx: 11820034422209601546, next: Base { idx: 0 } }] diff --git a/vendor/linesweeper/proptest-regressions/sweep/weak_ordering.txt b/vendor/linesweeper/proptest-regressions/sweep/weak_ordering.txt new file mode 100644 index 00000000..341ba7d2 --- /dev/null +++ b/vendor/linesweeper/proptest-regressions/sweep/weak_ordering.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 9652115745f15b2b647d72abc389e36c8ee3562dfeab63f8075c9135696eac71 # shrinks to perturbations = [Point { perturbation: PointPerturbation { x: RationalPerturbation { eps: 0 }, y: RationalPerturbation { eps: 6687372532051531/72057594037927936 } }, idx: 466820082892447096, next: Base { idx: 0 } }, Point { perturbation: PointPerturbation { x: RationalPerturbation { eps: 0 }, y: RationalPerturbation { eps: 5420508530135135/576460752303423488 } }, idx: 14248258729692824517, next: Base { idx: 0 } }, Superimposition { left: Point { perturbation: PointPerturbation { x: RationalPerturbation { eps: -1501017540037737/18014398509481984 }, y: RationalPerturbation { eps: -8783504785369825/144115188075855872 } }, idx: 9515324635301739004, next: Base { idx: 0 } }, right: Base { idx: 0 } }] diff --git a/vendor/linesweeper/proptest-regressions/topology.txt b/vendor/linesweeper/proptest-regressions/topology.txt new file mode 100644 index 00000000..b580ddc1 --- /dev/null +++ b/vendor/linesweeper/proptest-regressions/topology.txt @@ -0,0 +1,11 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e06d16cadc32d2ad23df2c5f0b22509a990df8719857f47e2346e415d50443e5 # shrinks to perturbations = [Subdivision { t: 0.9394124935587298, idx: 2015950306058370793, next: Base { idx: 0 } }, Subdivision { t: 0.13141341476907323, idx: 16536748748586597932, next: Base { idx: 0 } }] +cc 66cc16b5c318ce64564d715666aa9130ee35f41ee43eb20c0f9a8cf770476ae3 # shrinks to perturbations = [Subdivision { t: 0.8093222325403372, idx: 11406037439407339269, next: Base { idx: 0 } }] +cc bf6883b0e72f307394f6942e6cca0c30d7ca5b1de20b66ebcb20c45904be4870 # shrinks to perturbations = [Point { perturbation: PointPerturbation { x: Ulp(0), y: Ulp(-1) }, idx: 13955836529066927736, next: Base { idx: 0 } }] +cc a9d1eacd0a3cf800ccfa5c5df4bf6aeba6295a40855cc8aa42a021801a1f31af # shrinks to perturbations = [Point { perturbation: PointPerturbation { x: Ulp(0), y: Ulp(1) }, idx: 12374941241701864599, next: Subdivision { t: 0.8636770242633537, idx: 14298434860461557715, next: Base { idx: 0 } } }, Base { idx: 0 }] +cc a70758452b3942fb51e74f97c3f255d77637a7b83402f2af7233dbdd53c417b3 # shrinks to perturbations = [Point { perturbation: PointPerturbation { x: Ulp(0), y: Ulp(-1) }, idx: 198564688037606045, next: Base { idx: 0 } }, Point { perturbation: PointPerturbation { x: Eps(-0.06666888378841745), y: Eps(-0.04843769070842934) }, idx: 13190014615537582403, next: Base { idx: 0 } }, Base { idx: 0 }] diff --git a/vendor/linesweeper/src/arbitrary.rs b/vendor/linesweeper/src/arbitrary.rs new file mode 100644 index 00000000..120c7d85 --- /dev/null +++ b/vendor/linesweeper/src/arbitrary.rs @@ -0,0 +1,134 @@ +//! Utilities for fuzz and/or property testing using `arbitrary`. + +use arbitrary::Unstructured; +use kurbo::{CubicBez, Point}; +use polycool::Quadratic; + +use crate::geom; + +/// Generate an arbitrary float in some range. +pub fn float_in_range( + start: f64, + end: f64, + u: &mut Unstructured<'_>, +) -> Result { + if start >= end { + return Err(arbitrary::Error::EmptyChoose); + } + let num: u32 = u.arbitrary()?; + let t = num as f64 / u32::MAX as f64; + Ok((start + t * (end - start)).clamp(start, end)) +} + +fn float(u: &mut Unstructured<'_>) -> Result { + float_in_range(-1e6, 1e6, u) +} + +fn float_at_least(min: f64, u: &mut Unstructured<'_>) -> Result { + float_in_range(min, 1e6, u) +} + +fn float_at_most(max: f64, u: &mut Unstructured<'_>) -> Result { + float_in_range(-1e6, max, u) +} + +/// Generate a float in some range, but give it a chance to be close to another float. +fn another_float_in_range( + orig: f64, + start: f64, + end: f64, + u: &mut Unstructured<'_>, +) -> Result { + let close: bool = u.arbitrary()?; + if close { + let ulps: i32 = u.int_in_range(-32..=32)?; + let scale = 1.0f64 + ulps as f64 * f64::EPSILON; + Ok((orig * scale).clamp(start, end)) + } else { + float_in_range(start, end, u) + } +} + +fn point(u: &mut Unstructured<'_>) -> Result { + Ok(Point::new(float(u)?, float(u)?)) +} + +fn point_at_least(min: f64, u: &mut Unstructured<'_>) -> Result { + Ok(Point::new(float(u)?, float_at_least(min, u)?)) +} + +fn point_at_most(max: f64, u: &mut Unstructured<'_>) -> Result { + Ok(Point::new(float(u)?, float_at_most(max, u)?)) +} + +/// Generate an arbitrary cubic Bezier, guaranteed to be monotonically increasing in y. +pub fn monotonic_bezier(u: &mut Unstructured<'_>) -> Result { + let p0 = point(u)?; + let p3 = point(u)?; + let (p0, mut p3) = if p0.y < p3.y { (p0, p3) } else { (p3, p0) }; + if p3.y == p0.y { + p3.y += 1.0; + } + + let p1 = point_at_least(p0.y, u)?; + let p2 = point_at_most(p3.y, u)?; + + let ret = CubicBez::new(p0, p1, p2, p3); + Ok(geom::monotonic_cubic_pieces(ret) + .into_iter() + .find(|x| x.piece.end().y > x.piece.start().y) + .unwrap() + .piece + .to_kurbo_cubic()) +} + +/// Generate an arbitrary cubic Bezier, guaranteed to be monotonically increasing in y. +/// +/// This generated Bezier has a chance to be "close" to `first`, for example by starting +/// at the same point or with the same tangent. +pub fn another_monotonic_bezier( + u: &mut Unstructured<'_>, + first: &CubicBez, +) -> Result { + let same_start: bool = u.arbitrary()?; + let same_start_tangent: bool = same_start && u.arbitrary()?; + + let p0 = if same_start { first.p0 } else { point(u)? }; + + let p1 = if same_start_tangent { + first.p1 + } else { + point_at_least(p0.y, u)? + }; + + let p3 = point_at_least(p0.y.max(first.p0.y), u)?; + let p2 = point_at_most(p3.y, u)?; + + let ret = CubicBez::new(p0, p1, p2, p3); + Ok(geom::monotonic_cubic_pieces(ret) + .into_iter() + .find(|x| x.piece.end().y > x.piece.start().y) + .map(|c| c.piece.to_kurbo_cubic()) + .unwrap_or(ret)) +} + +/// Generate an arbitrary quadratic function, with coefficients of roughly the scale `size`. +pub fn quadratic(size: f64, u: &mut Unstructured<'_>) -> Result { + let use_coeffs: bool = u.arbitrary()?; + if use_coeffs { + let c2 = float_in_range(-size, size, u)?; + let c1 = another_float_in_range(c2, -size, size, u)?; + let c0 = another_float_in_range(c1, -size, size, u)?; + + Ok(Quadratic::new([c0, c1, c2])) + } else { + // Generate the roots, with a bias towards an almost-repeated root. + let size = size.sqrt(); + + let r1 = float_in_range(-size, size, u)?; + let r2 = another_float_in_range(r1, -size, size, u)?; + let scale = float_in_range(-size, size, u)?; + + Ok(Quadratic::new([scale * r1 * r2, -scale * (r1 + r2), scale])) + } +} diff --git a/vendor/linesweeper/src/curve/line.rs b/vendor/linesweeper/src/curve/line.rs new file mode 100644 index 00000000..d5678e1d --- /dev/null +++ b/vendor/linesweeper/src/curve/line.rs @@ -0,0 +1,159 @@ +//! Special-case curve-comparisons for straight lines. + +use kurbo::Line; + +use crate::curve::{CurveOrder, MetricsStatus, Order}; + +fn line_at_ys(line: Line, y0: f64, y1: f64) -> (f64, f64) { + let dy_recip = (line.p1.y - line.p0.y).recip(); + let t0 = (y0 - line.p0.y) * dy_recip; + let t1 = (y1 - line.p0.y) * dy_recip; + ( + (1.0 - t0) * line.p0.x + t0 * line.p1.x, + (1.0 - t1) * line.p0.x + t1 * line.p1.x, + ) +} + +/// Checks the relative order of two line segments. +/// +/// The two line segments must both be increasing in `y`, and their `y` ranges +/// must overlap. +pub fn intersect_lines(l0: Line, l1: Line, tolerance: f64) -> CurveOrder { + let y0 = l0.p0.y.max(l1.p0.y); + let y1 = l0.p1.y.min(l1.p1.y); + let mut ret = CurveOrder::new(y0); + let status = MetricsStatus::default(); + + debug_assert!(y0 <= y1); + + let (l0x0, l0x1) = line_at_ys(l0, y0, y1); + let (l1x0, l1x1) = line_at_ys(l1, y0, y1); + let dx0 = l1x0 - l0x0; + let dx1 = l1x1 - l0x1; + + if dx0.abs() <= tolerance && dx1.abs() <= tolerance { + ret.push(y1, Order::Ish, status); + return ret; + } else if (dx0 - dx1).abs() <= tolerance / 2.0 { + // The lines are almost parallel: we'll mark them as always having the + // same order. + let order = if dx0 <= -tolerance || dx1 <= -tolerance { + Order::Right + } else if dx0 >= tolerance || dx1 >= tolerance { + Order::Left + } else { + Order::Ish + }; + ret.push(y1, order, status); + return ret; + } + + // The t at which l0 crosses l1 - tolerance. + let t0 = (dx0 + tolerance) / (dx0 - dx1); + let t1 = (dx0 - tolerance) / (dx0 - dx1); + let z0 = y0 + (y1 - y0) * t0; + let z1 = y0 + (y1 - y0) * t1; + + // For a second, consider the whole lines and not the segments. If dx0 < dx1, + // l0 starts off Left, then they become Ish, then l0 is Right. If dx1 > dx0, + // they go Right, Ish, Left. + // + // Once we restrict to the line segments instead of the lines, the basic + // orderings are the same but they might get truncated. + if dx0 < dx1 { + debug_assert!(z0 <= z1); + if z0 > y0 { + ret.push(z0.min(y1), Order::Right, status); + if z0 >= y1 { + return ret; + } + } + if z1 > y0 && z1 > z0 { + ret.push(z1.min(y1), Order::Ish, status); + if z1 >= y1 { + return ret; + } + } + ret.push(y1, Order::Left, status); + } else { + debug_assert!(z0 >= z1); + if z1 > y0 { + ret.push(z1.min(y1), Order::Left, status); + if z1 >= y1 { + return ret; + } + } + if z0 > y0 && z0 > z1 { + ret.push(z0.min(y1), Order::Ish, status); + if z0 >= y1 { + return ret; + } + } + ret.push(y1, Order::Right, status); + } + + ret +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_intersection() { + let l0 = Line::new((0.0, 0.0), (0.0, 1.0)); + let l1 = Line::new((1.0, 0.0), (-1.0, 1.0)); + + let order = intersect_lines(l0, l1, 1e-6); + assert_eq!(order.cmps.len(), 3); + assert_eq!(order.cmps[0].order, Order::Left); + assert_eq!(order.cmps[1].order, Order::Ish); + assert_eq!(order.cmps[2].order, Order::Right); + assert!((order.cmps[0].end - 0.5).abs() < 1e-5); + + let order = intersect_lines(l1, l0, 1e-6); + assert_eq!(order.cmps.len(), 3); + assert_eq!(order.cmps[0].order, Order::Right); + assert_eq!(order.cmps[1].order, Order::Ish); + assert_eq!(order.cmps[2].order, Order::Left); + assert!((order.cmps[0].end - 0.5).abs() < 1e-5); + } + + #[test] + fn unchanging() { + let l0 = Line::new((-1.0, 0.0), (-1.0, 1.0)); + let l1 = Line::new((1.0, 0.0), (1.0, 1.0)); + + let order = intersect_lines(l0, l1, 1e-6); + assert_eq!(order.cmps.len(), 1); + assert_eq!(order.cmps[0].order, Order::Left); + + let l0 = Line::new((-2.0, 0.0), (0.0, 1.0)); + let l1 = Line::new((1.0, 0.0), (1.0, 1.0)); + + let order = intersect_lines(l0, l1, 1e-6); + assert_eq!(order.cmps.len(), 1); + assert_eq!(order.cmps[0].order, Order::Left); + } + + #[test] + fn close_near_endpoint() { + let l0 = Line::new((0.0, 0.0), (0.0, 1.0)); + let l1 = Line::new((1e-7, 0.0), (-1.0, 1.0)); + + let order = intersect_lines(l0, l1, 1e-6); + assert_eq!(order.cmps.len(), 2); + assert_eq!(order.cmps[0].order, Order::Ish); + assert_eq!(order.cmps[1].order, Order::Right); + assert!(order.cmps[0].end.abs() < 1e-5); + + let l0 = Line::new((0.0, 0.0), (0.0, 1.0)); + let l1 = Line::new((-1.0, 0.0), (1e-7, 1.0)); + + let order = intersect_lines(l0, l1, 1e-6); + assert_eq!(order.cmps.len(), 2); + assert_eq!(order.cmps[0].order, Order::Right); + assert_eq!(order.cmps[1].order, Order::Ish); + assert!((order.cmps[0].end - 1.0).abs() < 1e-5); + } +} diff --git a/vendor/linesweeper/src/curve/mod.rs b/vendor/linesweeper/src/curve/mod.rs new file mode 100644 index 00000000..f6023085 --- /dev/null +++ b/vendor/linesweeper/src/curve/mod.rs @@ -0,0 +1,2032 @@ +//! Curve comparison utilities. +use arrayvec::ArrayVec; +use kurbo::{CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez, Shape, Vec2}; +use polycool::{Cubic, Poly, Quadratic}; +use smallvec::SmallVec; + +pub mod line; +pub(crate) mod transverse; + +#[derive(Clone, Debug, PartialEq)] +struct CurveEntry { + end: f64, + order: T, +} + +type CurveOrderEntry = CurveEntry; + +impl CurveOrderEntry { + fn flip(&self) -> Self { + Self { + end: self.end, + order: match self.order { + Order::Right => Order::Left, + Order::Ish => Order::Ish, + Order::Left => Order::Right, + }, + } + } +} + +/// Observability for the curve comparison algorithm, recording recursion +/// depth by y-interval. +#[derive(Clone, Debug, PartialEq, Default)] +pub struct Metrics { + /// Each interval records a single recursion depth measurement. + pub entries: Vec, +} + +/// A single observability entry. +#[derive(Clone, Debug, PartialEq)] +pub struct MetricsEntry { + /// The y endpoint of this entry. (The start point is the endpoint + /// of the previous entry.) + pub end: f64, + /// The recursion depth of this entry, starting from zero. + pub depth: u32, +} + +impl MetricsEntry { + fn new(end: f64, status: MetricsStatus) -> Self { + Self { + end, + depth: status.depth, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Default)] +struct MetricsStatus { + depth: u32, +} + +impl MetricsStatus { + fn bump(&self) -> Self { + Self { + depth: self.depth + 1, + } + } +} + +/// The outcome of analyzing two curves for horizontal ordering. +/// +/// We assume that the two curves are monotonic in y, and we partition their +/// common y extent into sub-intervals, each of which gets assigned an order. +/// +/// For example, consider the curves c0 and c1 below. +/// +/// ```text +/// c0 c1 +/// y=0 \ / +/// \ / +/// | / +/// y=1 ─┼─ +/// / │ +/// / │ +/// y=2 / │ +/// ``` +/// +/// They start out with c0 to the left, cross over, and end up with c0 to the +/// right. We might represent this situation with a `CurveOrder` that says +/// +/// - `Order::Left` on the interval `[0, 0.99]` +/// - `Order::Ish` in the interval `[0.99, 1.01]`, and +/// - `Order::Right` on the interval `[1.01, 2]`. +/// +/// Note that this representation has some slack around the intersection point, +/// because we do everything numerically and there might be some errors. +#[derive(Clone, Debug)] +pub struct CurveOrder { + start: f64, + cmps: SmallVec<[CurveOrderEntry; 3]>, + /// Observability metrics for the intersection algorithm. + pub metrics: Metrics, +} + +/// An iterator over intervals in a [`CurveOrder`]. +pub struct CurveOrderIter<'a, T> { + next: f64, + iter: std::slice::Iter<'a, CurveEntry>, +} + +/// The different ways in which two curves can interact. +#[derive(Clone, Copy, Debug)] +pub enum CurveInteraction { + /// The curves cross between two heights. + Cross(f64, f64), + /// The curves touch between two heights, but don't actually cross. + Touch(f64, f64), +} + +impl Iterator for CurveOrderIter<'_, T> { + type Item = (f64, f64, T); + + fn next(&mut self) -> Option { + let next_entry = self.iter.next()?; + let start = self.next; + self.next = next_entry.end; + Some((start, next_entry.end, next_entry.order)) + } +} + +// TODO: we've grown a weird set of convenience methods. Design them better. +impl CurveOrder { + fn new(start: f64) -> Self { + CurveOrder { + start, + cmps: SmallVec::new(), + metrics: Metrics::default(), + } + } + + fn push(&mut self, end: f64, order: Order, status: MetricsStatus) { + self.metrics.entries.push(MetricsEntry::new(end, status)); + if let Some(last) = self.cmps.last_mut() { + match (last.order, order) { + (Order::Right, Order::Right) + | (Order::Left, Order::Left) + | (Order::Ish, Order::Ish) => { + debug_assert!(end >= last.end); + last.end = end; + } + (Order::Right, Order::Left) | (Order::Left, Order::Right) => { + // It would be nice if we could forbid this case, but the + // combination of almost-horizontal segments and errors in + // y position make it hard to avoid. + // + // What we do is insert a zero-height "ish" interval. Then + // adding y-slop will turn it into a taller interval. + debug_assert!(end >= last.end); + self.cmps.push(CurveOrderEntry { + end, + order: Order::Ish, + }); + self.cmps.push(CurveOrderEntry { end, order }); + } + _ => { + debug_assert!(end >= last.end); + if end > last.end { + self.cmps.push(CurveOrderEntry { end, order }); + } + } + } + } else { + self.cmps.push(CurveOrderEntry { end, order }); + } + } + + /// Returns an iterator over the ordering intervals. + pub fn iter(&self) -> CurveOrderIter<'_, Order> { + CurveOrderIter { + next: self.start, + iter: self.cmps.iter(), + } + } + + /// Returns a `CurveOrder` with all the orderings flipped. + pub fn flip(&self) -> Self { + Self { + start: self.start, + cmps: self.cmps.iter().map(CurveOrderEntry::flip).collect(), + metrics: self.metrics.clone(), + } + } + + /// Imagine that our curve is to the left of the other curve at height `y`. + /// What's the next height at which they touch? + /// + /// We "imagine" that our curve is to the left, but we don't actually insist + /// on it: if our curve is actually to the right at `y` we just say that + /// they cross immediately. + pub fn next_touch_after( + &self, + y: f64, + ) -> Option { + let mut iter = self + .iter() + .skip_while(|(_start, end, _order)| end <= &y) + .skip_while(|(_start, _end, order)| *order == Order::Left); + + let (y0, y1, order) = iter.next()?; + if order == Order::Right { + return Some(CurveInteraction::Cross(y, y)); + } + + let Some((_, next_y1, next_order)) = iter.next() else { + return Some(CurveInteraction::Touch(y0, y1)); + }; + + match next_order { + Order::Right => Some(CurveInteraction::Cross(y0, y1)), + Order::Left => { + if y < y0 { + Some(CurveInteraction::Touch(y0, y1)) + } else { + self.next_touch_after(next_y1) + } + } + Order::Ish => { + // The current order is Ish and adjacent orders get merged, + // so the next one isn't Ish. + unreachable!(); + } + } + } + + /// Adds some vertical imprecision to the order comparison. + /// + /// To understand why we need this, note that + /// - All our computations are approximate, and so the computed `y` values where orders + /// change will not be exact. When curves are almost horizontal, this error in `y` + /// can lead to a large error in `x`. + /// - For the sweep-line algorithm to work, we need the strong order to have no loops: + /// if at some height `y`, `c0` is left of `c1` and `c1` is left of `c2` then `c0` + /// is not allowed to be left of `c2`. + /// + /// Together, these two properties present a problem: the approximation + /// error in `y` can lead to ordering loops for some specific heights `y` + /// where there's a lot of crossing action. (To be honest, I'm not sure that this ever + /// actually happens. Fuzzing didn't find an example, and I didn't try too hard to + /// find one manually. But the possibility of ordering loops keeps me up at night, + /// so let's just assume that they might happen.) + /// + /// Our solution to this issue is to admit that all of our `y` values are imprecise, + /// by expanding the "ish" regions. This method expands all the "ish" regions by `slop` + /// in both directions. Geometrically, after applying y-slop, you end up with a comparison + /// where `c0` is declared "left" of `c1` at `y` if a small square around the point on `c0` + /// at height `y` stays to the left of `c1`. (If one segment is shorter than the other, + /// this is not quite true near the endpoints of the shorter segment. But close enough.) + pub fn with_y_slop(self, slop: f64) -> CurveOrder { + if slop == 0.0 { + return self; + } + + let mut ret = SmallVec::<[CurveOrderEntry; 3]>::new(); + + // unwrap: cmps is always non-empty + let last_end = self.cmps.last().unwrap().end; + + if self.start == last_end || self.cmps.len() <= 1 { + return self; + } + + for (_start, end, order) in self.iter() { + if order == Order::Ish { + let new_end = (end + slop).min(last_end); + if let Some(last) = ret.last_mut() + && last.order == Order::Ish + { + // The input order shouldn't have two "ish"es in a row, but in a previous + // iteration we may have deleted a non-ish entry if it was too short. In + // that case, we extend the previos "ish" instead of making a new one. + last.end = new_end; + } else { + ret.push(CurveOrderEntry { + end: new_end, + order: Order::Ish, + }); + } + } else { + let new_end = end - slop; + if new_end > self.start && ret.last().is_none_or(|last| last.end < new_end) { + ret.push(CurveOrderEntry { + end: new_end, + order, + }); + } + } + } + + // If we ended with a definite order, the loop above will have shortened it + // but we actually want it to extend to the end. + if let Some(last) = ret.last_mut() + && last.end < last_end + { + last.end = last_end; + } + if ret.is_empty() { + ret.push(CurveOrderEntry { + end: last_end, + order: Order::Ish, + }); + } + + CurveOrder { + start: self.start, + cmps: ret, + metrics: Metrics::default(), + } + } + + /// Asserts that we satisfy our internal invariants. For testing only. + pub fn check_invariants(&self) { + let mut cmps = self.cmps.iter(); + let mut last = cmps.next().unwrap(); + for cmp in cmps { + assert!(last.end <= cmp.end); + assert!(last.order != cmp.order); + assert!(last.order == Order::Ish || cmp.order == Order::Ish); + last = cmp; + } + } + + /// What's the order at `y`? + /// + /// If `y` is at the boundary of two intervals, takes the first one. + /// + /// TODO: maybe it would be more consistent with the other methods if + /// we took the second one. But then we need to be careful about what + /// happens at the endpoint... + /// + /// # Panics + /// + /// Panics if `y` is outside the range of our ordering. + pub fn order_at(&self, y: f64) -> Order { + self.iter() + .find(|(_start, end, _order)| end >= &y) + .unwrap() + .2 + } + + /// Returns the first order entry ending after `y`.:w + /// + /// # Panics + /// + /// Panics if our comparison range ends at or before `y`. + pub fn entry_at(&self, y: f64) -> (f64, f64, Order) { + self.iter().find(|(_start, end, _order)| *end > y).unwrap() + } + + /// What's the next definite (`Left` or `Right`) ordering after `y`? + /// + /// If there is no definite ordering (everything after `y` is just `Ish`), + /// returns `Ish`. + /// + /// As a corner case, if this comparison ends exactly at `y` then we return + /// the ordering exactly at `y`. + pub fn order_after(&self, y: f64) -> Order { + if y == self.cmps.last().unwrap().end { + self.cmps.last().unwrap().order + } else { + self.iter() + .skip_while(|(_start, end, _order)| end <= &y) + .find(|(_start, _end, order)| *order != Order::Ish) + .map_or(Order::Ish, |(_start, _end, order)| order) + } + } +} + +/// Find the parameter `t` at which `c` crosses height `y`. +pub fn solve_t_for_y(c: CubicBez, y: f64) -> f64 { + debug_assert!( + c.p0.y <= y && y <= c.p3.y && c.p0.y < c.p3.y, + "invalid y ({y}) for curve ({c:?})" + ); + + if y == c.p0.y { + return 0.0; + } + if y == c.p3.y { + return 1.0; + } + + let c3 = c.p3.y - 3.0 * c.p2.y + 3.0 * c.p1.y - c.p0.y; + let c2 = 3.0 * (c.p2.y - 2.0 * c.p1.y + c.p0.y); + let c1 = 3.0 * (c.p1.y - c.p0.y); + let c0 = c.p0.y - y; + let cubic = Cubic::new([c0, c1, c2, c3]); + + let eps = 1e-12; + let roots = cubic.roots_between(0.0, 1.0, eps); + if !roots.is_empty() { + return roots[0]; + } + + // There are situations (discovered by fuzzing) where because of rounding + // the cubic doesn't actually change signs. (Mathematically, it does change + // signs because it's `c.p0.y - y` at zero and `c.p3.y - y` at one, and we + // checked that those have opposite signs.) + // + // In this situation, it must be that the cubic was very close to zero + // at some endpoint, but after rounding the sign flipped. + debug_assert_eq!(cubic.eval(0.0).signum(), cubic.eval(1.0).signum()); + if (y - c.p0.y).abs() <= (y - c.p3.y).abs() { + 0.0 + } else { + 1.0 + } +} + +/// Finds the x coordinate at which `c` crosses through `y`. +pub fn solve_x_for_y(c: CubicBez, y: f64) -> f64 { + c.eval(solve_t_for_y(c, y)).x +} + +/// Restricts a Bézier curve to a vertical range. +/// +/// The input curve should be monotonic in `y`, and its range should include +/// `y0` and `y1` (which should be ordered). +pub fn y_subsegment(c: CubicBez, y0: f64, y1: f64) -> CubicBez { + debug_assert!(y0 < y1); + debug_assert!(c.p0.y <= y0 && y1 <= c.p3.y); + let t0 = solve_t_for_y(c, y0); + let t1 = solve_t_for_y(c, y1); + let mut ret = c.subsegment(t0..t1); + ret.p0.y = y0; + ret.p3.y = y1; + ret +} + +/// Specifies the roots of a quadratic, along with the signs taken before, after, and between the roots. +#[derive(Clone, Copy, Debug)] +pub struct QuadraticSigns { + /// The smaller root. + pub smaller_root: f64, + /// The bigger root. + pub bigger_root: f64, + /// The sign of the quadratic in the limit at `f64::NEG_INFINITY`. + /// + /// If this is zero, the quadratic is (approximately) zero everywhere. + /// Otherwise, the quadratic takes this sign until the first root, then the + /// opposite sign until the second root, then this sign again. + pub initial_sign: f64, +} + +/// An approximate horizontal ordering. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Order { + /// The first thing is to the right of the second thing. + Right, + /// The two things are close. + Ish, + /// The first thing is to the left of the second thing. + Left, +} + +impl Order { + /// Returns the opposite ordering. + pub fn flip(self) -> Order { + match self { + Order::Right => Order::Left, + Order::Ish => Order::Ish, + Order::Left => Order::Right, + } + } +} + +/// An approximate horizontal ordering. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum MaybeOrder { + /// The first thing is to the right of the second thing. + Right, + /// The two things are close. + Ish, + /// The first thing is to the left of the second thing. + Left, + /// We don't have enough information to tell yet. + Unknown, +} + +impl From for MaybeOrder { + fn from(ord: Order) -> Self { + match ord { + Order::Right => MaybeOrder::Right, + Order::Ish => MaybeOrder::Ish, + Order::Left => MaybeOrder::Left, + } + } +} + +impl TryFrom for Order { + type Error = (); + + fn try_from(ord: MaybeOrder) -> Result { + match ord { + MaybeOrder::Right => Ok(Order::Right), + MaybeOrder::Ish => Ok(Order::Ish), + MaybeOrder::Left => Ok(Order::Left), + MaybeOrder::Unknown => Err(()), + } + } +} + +/// What direction does the curve go? +#[derive(Clone, Copy, Debug)] +pub enum Direction { + /// The curve's `x` coordinate is increasing in `t`. + Increasing, + /// The curve's `x` coordinate is decreasing in `t`. + Decreasing, +} + +#[derive(Clone, Copy, Debug)] +enum Bound { + Left, + Right, +} + +/// A buffer for accumulating ordering comparisons from a [`QuadraticApprox`]. +#[derive(Clone, Debug, Default)] +pub struct ApproxComparison { + /// The order entries. + entries: ArrayVec, CAP>, +} + +/// There are up to 27 of them, because each quadratic comparison can +/// produce five entries (left, unknown, ish, unknown, right, unknown, ish, unknown, left). And when the +/// almost-horizontal robustness code kicks in, a curve comparison can +/// trigger up to three quadratic comparisons. +type QuadApproxComparison = ApproxComparison<27>; + +struct MergedRangeIter { + a_iter: I, + b_iter: J, + a_cur: Option<(f64, S)>, + b_cur: Option<(f64, T)>, +} +impl MergedRangeIter +where + I: Iterator, + J: Iterator, + S: Clone, + T: Clone, +{ + fn new(mut a: I, mut b: J) -> Self { + Self { + a_cur: a.next(), + b_cur: b.next(), + a_iter: a, + b_iter: b, + } + } +} + +impl Iterator for MergedRangeIter +where + I: Iterator, + J: Iterator, + S: Clone, + T: Clone, +{ + type Item = (f64, S, T); + + fn next(&mut self) -> Option { + if let (Some((a, a_payload)), Some((b, b_payload))) = + (self.a_cur.clone(), self.b_cur.clone()) + { + let end = a.min(b); + if a == end { + self.a_cur = self.a_iter.next(); + } + if b == end { + self.b_cur = self.b_iter.next(); + } + Some((end, a_payload, b_payload)) + } else { + None + } + } +} + +impl ApproxComparison { + fn push(&mut self, end: f64, order: MaybeOrder) { + debug_assert!(self.entries.last().is_none_or(|e| e.end < end)); + if let Some(e) = self.entries.last_mut() + && e.order == order + { + e.end = end; + } else { + self.entries.push(CurveEntry { end, order }); + } + } + + fn merge_from( + a: &ApproxComparison, + b: &ApproxComparison, + merge: impl Fn(MaybeOrder, MaybeOrder) -> MaybeOrder, + ) -> Self { + let mut a_iter = a.entries.iter(); + let mut b_iter = b.entries.iter(); + let mut ret = Self::default(); + let mut a_cur = a_iter.next(); + let mut b_cur = b_iter.next(); + + while let (Some(a), Some(b)) = (a_cur, b_cur) { + let end = a.end.min(b.end); + ret.push(end, merge(a.order, b.order)); + if a.end == end { + a_cur = a_iter.next(); + } + if b.end == end { + b_cur = b_iter.next(); + } + } + ret + } + + fn iter(&self, start: f64) -> CurveOrderIter<'_, MaybeOrder> { + CurveOrderIter { + next: start, + iter: self.entries.iter(), + } + } +} + +/// Partitions the real line according to the sign of `q`. The return value is +/// `(initially_positive, r0, r1)`, where `r0 < r1`. If `initially_positive`, +/// the quadratic is positive up to `r0`, then negative up to `r1`, and then +/// positive again up to infinity. +/// +/// It is perfectly normal for `r0` or `r1` (or both) to be infinite. For example, +/// if `r0` is `f64::NEG_INFINITY` and `initially_positive` is `true` it means that +/// the quadratic is positive up to `f64::NEG_INFINITY` (which is vacuous, really) +/// and then negative from there until `r1`. +pub(crate) fn quadratic_signs(q: Quadratic) -> (bool, f64, f64) { + let &[c, b, a] = q.coeffs(); + let disc = b * b - 4.0 * a * c; + // The handling of b here is a little confusing, and it has to do with how + // the "roots" come out in the linear case. If a is zero, and b is not we + // find roots at `-a.signum() * b.signum() * f64::INFINITY` and something + // finite. So if a is positive zero and b is positive, the first root we + // find will be negative infinity and so we want the sign *after* that first + // root to be negative, and so the initial sign should be positive. And if a + // is positive zero and b is negative, our first root will be finite and we + // want the sign before it to be positive. If a is negative zero then things + // swap around, and the summary is that if a is zero and b is not, the initial + // sign should be the sign of a. Hooray for signed zero! + let initially_negative = a < 0.0 + || (a == 0.0 && b != 0.0 && a.signum() == -1.0) + || (a == 0.0 && b == 0.0 && c < 0.0); + let numerator = if disc.is_finite() { + if disc <= 0.0 { + // There are no sign changes: the sign of `q` is always the sign of `a`. + return (!initially_negative, f64::NEG_INFINITY, f64::NEG_INFINITY); + } else { + // There are two choices for the sign here. This choice gives the + // root with larger magnitude, which is better for numerical stability + // because we're going to divide by it to find the other root. + // + // See https://math.stackexchange.com/questions/866331 for details. + -0.5 * (b + disc.sqrt().copysign(b)) + } + } else { + // We're trying to compute + // + // b + sqrt(b^2 - 4 a c) + // + // but the discriminant overflowed, so rewrite it as + // + // b (1 + sqrt(1 - 4 a c / b^2)) + // + // If the discriminant here overflows it must be because a * c dominates, + // especially if we do the computation in this weird order: + let scaled_disc = 1.0 - (c / b / b * a) * 4.0; + if !scaled_disc.is_finite() { + if c * a > 0.0 { + // disc must have overflowed to -infinity. There are no real roots. + return (!initially_negative, f64::NEG_INFINITY, f64::NEG_INFINITY); + } else { + // disc must have overflowed to +infinity, meaning that + // there are two roots but they're so far out that the + // equation is effectively constant. The sign is the opposite + // of the leading term's sign: the roots are super far out, + // so the effective sign most of the time is opposite of the + // sign at infinity. + return (initially_negative, f64::NEG_INFINITY, f64::NEG_INFINITY); + } + } + -0.5 * b * (1.0 + scaled_disc.sqrt()) + }; + + let r1 = numerator / a; + let r2 = c / numerator; + let (r1, r2) = if r1 <= r2 { (r1, r2) } else { (r2, r1) }; + (!initially_negative, r1, r2) +} + +/// Populates `out` according to the sign of `q`, restricted to +/// the interval between `y0` and `y1`. `neg` is the value to put +/// in `out` when `q` is negative, and `pos` is the value to put in +/// `out` when `q` is positive. +fn push_quadratic_sign( + q: Quadratic, + neg: Order, + pos: Order, + y0: f64, + y1: f64, + out: &mut ArrayVec, CAP>, +) where + T: PartialEq, + Order: Into, +{ + let mut push = |end: f64, order: Order| { + let order = order.into(); + debug_assert!(out.last().is_none_or(|e| e.end < end)); + if let Some(e) = out.last_mut() + && e.order == order + { + e.end = end; + } else { + out.push(CurveEntry { end, order }); + } + }; + + let (initially_positive, r1, r2) = quadratic_signs(q); + let (initial_sign, next_sign) = if initially_positive { + (pos, neg) + } else { + (neg, pos) + }; + + let r1 = r1.min(y1); + let r2 = r2.min(y1); + if r1 > y0 { + push(r1, initial_sign); + } + if r2 > r1 && r2 > y0 { + push(r2, next_sign); + } + if y1 > r2 { + push(y1, initial_sign); + } +} + +/// Analyze the sign of a quadratic. +/// +/// Consider the quadratic equation `q(y)` for `y` between `y0` and `y1`, and some +/// thresholds `lower < upper`. We consider the quadratic "less" if it's smaller than `lower`, +/// "greater" if it's bigger than `upper`, and "unknown" if it's between the two thresholds. +/// We push the results of this sign analysis to `out`. +fn push_two_level_quadratic_signs( + q: Quadratic, + lower: f64, + upper: f64, + y0: f64, + y1: f64, + out: &mut QuadApproxComparison, +) { + debug_assert!(lower < upper); + + let mut push = |end: f64, order: MaybeOrder| { + if end > y0 + && out + .entries + .last() + .is_none_or(|last| last.end < y1 && last.end < end) + { + out.push(end.min(y1), order); + } + }; + let lower_q = raise_quadratic(q, -lower); + let upper_q = raise_quadratic(q, -upper); + + let (lower_initially_positive, r_lower, s_lower) = quadratic_signs(lower_q); + let (upper_initially_positive, r_upper, s_upper) = quadratic_signs(upper_q); + // dbg!(r_lower, s_lower, r_upper, s_upper); + + use MaybeOrder::*; + if upper_initially_positive { + debug_assert!(r_upper <= r_lower || r_lower.is_infinite()); + debug_assert!(s_lower <= s_upper); + + push(r_upper, Left); + push(r_lower, Unknown); + push(s_lower, Right); + push(s_upper, Unknown); + } else { + debug_assert!(r_upper >= r_lower || r_upper.is_infinite()); + debug_assert!(s_lower >= s_upper); + + push(r_lower, Right); + push(r_upper, Unknown); + push(s_upper, Left); + push(s_lower, Unknown); + } + let final_sign = if upper_initially_positive && lower_initially_positive { + Left + } else if !upper_initially_positive && !lower_initially_positive { + Right + } else { + Unknown + }; + push(y1, final_sign); +} + +/// An axis-aligned quadratic approximation to a cubic Bézier. +/// +/// The quadratic here gives `y` as a function of `x`. +#[derive(Clone, Copy, Debug)] +pub struct QuadraticApprox { + /// The quadratic approximation. + pub q: Quadratic, + /// The amount by which the approximation undershoots the real + /// value. Always non-positive. + /// + /// To be precise, the true `x` coordinate of the curve we're + /// approximating will be at least ` + dmin`. + /// + /// Note that while this value is intended to bound the error, + /// this isn't strictly a guarantee: we use floating-point to + /// compute it and we aren't careful with rounding direction. + /// But because the basic algorithm has some slack built in, + /// it's very likely that the slack dominates the floating-point + /// error. So this *probably* is a bound, and our sweep-line + /// algorithm treats it as one. + pub dmin: f64, + /// The amount by which the approximation overshoots the real + /// value. Always non-negative. + /// + /// To be precise, the true `x` coordinate of the curve we're + /// approximating will be at most ` + dmax`. + pub dmax: f64, + /// If non-`None`, this is an approximation to part of curve that is + /// almost-horizontal, and which needs a little extra care in collision + /// detection to account for the fact that a small `y` error in subdividing + /// the curve might have led to a large `x` error. + pub almost_horiz: Option<(Direction, f64, f64)>, +} + +fn raise_quadratic(q: Quadratic, c: f64) -> Quadratic { + let &[c_old, b, a] = q.coeffs(); + Quadratic::new([c_old + c, b, a]) +} + +// Returns the quadratic representing `q(y - shift)`. +fn shift_quadratic(q: Quadratic, shift: f64) -> Quadratic { + let &[c, b, a] = q.coeffs(); + Quadratic::new([c + a * shift * shift - b * shift, b - 2.0 * shift * a, a]) +} + +impl QuadraticApprox { + /// Compute an approximation for a cubic Bézier, which we assume to be + /// monotonically increasing in `y`. + /// + /// Note that this can produce very large coefficients if + /// + /// - the Bézier's y range is small, or + /// - any of the y coordinates is large + /// + /// The second effect can be mitigated by translation (especially if + /// the y range is small *and* the y coordinates are large, because then + /// translation would make them all small), and the first effect can be + /// mitigated by rescaling. + pub fn from_cubic(c: CubicBez, almost_horiz: Option) -> Self { + let q = fit_quadratic(c); + let &[c0, c1, c2] = q.coeffs(); + + // To compare the polynomial c0 + c1 y + c2 y^2 to our Bezier curve, + // we reparametrize it in t by expanding out the cubic y(t). This + // gives a degree-6 polynomial, and then we subtract off x(t) to + // find the error. + let y = cubic_from_bez_y(c); + let x = cubic_from_bez_x(c); + let [y0, y1, y2, y3] = *y.coeffs(); + let [x0, x1, x2, x3] = *x.coeffs(); + let error = Poly::new([ + c0 + c1 * y0 + c2 * y0 * y0 - x0, + c1 * y1 + c2 * (2.0 * y0 * y1) - x1, + c1 * y2 + c2 * (2.0 * y0 * y2 + y1 * y1) - x2, + c1 * y3 + c2 * 2.0 * (y0 * y3 + y1 * y2) - x3, + c2 * (2.0 * y1 * y3 + y2 * y2), + c2 * 2.0 * y2 * y3, + c2 * y3 * y3, + ]); + let extrema = error.deriv().roots_between(0.0, 1.0, 1e-8); + + // Errors at the endpoints. These could also be computed more slowly + // as -error.eval(0.0) and -error.eval(1.0). + let err0 = c.p0.x - q.eval(c.p0.y); + let err1 = c.p3.x - q.eval(c.p3.y); + + let mut dmin = 0.0f64.min(err0).min(err1); + let mut dmax = 0.0f64.max(err0).max(err1); + + for t in extrema { + let e = -error.eval(t); + dmin = dmin.min(e); + dmax = dmax.max(e); + } + + QuadraticApprox { + q, + dmin, + dmax, + almost_horiz: almost_horiz.map(|d| (d, c.p0.x, c.p3.x)), + } + } + + /// Translate this quadratic approximation in y. + pub fn shift_y(&self, amount: f64) -> Self { + Self { + q: shift_quadratic(self.q, amount), + ..*self + } + } + + fn initial_bound(&self, which: Bound, tolerance: f64) -> Option { + match (self.almost_horiz, which) { + (Some((Direction::Increasing, x, _)), Bound::Left) => { + Some(Quadratic::new([x - tolerance, 0.0, 0.0])) + } + (Some((Direction::Decreasing, x, _)), Bound::Right) => { + Some(Quadratic::new([x + tolerance, 0.0, 0.0])) + } + _ => None, + } + } + + fn final_bound(&self, which: Bound, tolerance: f64) -> Option { + match (self.almost_horiz, which) { + (Some((Direction::Increasing, _, x)), Bound::Right) => { + Some(Quadratic::new([x + tolerance, 0.0, 0.0])) + } + (Some((Direction::Decreasing, _, x)), Bound::Left) => { + Some(Quadratic::new([x - tolerance, 0.0, 0.0])) + } + _ => None, + } + } + + fn middle_bound(&self, which: Bound, tolerance: f64, outer_bound: bool) -> Quadratic { + let shift = match (self.almost_horiz, which) { + (Some((Direction::Increasing, ..)), Bound::Right) + | (Some((Direction::Decreasing, ..)), Bound::Left) => -tolerance, + (Some((Direction::Increasing, ..)), Bound::Left) + | (Some((Direction::Decreasing, ..)), Bound::Right) => tolerance, + _ => 0.0, + }; + let factor = if outer_bound { 1.0 } else { -1.0 }; + let raise = match which { + Bound::Left => factor * self.dmin - tolerance, + Bound::Right => factor * self.dmax + tolerance, + }; + shift_quadratic(raise_quadratic(self.q, raise), shift) + } + + #[allow(dead_code)] + fn compare( + &self, + other: &QuadraticApprox, + sure_tolerance: f64, + ish_tolerance: f64, + y0: f64, + y1: f64, + ) -> QuadApproxComparison { + if self.almost_horiz.is_none() && other.almost_horiz.is_none() { + let diff = *other - *self; + let mut out = QuadApproxComparison::default(); + push_two_level_quadratic_signs( + diff.q, + -diff.dmax - sure_tolerance, + -diff.dmin + sure_tolerance, + y0, + y1, + &mut out, + ); + return out; + } + + use MaybeOrder::*; + let our_right_their_left = self.compare_one_side(other, sure_tolerance, y0, y1, true); + let their_right_our_left = other.compare_one_side(self, sure_tolerance, y0, y1, true); + let sure_order = QuadApproxComparison::merge_from( + &our_right_their_left, + &their_right_our_left, + |ortl, trol| match (ortl, trol) { + (Left, _) => { + debug_assert_eq!(trol, Ish); + Left + } + (_, Left) => { + debug_assert_eq!(ortl, Ish); + Right + } + _ => Ish, + }, + ); + + let our_right_their_left = self.compare_one_side(other, ish_tolerance, y0, y1, false); + let their_right_our_left = other.compare_one_side(self, ish_tolerance, y0, y1, false); + let ish_order = QuadApproxComparison::merge_from( + &our_right_their_left, + &their_right_our_left, + |ortl, trol| match (ortl, trol) { + (Left, _) => Left, + (_, Left) => Right, + _ => Ish, + }, + ); + + QuadApproxComparison::merge_from(&sure_order, &ish_order, |sure, ish| match (sure, ish) { + (MaybeOrder::Left | MaybeOrder::Right, _) => sure, + (_, MaybeOrder::Ish) => MaybeOrder::Ish, + _ => MaybeOrder::Unknown, + }) + } + + fn compare_one_side( + &self, + other: &QuadraticApprox, + tolerance: f64, + mut y0: f64, + y1: f64, + outer_bound: bool, + ) -> QuadApproxComparison { + let mut buf = ApproxComparison::default(); + let r = self.initial_bound(Bound::Right, tolerance); + let l = other.initial_bound(Bound::Left, tolerance); + if r.is_some() || l.is_some() { + let r = r.unwrap_or_else(|| self.middle_bound(Bound::Right, tolerance, outer_bound)); + let l = l.unwrap_or_else(|| other.middle_bound(Bound::Left, tolerance, outer_bound)); + let end = (y0 + tolerance).min(y1); + push_quadratic_sign(r - l, Order::Left, Order::Ish, y0, end, &mut buf.entries); + y0 = end; + } + + let r = self.final_bound(Bound::Right, tolerance); + let l = other.final_bound(Bound::Left, tolerance); + let end = if r.is_some() || l.is_some() { + (y1 - tolerance).max(y0) + } else { + y1 + }; + + if end > y0 { + let r = self.middle_bound(Bound::Right, tolerance, outer_bound); + let l = other.middle_bound(Bound::Left, tolerance, outer_bound); + push_quadratic_sign(r - l, Order::Left, Order::Ish, y0, end, &mut buf.entries); + y0 = end; + } + + if y0 < y1 { + let r = r.unwrap_or_else(|| self.middle_bound(Bound::Right, tolerance, outer_bound)); + let l = l.unwrap_or_else(|| other.middle_bound(Bound::Left, tolerance, outer_bound)); + push_quadratic_sign(r - l, Order::Left, Order::Ish, y0, y1, &mut buf.entries); + } + + buf + } + + #[cfg(test)] + fn eval(&self, y: f64) -> f64 { + self.q.eval(y) + } +} + +impl std::ops::Sub for QuadraticApprox { + type Output = Self; + + fn sub(self, other: Self) -> Self { + // Subtracting quadratic approximations is only a valid way of comparing + // them if the extra almost-horizontal robustness stuff is inactive. + debug_assert!(self.almost_horiz.is_none() && other.almost_horiz.is_none()); + + QuadraticApprox { + q: self.q - other.q, + dmin: self.dmin - other.dmax, + dmax: self.dmax - other.dmin, + almost_horiz: None, + } + } +} + +// Find a quadratic passing through the given points. The x's are assuming to be +// in strictly increasing order (and if they are close to one another, this will +// be numerically unstable). +fn interpolate_quadratic(x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> Quadratic { + debug_assert!(x0 < x1 && x1 < x2); + + // If we took reciprocals here, we could eliminate some divisions (at the cost of some accuracy). + let dx10 = x1 - x0; + let dx21 = x2 - x1; + let dx20 = x2 - x0; + + // We use the Lagrange basis functions. These are their coefficients, constant terms first. + // We pre-scale by their interpolation coefficients, just to group multiplications more efficiently. + // As a result, ell0 is the polynomial with ell0(x0) = y0, ell0(x1) = 0, ell0(x2) = 0. + // ell0 = y0 (x - x1) (x - x2) / ((x0 - x1) (x0 - x2)) + let denom0 = dx10 * dx20; + let factor0 = y0 / denom0; + let ell0 = [x1 * x2 * factor0, -(x1 + x2) * factor0, factor0]; + + // ell1 = y1 (x - x0) (x - x2) / ((x1 - x0) (x1 - x2)) + let denom1 = -dx10 * dx21; + let factor1 = y1 / denom1; + let ell1 = [x0 * x2 * factor1, -(x0 + x2) * factor1, factor1]; + + // ell2 = y2 (x - x0) (x - x1) / ((x2 - x0) (x2 - x1)) + let denom2 = dx21 * dx20; + let factor2 = y2 / denom2; + let ell2 = [x0 * x1 * factor2, -(x0 + x1) * factor2, factor2]; + + Quadratic::new([ + ell0[0] + ell1[0] + ell2[0], + ell0[1] + ell1[1] + ell2[1], + ell0[2] + ell1[2] + ell2[2], + ]) +} + +fn fit_quadratic(c: CubicBez) -> Quadratic { + let t0 = 0.0; + let t1 = 0.3; + let t2 = 0.7; + let t3 = 1.0; + + let p0 = c.eval(t0); + let p1 = c.eval(t1); + let p2 = c.eval(t2); + let p3 = c.eval(t3); + + // For very short (or almost horizontal) segments the interpolation is too + // unstable. In that case, just take a chord. + if p1.y - p0.y < 1e-12 || p2.y - p1.y < 1e-12 { + // p0.x * (y - p3.y) / (p0.y - p3.y) + // + p3.x * (y - p0.y) / (p3.y - p0.y) + return Quadratic::new([ + (p0.x * p3.y - p3.x * p0.y) / (p3.y - p0.y), + (p3.x - p0.x) / (p3.y - p0.y), + 0.0, + ]); + } + + let p = interpolate_quadratic(p0.y, p0.x, p1.y, p1.x, p2.y, p2.x); + let q = interpolate_quadratic(p0.y, 1.0, p1.y, -1.0, p2.y, 1.0); + + // Note that the denominator won't be small (assuming that p0.y, p1.y, p2.y, + // p3.y are ordered), because q(p2.y) is 1, and q has already had two sign + // changes before p2.y, so it can't change sign again. + let e = (p.eval(p3.y) - p3.x) / (q.eval(p3.y) + 1.0); + + let p = p.coeffs(); + let q = q.coeffs(); + Quadratic::new([p[0] - q[0] * e, p[1] - q[1] * e, p[2] - q[2] * e]) +} + +#[allow(dead_code)] +fn fit_quadratic_with_endpoints_and_area(c: CubicBez) -> Quadratic { + let seg = PathSeg::Cubic(c); + let close_seg = PathSeg::Line(Line::new(c.p3, c.p0)); + let area = seg.area() + close_seg.area(); + let dy = c.p3.y - c.p0.y; + // Note: this solution gives 0 error at endpoints. Arguably + // a better solution would be based on mean / moments. + let c2 = -6. * area / dy.powi(3); + let c1 = (c.p3.x - c.p0.x - (c.p3.y.powi(2) - c.p0.y.powi(2)) * c2) / dy; + let c0 = c.p0.x - c1 * c.p0.y - c2 * c.p0.y.powi(2); + Quadratic::new([c0, c1, c2]) +} + +/// Get the parameters such that the curve can be represented by the following formula: +/// B(t) = c0 + c1 * t + c2 * t^2 +pub fn quad_parameters(q: QuadBez) -> (Vec2, Vec2, Vec2) { + let c0 = q.p0.to_vec2(); + let c1 = (q.p1 - q.p0) * 2.0; + let c2 = c0 - q.p1.to_vec2() * 2.0 + q.p2.to_vec2(); + (c0, c1, c2) +} + +fn l_infty_distance(p0: Point, p1: Point) -> f64 { + (p0.x - p1.x).abs().max((p0.y - p1.y).abs()) +} + +fn max_distance(c0: CubicBez, c1: CubicBez) -> f64 { + l_infty_distance(c0.p0, c1.p0) + .max(l_infty_distance(c0.p1, c1.p1)) + .max(l_infty_distance(c0.p2, c1.p2)) + .max(l_infty_distance(c0.p3, c1.p3)) +} + +#[derive(Clone, Debug)] +struct CubicParams { + c: CubicBez, + dir: Option, + approx: QuadraticApprox, +} + +#[allow(clippy::too_many_arguments)] +fn intersect_cubics_rec( + cubic0: &CubicParams, + cubic1: &CubicParams, + use_precomputed_approx: bool, + y0: f64, + y1: f64, + sure_tolerance: f64, + ish_tolerance: f64, + out: &mut CurveOrder, + status: MetricsStatus, +) { + // eprintln!("recursing to {y0}..{y1}"); + let mut c0 = y_subsegment(cubic0.c, y0, y1); + let mut c1 = y_subsegment(cubic1.c, y0, y1); + // dbg!(c0, orig_c0); + // dbg!(c1, orig_c1); + + if max_distance(c0, c1) <= ish_tolerance { + out.push(y1, Order::Ish, status); + return; + } + if y1 - y0 < ish_tolerance { + // For very short intervals there's some numerical instability in constructing the + // approximating quadratics, so we just do a coarser comparison based on bounding + // boxes. + let b0 = Shape::bounding_box(&c0); + let b1 = Shape::bounding_box(&c1); + let order = if b1.min_x() >= b0.max_x() + ish_tolerance { + Order::Left + } else if b0.min_x() >= b1.max_x() + ish_tolerance { + Order::Right + } else { + Order::Ish + }; + out.push(y1, order, status); + return; + } + + // If the y coordinates are very off-center, the quadratic coefficients become + // large and cause instability. So we re-center and then compensate afterwards. + let y_mid = (y0 + y1) / 2.0; + c0.p0.y -= y_mid; + c0.p1.y -= y_mid; + c0.p2.y -= y_mid; + c0.p3.y -= y_mid; + c1.p0.y -= y_mid; + c1.p1.y -= y_mid; + c1.p2.y -= y_mid; + c1.p3.y -= y_mid; + + // TODO: document the precomputed approximation + let (ep0, ep1) = if use_precomputed_approx { + let orig_y_mid0 = (cubic0.c.p0.y + cubic0.c.p3.y) / 2.0; + let orig_y_mid1 = (cubic1.c.p0.y + cubic1.c.p3.y) / 2.0; + ( + cubic0.approx.shift_y(orig_y_mid0 - y_mid), + cubic1.approx.shift_y(orig_y_mid1 - y_mid), + ) + } else { + ( + QuadraticApprox::from_cubic(c0, cubic0.dir), + QuadraticApprox::from_cubic(c1, cubic1.dir), + ) + }; + + //let mut scratch = CurveOrder::new((y0 - y_mid) * y_scale); + let mut signs = ep0.compare(&ep1, sure_tolerance, ish_tolerance, y0 - y_mid, y1 - y_mid); + + // dbg!(orig_c0, orig_c1, ep0, ep1, y0, y1, &signs); + // for (y0, y1, order) in signs.iter((y0 - y_mid) * y_scale) { + // dbg!(y0, y1, order); + // match order { + // MaybeOrder::Right => { + // debug_assert!(solve_x_for_y(c0, y0) > solve_x_for_y(c1, y0)); + // debug_assert!(dbg!(solve_x_for_y(c0, y1)) > dbg!(solve_x_for_y(c1, y1))); + // } + // MaybeOrder::Left => { + // debug_assert!(solve_x_for_y(c0, y0) < solve_x_for_y(c1, y0)); + // debug_assert!(solve_x_for_y(c0, y1) < solve_x_for_y(c1, y1)); + // } + // _ => {} + // } + // } + + // Re-center the roots. It's important that the starting and ending positions + // have no rounding error, so deal with them separately. + for entry in &mut signs.entries { + entry.end = (entry.end + y_mid).clamp(y0, y1); + } + signs.entries.last_mut().unwrap().end = y1; + + for (y0, y1, order) in signs.iter(y0) { + match order { + MaybeOrder::Right => { + debug_assert!(solve_x_for_y(cubic0.c, y0) > solve_x_for_y(cubic1.c, y0)); + debug_assert!(solve_x_for_y(cubic0.c, y1) > solve_x_for_y(cubic1.c, y1)); + } + MaybeOrder::Left => { + debug_assert!(solve_x_for_y(cubic0.c, y0) < solve_x_for_y(cubic1.c, y0)); + debug_assert!(solve_x_for_y(cubic0.c, y1) < solve_x_for_y(cubic1.c, y1)); + } + _ => {} + } + } + + for (new_y0, new_y1, order) in signs.iter(y0) { + // It's possible to end up with empty intervals: even though they're non-empty + // when we construct them, scaling and rounding can make them empty. This case + // could do with more thought, but for now we just ignore empty ones. The main + // effect here is probably that we delete a tiny "ish" interval around a crossing. + if new_y0 == new_y1 { + continue; + } + // dbg!(new_y0, new_y1, order); + // dbg!( + // solve_x_for_y(orig_c0, new_y0), + // solve_x_for_y(orig_c1, new_y0) + // ); + // dbg!( + // solve_x_for_y(orig_c0, new_y1), + // solve_x_for_y(orig_c1, new_y1) + // ); + if let Ok(order) = order.try_into() { + out.push(new_y1, order, status); + } else { + let mid = 0.5 * (new_y0 + new_y1); + + // We test the difference between y1 and y0, not new_y1 and new_y0. + // This help reduce false positives where the error in dep is substantial + // but it just barely had a root and so we picked up a very small + // "ish" interval that's just an artifact. In this case, new_y0 and new_y1 + // will be very close, but we'll recurse one more time to get a better + // quadratic approximation. + if y1 - y0 <= ish_tolerance + || (ep0.dmax + ep1.dmax <= 2.0 * ish_tolerance + && ep0.dmin + ep1.dmin >= -2.0 * ish_tolerance) + { + out.push(new_y1, Order::Ish, status); + } else if new_y1 - new_y0 < 0.5 * (y1 - y0) { + intersect_cubics_rec( + cubic0, + cubic1, + false, + new_y0, + new_y1, + sure_tolerance, + ish_tolerance, + out, + status.bump(), + ); + } else { + // eprintln!( + // "recursing because interval didn't shrink: {y0} - {new_y0} - {new_y1} - {y1}, error = {}, scratch {:?}, x slope {}", + // dep.dmax - dep.dmin, scratch, + // (c0.p3.x - c0.p0.x) / (y1 - y0) + // ); + intersect_cubics_rec( + cubic0, + cubic1, + false, + new_y0, + mid, + sure_tolerance, + ish_tolerance, + out, + status.bump(), + ); + intersect_cubics_rec( + cubic0, + cubic1, + false, + mid, + new_y1, + sure_tolerance, + ish_tolerance, + out, + status.bump(), + ); + } + } + } +} + +/// A decomposition of a cubic Bezier into almost-horizontal and not-almost-horizontal pieces. +#[derive(Clone, Debug, Default)] +pub struct AlmostHorizontalDecomposition { + pieces: ArrayVec, +} + +impl AlmostHorizontalDecomposition { + /// Decompose a cubic into almost-horizontal and not-almost-horizontal pieces. + /// + /// The cubic `c` must be (approximately) y-monotone and must not be completely horizontal. + pub fn from_monotone_cubic(c: CubicBez) -> Self { + const THRESHOLD: f64 = 16.0; + let dx = cubic_from_bez_x(c).deriv(); + let dy = cubic_from_bez_y(c).deriv(); + + // We want to solve |dx/dy| > THRESHOLD. Since dy is non-negative, this is equivalent to + // dx - dy * THRESHOLD > 0 or + // -dx - dy * THRESHOLD > 0. + let increasing = dx - dy * THRESHOLD; + let decreasing = dy * (-THRESHOLD) - dx; + + let mut increasing_buf = ApproxComparison::<3>::default(); + let mut decreasing_buf = ApproxComparison::<3>::default(); + push_quadratic_sign( + increasing, + Order::Ish, + Order::Right, + 0.0, + 1.0, + &mut increasing_buf.entries, + ); + + push_quadratic_sign( + decreasing, + Order::Ish, + Order::Left, + 0.0, + 1.0, + &mut decreasing_buf.entries, + ); + + use MaybeOrder::*; + let subdivision = + ApproxComparison::<6>::merge_from(&increasing_buf, &decreasing_buf, |inc, dec| match ( + inc, dec, + ) { + (Right, _) => Right, + (_, Left) => Left, + _ => Ish, + }); + + // The subdivision can produce degenerate things (like, horizontal + // segments or zero-length segments). We filter them out, because the + // curve comparison expects all y-intervals to be non-empty. Our filtering + // mechanism works in the t-parameter space: if a subinterval would give + // a horizontal segment, just drop it and tack that t-interval onto the + // next one. + let mut last_y = c.p0.y; + let mut last_end = 0.0; + let orig_c = c; + let mut ret = ArrayVec::default(); + for (_, end, order) in subdivision.iter(0.0) { + let start = last_end; + // If there's no actual subdivision, skip the subsegment operation. + // Although `subsegment` perserves p0 and p3 exactly, it can perturb p1 + // and p2. + let mut c = if start == 0.0 && end == 1.0 { + orig_c + } else { + orig_c.subsegment(start..end) + }; + + // Enforce y-monotonicity, from the previous curve to this one, and + // also for the initial and final tangents of this subsegment. + c.p0.y = c.p0.y.clamp(last_y, orig_c.p3.y); + c.p1.y = c.p1.y.clamp(c.p0.y, orig_c.p3.y); + c.p3.y = c.p3.y.clamp(last_y, orig_c.p3.y); + c.p2.y = c.p2.y.clamp(last_y, c.p3.y); + last_y = c.p3.y; + let approx_horiz = match order { + Right => { + // This is supposed to be increasing in x, so fix up the initial final tangents just in case. + // They're very likely to already be in the right direction, but if the cubic is very wild + // then a small error in y_subsegment could mess things up. + c.p1.x = c.p1.x.max(c.p0.x); + c.p2.x = c.p2.x.min(c.p3.x); + Some(Direction::Increasing) + } + Ish | Unknown => None, + Left => { + c.p1.x = c.p1.x.min(c.p0.x); + c.p2.x = c.p2.x.max(c.p3.x); + Some(Direction::Decreasing) + } + }; + if c.p0.y != c.p3.y { + let mut recentered_c = c; + let y_mid = (c.p0.y + c.p3.y) / 2.0; + recentered_c.p0.y -= y_mid; + recentered_c.p1.y -= y_mid; + recentered_c.p2.y -= y_mid; + recentered_c.p3.y -= y_mid; + let p = CubicParams { + c, + dir: approx_horiz, + approx: QuadraticApprox::from_cubic(recentered_c, approx_horiz), + }; + ret.push(p); + last_end = end; + } + } + // In case the last interval got filtered out, make sure we end at the right place. + ret.last_mut().unwrap().c.p3 = orig_c.p3; + + Self { pieces: ret } + } +} + +/// Compute the horizontal order between two cubics, for the vertical range that they have in common. +/// +/// The returned order breaks the vertical range into regions, and in each region either specifies +/// a definite order or says that the curves are close. For example, for the following two curves +/// we might identify five vertical regions. +/// +/// ```text +/// c0 c1 +/// \ / +/// \ / left +/// \ / ______ +/// \/ close +/// /\ ______ +/// / \ +/// ( \ right +/// \ ) +/// \ / ______ +/// \/ close +/// /\ ______ +/// / \ left +/// ``` +/// +/// There are two "closeness" parameters: `sure_tolerance` and `ish_tolerance`, +/// of which `ish_tolerance` should be larger. The `sure_tolerance` parameter +/// tells us when we are allowed to say that there's a definite order: if `c0` +/// is more than `sure_tolerance` to the left of `c1`, we can say that it's to +/// the left. The purpose of this parameter is to protect against numerical +/// errors. In the future, maybe we can determine it automatically based on the +/// magnitude of the inputs. +/// +/// The `ish_tolerance` parameter tells us when we're allowed to say that +/// the two curves are close: if they're definitely within `ish_tolerance` +/// of one another, we can say that they're close. +pub fn intersect_cubics( + c0: CubicBez, + c1: CubicBez, + sure_tolerance: f64, + ish_tolerance: f64, +) -> CurveOrder { + intersect_cubics_with_precomputed_decomp( + c0, + &AlmostHorizontalDecomposition::from_monotone_cubic(c0), + c1, + &AlmostHorizontalDecomposition::from_monotone_cubic(c1), + sure_tolerance, + ish_tolerance, + ) +} + +/// A version of [intersect_cubics] that uses a precomputed almost-horizontal +/// decomposition. +/// +/// Pre-computing the almost-horizontal decomposition can be much faster, because +/// then you can do it per-curve instead of per-comparison. +pub fn intersect_cubics_with_precomputed_decomp( + c0: CubicBez, + c0_div: &AlmostHorizontalDecomposition, + c1: CubicBez, + c1_div: &AlmostHorizontalDecomposition, + sure_tolerance: f64, + ish_tolerance: f64, +) -> CurveOrder { + let y0 = c0.p0.y.max(c1.p0.y); + let y1 = c0.p3.y.min(c1.p3.y); + + let mut ret = CurveOrder::new(y0); + if y0 < y1 { + let c0_div = c0_div + .pieces + .iter() + .filter(|cp| cp.c.p3.y > y0) + .map(|cp| (cp.c.p3.y, cp)); + let c1_div = c1_div + .pieces + .iter() + .filter(|cp| cp.c.p3.y > y0) + .map(|cp| (cp.c.p3.y, cp)); + for (_, c0, c1) in MergedRangeIter::new(c0_div, c1_div) { + let y0 = c0.c.p0.y.max(c1.c.p0.y); + let y1 = c0.c.p3.y.min(c1.c.p3.y); + if y0 < y1 { + intersect_cubics_rec( + c0, + c1, + true, + y0, + y1, + sure_tolerance, + ish_tolerance, + &mut ret, + MetricsStatus::default(), + ); + } + } + } else if y0 == y1 { + // Neither of the curves should be purely horizontal, so it must be + // that they just overlap at a single point. + debug_assert!(c0.p0.y == c1.p3.y || c0.p3.y == c1.p0.y); + + let (x0, x1) = if c0.p0.y == c1.p3.y { + (c0.p0.x, c1.p3.x) + } else { + (c0.p3.x, c1.p0.x) + }; + let order = if x0 < x1 - ish_tolerance { + Order::Left + } else if x0 > x1 + ish_tolerance { + Order::Right + } else { + Order::Ish + }; + + ret.push(y1, order, MetricsStatus::default()); + } + //fix_up_endpoints(&mut ret, c0, c1, eps); + debug_assert_eq!(ret.cmps.last().unwrap().end, y1); + ret +} + +fn cubic_from_bez(a0: f64, a1: f64, a2: f64, a3: f64) -> Cubic { + let c3 = a3 - 3.0 * a2 + 3.0 * a1 - a0; + let c2 = 3.0 * (a2 - 2.0 * a1 + a0); + let c1 = 3.0 * (a1 - a0); + let c0 = a0; + Cubic::new([c0, c1, c2, c3]) +} + +fn cubic_from_bez_x(c: CubicBez) -> Cubic { + cubic_from_bez(c.p0.x, c.p1.x, c.p2.x, c.p3.x) +} + +fn cubic_from_bez_y(c: CubicBez) -> Cubic { + cubic_from_bez(c.p0.y, c.p1.y, c.p2.y, c.p3.y) +} + +#[cfg(any(test, feature = "arbitrary"))] +#[doc(hidden)] +pub mod arbtests { + use super::{cubic_from_bez_x, cubic_from_bez_y}; + use arbitrary::Unstructured; + use kurbo::ParamCurve as _; + + use super::solve_t_for_y; + + pub fn solve_for_t(u: &mut Unstructured) -> Result<(), arbitrary::Error> { + let c = crate::arbitrary::monotonic_bezier(u)?; + if c.p0.y == c.p3.y { + return Ok(()); + } + + // How much relative accuracy do we expect? + // This was determined empirically: 1e-10 fails fuzz tests. + let accuracy = 1e-9; + let max_coeff = cubic_from_bez_x(c) + .max_abs_coefficient() + .max(cubic_from_bez_y(c).max_abs_coefficient()); + let threshold = accuracy * max_coeff.max(1.0); + + let y = crate::arbitrary::float_in_range(c.p0.y, c.p3.y, u)?; + let t = solve_t_for_y(c, y); + assert!((c.eval(t).y - y).abs() <= threshold); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn solve_for_t_accuracy() { + let c = CubicBez { + p0: (0.5, 0.0).into(), + p1: (0.19531249655301508, 0.6666666864572713).into(), + p2: (0.00781250000181899, 1.0).into(), + p3: (0.0, 1.0).into(), + }; + let y0 = 0.0; + let y1 = 0.9999999406281859; + let c0 = c.subsegment(solve_t_for_y(c, y0)..solve_t_for_y(c, y1)); + assert!((dbg!(c0.p3.y) - dbg!(y1)).abs() < 1e-8); + let y1 = 0.9999999; + let c0 = c.subsegment(solve_t_for_y(c, y0)..solve_t_for_y(c, y1)); + assert!((dbg!(c0.p3.y) - dbg!(y1)).abs() < 1e-8); + } + + #[test] + fn solve_for_t_accuracy2() { + let c = CubicBez { + p0: (0.9999999406281859, 0.0).into(), + p1: (0.011764705882352941, 0.011764705882352941).into(), + p2: (0.011764705882352941, 0.023391003460207612).into(), + p3: (0.011764705882352941, 0.03488052106655811).into(), + }; + let y = 0.012468608207852864; + let t = solve_t_for_y(c, y); + assert!(dbg!(c.eval(t).y - y).abs() < 1e-8); + } + + #[test] + fn solve_for_t_accuracy3() { + let c = CubicBez { + p0: (0.1443023801753, -0.00034678801186988073).into(), + p1: (0.3760345290602, -0.00011491438194505266).into(), + p2: (0.6070068772783, 0.00011680717653411721).into(), + p3: (0.8372203215247, 0.0003483767633017387).into(), + }; + + let y = 0.00016929232880729566; + let t = solve_t_for_y(c, y); + assert!(dbg!(c.eval(t).y - y).abs() < 1e-8); + } + + #[test] + fn solve_for_t_arbtest() { + arbtest::arbtest(arbtests::solve_for_t); + } + + #[test] + fn non_touching() { + let sure_tol = 0.01; + let ish_tol = 0.03; + let a = Line::new((-1e6, 0.0), (0.0, 1.0)); + let b = Line::new((ish_tol * 1.5, 0.0), (ish_tol * 1.5, 1.0)); + let a = PathSeg::Line(a).to_cubic(); + let b = PathSeg::Line(b).to_cubic(); + + let cmp = intersect_cubics(a, b, sure_tol, ish_tol); + assert_eq!(cmp.order_at(1.0), Order::Left); + } + + #[test] + fn almost_touching() { + let sure_tol = 0.25; + let ish_tol = 0.3; + let a = CubicBez::new((-1.0, 0.0), (0.5, 0.6), (0.5, 0.4), (-1.0, 1.0)); + let b = CubicBez::new((0.3, 0.0), (0.3, 1.0 / 3.0), (0.3, 2.0 / 3.0), (0.3, 1.0)); + let cmp = intersect_cubics(a, b, sure_tol, ish_tol); + assert_eq!(cmp.iter().count(), 3); + } + + #[test] + fn quad_approximation() { + let c = CubicBez { + p0: (0.1443023801753, -0.00034678801186988073).into(), + p1: (0.3760345290602, -0.00011491438194505266).into(), + p2: (0.6070068772783, 0.00011680717653411721).into(), + p3: (0.8372203215247, 0.0003483767633017387).into(), + }; + + let y = 0.00016929232880729566; + let t = solve_t_for_y(c, y); + dbg!(t, c.eval(t)); + + let ep = QuadraticApprox::from_cubic(c, None); + dbg!(y); + dbg!(ep); + dbg!(ep.eval(y)); + dbg!(solve_x_for_y(c, y)); + + let diff = solve_x_for_y(c, y) - ep.eval(y); + assert!(diff <= ep.dmax); + assert!(diff >= ep.dmin); + } + + // At some point, this test case looped infinitely. + #[test] + fn test_loop() { + let c0 = CubicBez { + p0: (0.5, 0.0).into(), + p1: (0.0014551791831513819, 0.9999847770668531).into(), + p2: (0.9999999850988388, 1.0).into(), + p3: (0.0, 1.0).into(), + }; + let c1 = CubicBez { + p0: (0.5, 0.0).into(), + p1: (0.0014551791831513819, 0.9999847770668531).into(), + p2: (1.0, 0.9999999999999717).into(), + p3: (0.0, 0.9999999999999717).into(), + }; + + dbg!(intersect_cubics(c0, c1, 0.5e-6, 1.5e-6)); + } + + #[test] + fn y_slop_single_seg() { + let order = CurveOrder { + start: 0.0, + cmps: [CurveOrderEntry { + end: 1.0, + order: Order::Right, + }] + .into_iter() + .collect(), + metrics: Default::default(), + }; + + assert_eq!(order.clone().with_y_slop(0.5).cmps, order.cmps); + } + + #[test] + fn y_slop_single_short_seg() { + let order = CurveOrder { + start: 0.0, + cmps: [CurveOrderEntry { + end: 0.2, + order: Order::Right, + }] + .into_iter() + .collect(), + metrics: Default::default(), + }; + + assert_eq!(order.clone().with_y_slop(0.5).cmps, order.cmps); + } + + #[test] + fn y_slop_short_start_order() { + let order = CurveOrder { + start: 0.0, + cmps: [ + CurveOrderEntry { + end: 0.1, + order: Order::Right, + }, + CurveOrderEntry { + end: 1.0, + order: Order::Ish, + }, + ] + .into_iter() + .collect(), + metrics: Default::default(), + }; + + assert_eq!( + order.clone().with_y_slop(0.5).cmps.as_slice(), + &[CurveOrderEntry { + end: 1.0, + order: Order::Ish + }] + ); + } + + #[test] + fn y_slop_short_end_order() { + let order = CurveOrder { + start: 0.0, + cmps: [ + CurveOrderEntry { + end: 0.9, + order: Order::Ish, + }, + CurveOrderEntry { + end: 1.0, + order: Order::Left, + }, + ] + .into_iter() + .collect(), + metrics: Default::default(), + }; + + assert_eq!( + order.clone().with_y_slop(0.5).cmps.as_slice(), + &[CurveOrderEntry { + end: 1.0, + order: Order::Ish + }] + ); + } + + #[test] + fn y_slop_no_height() { + let order = CurveOrder { + start: 1.0, + cmps: [CurveOrderEntry { + end: 1.0, + order: Order::Right, + }] + .into_iter() + .collect(), + metrics: Default::default(), + }; + + assert_eq!(order.clone().with_y_slop(0.5).cmps, order.cmps); + } + + // Cases where two curves share just a single y coordinate. + #[test] + fn curve_order_no_overlap() { + let c0 = CubicBez { + p0: (0.0, 0.0).into(), + p1: (0.0, 0.0).into(), + p2: (0.0, 1.0).into(), + p3: (0.0, 1.0).into(), + }; + let c1 = CubicBez { + p0: (1.0, 1.0).into(), + p1: (1.0, 1.0).into(), + p2: (1.0, 2.0).into(), + p3: (1.0, 2.0).into(), + }; + let order = intersect_cubics(c0, c1, 0.125, 0.25); + assert_eq!(order.start, 1.0); + assert_eq!( + &order.cmps[..], + &[CurveOrderEntry { + end: 1.0, + order: Order::Left + }] + ); + + let c1 = CubicBez { + p0: (1.0, 1.0).into(), + p1: (1.0, 1.0).into(), + p2: (0.0, 2.0).into(), + p3: (0.0, 2.0).into(), + }; + let order = intersect_cubics(c0, c1, 0.125, 0.25); + assert_eq!(order.start, 1.0); + assert_eq!( + &order.cmps[..], + &[CurveOrderEntry { + end: 1.0, + order: Order::Left + }] + ); + + let c1 = CubicBez { + p0: (0.1, 1.0).into(), + p1: (0.1, 1.0).into(), + p2: (0.0, 2.0).into(), + p3: (0.0, 2.0).into(), + }; + let order = intersect_cubics(c0, c1, 0.125, 0.25); + assert_eq!(order.start, 1.0); + assert_eq!( + &order.cmps[..], + &[CurveOrderEntry { + end: 1.0, + order: Order::Ish + }] + ); + } + + // This test recurses a bit too deeply. The two curves are quite close, + // and near the end they're just a bit more than eps apart. + #[test] + fn painted_dreams_deep_recursion() { + let sure_tol = 0.5e-7; + let ish_tol = 1.5e-7; + let c0 = CubicBez::new( + (268.2700181987094, 465.9600610772311), + (261.5036543284458, 474.04917281766745), + (255.01588390924064, 481.16474559246694), + (248.76144328725377, 487.4191862144538), + ); + let c1 = CubicBez::new( + (268.2700181987094, 465.9600610772311), + (261.508835610275, 474.04297901254995), + (255.02582000674386, 481.1538483487484), + (248.77581162941254, 487.404817872295), + ); + + dbg!(intersect_cubics(c0, c1, sure_tol, ish_tol)); + } + + #[test] + fn quadratic_comparison() { + let c0 = CubicBez::new((0.0, 0.0), (10.0, 10.0), (20.0, 20.0), (30.0, 30.0)); + let c1 = CubicBez::new((30.0, 0.0), (20.0, 10.0), (10.0, 20.0), (0.0, 30.0)); + let sure_tolerance = 1.0; + let ish_tolerance = 2.0; + + let qa0 = QuadraticApprox::from_cubic(c0, None); + let qa1 = QuadraticApprox::from_cubic(c1, None); + assert_eq!( + 3, + qa0.compare(&qa1, sure_tolerance, ish_tolerance, 0.0, 30.0) + .entries + .len() + ); + + let c0 = CubicBez::new((0.0, 0.0), (100.0, 10.0), (200.0, 20.0), (300.0, 30.0)); + let c1 = CubicBez::new((3.0, 0.0), (103.0, 10.0), (203.0, 20.0), (303.0, 30.0)); + + let qa0 = QuadraticApprox::from_cubic(c0, Some(Direction::Increasing)); + let qa1 = QuadraticApprox::from_cubic(c1, Some(Direction::Increasing)); + assert_eq!( + 1, + qa0.compare(&qa1, sure_tolerance, ish_tolerance, 0.0, 30.0) + .entries + .len() + ); + + let c1 = CubicBez::new((150.0, 0.0), (150.0, 10.0), (150.0, 20.0), (150.0, 30.0)); + + let qa0 = QuadraticApprox::from_cubic(c0, Some(Direction::Increasing)); + let qa1 = QuadraticApprox::from_cubic(c1, Some(Direction::Increasing)); + assert_eq!( + 3, + qa0.compare(&qa1, sure_tolerance, ish_tolerance, 0.0, 30.0) + .entries + .len() + ); + } + + // TODO: improve this test + #[test] + fn break_horizontal() { + let c0 = CubicBez::new((0.0, 0.0), (10.0, 10.0), (20.0, 20.0), (30.0, 30.0)); + dbg!(AlmostHorizontalDecomposition::from_monotone_cubic(c0)); + + let c0 = CubicBez::new((0.0, 0.0), (10.0, 10.0), (20.0, 20.0), (500.0, 30.0)); + dbg!(AlmostHorizontalDecomposition::from_monotone_cubic(c0)); + + let c0 = CubicBez::new((0.0, 0.0), (2000.0, 10.0), (-1000.0, 20.0), (1000.0, 30.0)); + dbg!(AlmostHorizontalDecomposition::from_monotone_cubic(c0)); + + let c0 = CubicBez::new((0.0, 0.0), (2000.0, 0.0), (-1000.0, 30.0), (1000.0, 30.0)); + dbg!(AlmostHorizontalDecomposition::from_monotone_cubic(c0)); + } +} diff --git a/vendor/linesweeper/src/curve/split_quad.rs b/vendor/linesweeper/src/curve/split_quad.rs new file mode 100644 index 00000000..520d9436 --- /dev/null +++ b/vendor/linesweeper/src/curve/split_quad.rs @@ -0,0 +1,57 @@ +#![allow(missing_docs)] +#![allow(dead_code)] + +use arrayvec::ArrayVec; + +use super::Quadratic; + +pub struct SplitQuadPart { + quad: Quadratic, + until: f64, +} + +pub struct SplitQuad { + quads: ArrayVec, +} + +impl SplitQuad { + pub fn single(q: Quadratic) -> Self { + Self { + quads: [SplitQuadPart { + quad: q, + until: f64::INFINITY, + }] + .into_iter() + .collect(), + } + } +} + +pub struct QuadBound { + upper: SplitQuad, + lower: SplitQuad, +} + +impl QuadBound { + pub fn new(mut q: Quadratic, upper: f64, lower: f64, shift: f64) -> Self { + debug_assert!(lower <= 0.0 && 0.0 <= upper); + debug_assert!(shift >= 0.0); + let center = -q.c1 / (2.0 * q.c0); + let extremum = q.c0 + q.c1 * center; + if center.is_infinite() || extremum.is_infinite() { + // Our quad is close enough to linear. + q.c2 = 0.0; + let upper = q + upper; + let lower = q + lower; + + let signed_shift = if q.c1 > 0.0 { shift } else { -shift }; + + Self { + upper: SplitQuad::single(upper.shift(-signed_shift)), + lower: SplitQuad::single(lower.shift(signed_shift)), + } + } else { + todo!() + } + } +} diff --git a/vendor/linesweeper/src/curve/transverse.rs b/vendor/linesweeper/src/curve/transverse.rs new file mode 100644 index 00000000..be3a96ff --- /dev/null +++ b/vendor/linesweeper/src/curve/transverse.rs @@ -0,0 +1,189 @@ +//! Conservative checks for transversal intersections. +//! +//! We go to some effort to output curves with the correct horizontal +//! order. When curves are close to one another, our general solution +//! involves approximation with axis-aligned quadratics. This works, +//! but can lead to lots of subdivision. This module is about +//! improving a common case: when two curves intersect each other +//! transversally, they are close to one another near the intersection +//! point, but we can still tell that they are ordered. Hence, we +//! can avoid subdividing. + +use kurbo::{CubicBez, ParamCurve, ParamCurveDeriv, QuadBez}; + +use crate::curve::solve_t_for_y; + +// Finds the minimum of `a t^2 + 2 b t (1 - t) + c (1 - t)^2` in the given range of t. +fn quad_max_between(a: f64, b: f64, c: f64, range: std::ops::RangeInclusive) -> f64 { + // We don't check for inf or nan here, but our subsequent comparisons will filter + // those out. (The order of the comparisons is important, because nan always compares + // false.) + let critical_t = (c - b) / (a - b + (c - b)); + + let t0 = *range.start(); + let t1 = *range.end(); + + let eval = |t: f64| a * t * t + 2.0 * b * t * (1.0 - t) + c * (1.0 - t) * (1.0 - t); + let mut max = eval(t0).max(eval(t1)); + + if critical_t > t0 && critical_t < t1 { + max = max.max(eval(critical_t)); + } + max +} + +fn quad_min_between(a: f64, b: f64, c: f64, range: std::ops::RangeInclusive) -> f64 { + -quad_max_between(-a, -b, -c, range) +} + +pub fn transversal_after(left: CubicBez, right: CubicBez, y1: f64) -> bool { + debug_assert_eq!(left.start().y, right.start().y); + debug_assert!(left.start().x <= right.start().x); + debug_assert!(left.start().y < y1); + + // First, check if they're transversal at the initial point. We exclude + // curves whose tangents are close to degenerate here, just to make things + // nicer numerically. + let left_tangent = left.p1 - left.p0; + let right_tangent = right.p1 - right.p0; + if left_tangent.hypot2() < 1e-20 || right_tangent.hypot2() < 1e-20 { + return false; + } + let cross = left_tangent.cross(right_tangent); + if left_tangent.dot(right_tangent) > 0.0 + && cross * cross < 1e-2 * left_tangent.hypot2() * right_tangent.hypot2() + { + return false; + } + + let t1_left = solve_t_for_y(left, y1); + let QuadBez { + p0: l0, + p1: l1, + p2: l2, + } = left.deriv(); + let left_max_dx_dt = quad_max_between(l2.x, l1.x, l0.x, 0.0..=t1_left); + + let t1_right = solve_t_for_y(right, y1); + let QuadBez { + p0: r0, + p1: r1, + p2: r2, + } = right.deriv(); + let right_min_dx_dt = quad_min_between(r2.x, r1.x, r0.x, 0.0..=t1_right); + + if left_max_dx_dt < 0.0 && right_min_dx_dt > 0.0 { + return true; + } + + let left_dy_dt = if left_max_dx_dt < 0.0 { + quad_max_between(l2.y, l1.y, l0.y, 0.0..=t1_left) + } else { + quad_min_between(l2.y, l1.y, l0.y, 0.0..=t1_left) + }; + + let right_dy_dt = if right_min_dx_dt < 0.0 { + quad_min_between(r2.y, r1.y, r0.y, 0.0..=t1_right) + } else { + quad_max_between(r2.y, r1.y, r0.y, 0.0..=t1_right) + }; + + left_max_dx_dt * right_dy_dt < right_min_dx_dt * left_dy_dt +} + +pub fn transversal_before(left: CubicBez, right: CubicBez, y0: f64) -> bool { + // Reflect everything in y. + let p = |q: kurbo::Point| kurbo::Point { x: q.x, y: -q.y }; + let c = |d: CubicBez| CubicBez { + p0: p(d.p3), + p1: p(d.p2), + p2: p(d.p1), + p3: p(d.p0), + }; + transversal_after(c(left), c(right), -y0) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_transversal() { + let left = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 1.).into(), + p2: (-1., 1.).into(), + p3: (0., 2.).into(), + }; + let right = CubicBez { + p0: (0., 0.).into(), + p1: (1., 1.).into(), + p2: (1., 1.).into(), + p3: (0., 2.).into(), + }; + + assert!(transversal_after(left, right, 0.5)); + + // It isn't really necessary for the implementation to pass + // these two tests; they just validate that our particular + // implementation does what we think. Our implementation looks + // only at derivatives, and the derivatives change direction + // at y = 1.0. + assert!(transversal_after(left, right, 0.9)); + assert!(!transversal_after(left, right, 1.1)); + } + + #[test] + fn parallel() { + let left = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 1.).into(), + p2: (-1., 1.).into(), + p3: (0., 2.).into(), + }; + let right = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 1.).into(), + p2: (1., 1.).into(), + p3: (0., 2.).into(), + }; + + assert!(!transversal_after(left, right, 0.01)); + } + + #[test] + fn parallel_and_horizontal() { + let left = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 0.).into(), + p2: (-1., 1.).into(), + p3: (0., 2.).into(), + }; + let right = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 0.).into(), + p2: (1., 1.).into(), + p3: (0., 2.).into(), + }; + + assert!(!transversal_after(left, right, 0.01)); + } + + #[test] + fn antiparallel_and_horizontal() { + let left = CubicBez { + p0: (0., 0.).into(), + p1: (-1., 0.).into(), + p2: (-1., 1.).into(), + p3: (0., 2.).into(), + }; + let right = CubicBez { + p0: (0., 0.).into(), + p1: (1., 0.).into(), + p2: (1., 1.).into(), + p3: (0., 2.).into(), + }; + + assert!(transversal_after(left, right, 0.01)); + } +} diff --git a/vendor/linesweeper/src/generators.rs b/vendor/linesweeper/src/generators.rs new file mode 100644 index 00000000..1451369f --- /dev/null +++ b/vendor/linesweeper/src/generators.rs @@ -0,0 +1,147 @@ +//! Utilities for generating examples, benchmarks, and test cases. + +use std::f64::consts::PI; + +use kurbo::{Affine, BezPath}; + +use crate::Point; + +type Contours = Vec>; + +/// Generate a bunch of squares, arranged in a grid. +/// +/// The top-left of the first square is at (x0, y0). Each square has size `size +/// x size`, and the distance between squares (both horizontally and vertically) +/// is `offset`. +/// +/// If `slant` is non-zero, generates parallelograms instead of squares: the +/// right-hand side of each square gets translated down by `slant`. +fn squares((x0, y0): (f64, f64), size: f64, offset: f64, slant: f64, count: usize) -> Contours { + let mut ret = Vec::new(); + for i in 0..count { + let x = x0 + i as f64 * offset; + for j in 0..count { + let y = y0 + j as f64 * offset; + ret.push(vec![ + Point::new(x, y), + Point::new(x, y + size), + Point::new(x + size, y + size + slant), + Point::new(x + size, y + slant), + ]); + } + } + + ret +} + +/// Generate an `n` by `n` checkerboard-like pattern with overlapping squares. +/// For `n = 3`, it looks like: +/// +/// ```text +/// ┌────┐ ┌────┐ ┌────┐ +/// │ │ │ │ │ │ +/// │ ┌─┼─┼─┐┌─┼─┼─┐ │ +/// └──┼─┘ └─┼┼─┘ └─┼──┘ +/// ┌──┼─┐ ┌─┼┼─┐ ┌─┼──┐ +/// │ └─┼─┼─┘└─┼─┼─┘ │ +/// │ ┌─┼─┼─┐┌─┼─┼─┐ │ +/// └──┼─┘ └─┼┼─┘ └─┼──┘ +/// ┌──┼─┐ ┌─┼┼─┐ ┌─┼──┐ +/// │ └─┼─┼─┘└─┼─┼─┘ │ +/// │ │ │ │ │ │ +/// └────┘ └────┘ └────┘ +/// ``` +/// +/// We return the pattern in two parts: the outer collection of `n x n` +/// non-overlapping squares, and the inner collection of `(n - 1) x (n - 1)` +/// non-overlapping squares. +pub fn checkerboard(n: usize) -> (Contours, Contours) { + ( + squares((0.0, 0.0), 30.0, 40.0, 0.0, n), + squares((20.0, 20.0), 30.0, 40.0, 0.0, n - 1), + ) +} + +/// Like `checkerboard`, but with no exactly-horizontal lines. +/// +/// Horizontal lines have special handling in the sweep-line algorithm, so +/// their presence or absence can affect performance. +pub fn slanted_checkerboard(n: usize) -> (Contours, Contours) { + ( + squares((0.0, 0.0), 30.0, 40.0, 1.0, n), + squares((20.0, 20.0), 30.0, 40.0, 1.0, n - 1), + ) +} + +/// The "evens" are a bunch of long, skinny parallelograms going from top-left +/// to bottom-right. The "odds" go from top-right to bottom-left. +pub fn slanties(n: usize) -> (Contours, Contours) { + let h = 20.0 * n as f64; + + let mut even = Vec::new(); + let mut odd = Vec::new(); + for i in 0..n { + let x_off = 20.0 * i as f64; + even.push(vec![ + Point::new(x_off, 0.0), + Point::new(x_off + h, h), + Point::new(x_off + h + 10.0, h), + Point::new(x_off + 10.0, 0.0), + ]); + + odd.push(vec![ + Point::new(x_off + h, 0.0), + Point::new(x_off, h), + Point::new(x_off + 10.0, h), + Point::new(x_off + h + 10.0, 0.0), + ]); + } + + (even, odd) +} + +/// A bunch of things that all intersect at the origin, some tangentially. +pub fn star(n: usize) -> (BezPath, BezPath) { + fn mul(p: kurbo::Point, f: f64) -> kurbo::Point { + (p.x * f, p.y * f).into() + } + + let angle_increment = PI / (n as f64 * 2.0); + let mut angle = 0.0; + + let mut straight = BezPath::new(); + let mut bent = BezPath::new(); + let p = kurbo::Point::new(1.0, 0.0); + for i in 0..n { + let p0 = Affine::rotate(angle) * p; + let p1 = Affine::rotate(angle + angle_increment) * p; + let p2 = Affine::rotate(angle + angle_increment + PI) * p; + let p3 = Affine::rotate(angle + PI) * p; + + if i.is_multiple_of(2) { + straight.move_to(p0); + straight.line_to(p1); + straight.line_to(p2); + straight.line_to(p3); + straight.close_path(); + } else { + let zero_ish = mul(p0, 1e-12); + let neg_zero_ish = mul(p0, -1e-12); + bent.move_to(p0); + bent.line_to(p1); + bent.curve_to(mul(p1, 2. / 3.), mul(p0.midpoint(p1), 1. / 3.), zero_ish); + bent.curve_to(mul(p2.midpoint(p3), 1. / 3.), mul(p2, 2. / 3.), p2); + bent.line_to(p3); + bent.curve_to( + mul(p3, 2. / 3.), + mul(p2.midpoint(p3), 1. / 3.), + neg_zero_ish, + ); + bent.curve_to(mul(p0.midpoint(p1), 1. / 3.), mul(p0, 2. / 3.), p0); + } + + angle += 2.0 * angle_increment; + } + + (straight, bent) +} diff --git a/vendor/linesweeper/src/geom.rs b/vendor/linesweeper/src/geom.rs new file mode 100644 index 00000000..6aa0bdab --- /dev/null +++ b/vendor/linesweeper/src/geom.rs @@ -0,0 +1,542 @@ +//! Geometric primitives, like points and lines. + +use arrayvec::ArrayVec; +use kurbo::{CubicBez, ParamCurve, ParamCurveExtrema, QuadBez}; +use polycool::Quadratic; + +use crate::curve::{quadratic_signs, solve_t_for_y, solve_x_for_y}; +use crate::num::CheapOrderedFloat; + +/// A two-dimensional point. +/// +/// Points are sorted by `y` and then by `x`, for the convenience of our sweep-line +/// algorithm (which moves in increasing `y`). +#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, PartialEq)] +pub struct Point { + /// Vertical coordinate. + /// + /// Although it isn't important for functionality, the documentation and method naming + /// assumes that larger values are down. + pub y: f64, + /// Horizontal component. + /// + /// Although it isn't important for functionality, the documentation and method naming + /// assumes that larger values are to the right. + pub x: f64, +} + +impl Ord for Point { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + ( + CheapOrderedFloat::from(self.y), + CheapOrderedFloat::from(self.x), + ) + .cmp(&( + CheapOrderedFloat::from(other.y), + CheapOrderedFloat::from(other.x), + )) + } +} + +impl PartialOrd for Point { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for Point {} + +impl std::fmt::Debug for Point { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({:?}, {:?})", self.x, self.y) + } +} + +impl Point { + /// Create a new point. + /// + /// Note that the `x` coordinate comes first. This might be a tiny bit + /// confusing because we're sorting by `y` coordinate first, but `(x, y)` is + /// the only sane order (prove me wrong). + pub fn new(x: f64, y: f64) -> Self { + debug_assert!(x.is_finite()); + debug_assert!(y.is_finite()); + Point { x, y } + } + + /// Compute an affine combination between `self` and `other`; that is, `(1 - t) * self + t * other`. + /// + /// Panics if a NaN is encountered, which might happen if some input is infinite. + pub fn affine(&self, other: &Self, t: f64) -> Self { + Point { + x: (1.0 - t) * self.x + t * other.x, + y: (1.0 - t) * self.y + t * other.y, + } + } + + /// Compatibility with `kurbo` points. + pub fn to_kurbo(self) -> kurbo::Point { + kurbo::Point::new(self.x, self.y) + } + + /// Compatibility with `kurbo` points. + pub fn from_kurbo(c: kurbo::Point) -> Self { + Self::new(c.x, c.y) + } +} + +impl From<(f64, f64)> for Point { + fn from((x, y): (f64, f64)) -> Self { + Self { x, y } + } +} + +impl From for kurbo::Point { + fn from(p: Point) -> Self { + kurbo::Point::new(p.x, p.y) + } +} + +impl From for Point { + fn from(p: kurbo::Point) -> Self { + Point::new(p.x, p.y) + } +} + +/// A contour segment. +#[derive(Clone, PartialEq, Eq)] +pub struct Segment { + inner: SegmentInner, +} + +#[derive(Clone, PartialEq, Eq)] +enum SegmentInner { + Line { + p0: Point, + p1: Point, + }, + Quadratic { + p0: Point, + p1: Point, + p2: Point, + }, + Cubic { + p0: Point, + p1: Point, + p2: Point, + p3: Point, + }, +} + +impl std::fmt::Debug for Segment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.inner { + SegmentInner::Line { p0, p1 } => write!(f, "{p0:?} -- {p1:?}"), + SegmentInner::Quadratic { p0, p1, p2 } => write!(f, "{p0:?} -- {p1:?} -- {p2:?}"), + SegmentInner::Cubic { p0, p1, p2, p3 } => { + write!(f, "{p0:?} -- {p1:?} -- {p2:?} -- {p3:?}") + } + } + } +} + +/// A y-monotonic piece of a cubic Bezier. +#[derive(Debug)] +pub(crate) struct MonotonicPiece { + pub start_t: f64, + pub end_t: f64, + pub piece: Segment, + pub positively_oriented: bool, +} + +pub(crate) fn monotonic_pieces(s: kurbo::PathSeg) -> ArrayVec { + match s { + kurbo::PathSeg::Line(ell) => { + let mut ret = ArrayVec::new(); + let p0: Point = ell.p0.into(); + let p1: Point = ell.p1.into(); + let (p0, p1, positively_oriented) = if p0 <= p1 { + (p0, p1, true) + } else { + (p1, p0, false) + }; + if p0 != p1 { + ret.push(MonotonicPiece { + start_t: 0.0, + end_t: 1.0, + piece: Segment::straight(p0, p1), + positively_oriented, + }); + } + ret + } + kurbo::PathSeg::Quad(q) => monotonic_quadratic_pieces(q), + kurbo::PathSeg::Cubic(c) => monotonic_cubic_pieces(c), + } +} + +/// This could return an `ArrayVec<_, 2>`, but by returning `ArrayVec<_, 3>` we +/// make it the same return type as `monotonic_cubic_pieces`. +fn monotonic_quadratic_pieces(q: QuadBez) -> ArrayVec { + fn to_seg(q: QuadBez) -> Option<(Segment, bool)> { + let (p0, mut p1, p2, orient) = if (q.p0.y, q.p0.x) <= (q.p2.y, q.p2.x) { + (q.p0, q.p1, q.p2, true) + } else { + (q.p2, q.p1, q.p0, false) + }; + if p0 != p2 { + // Our derivative-solving should have made this cubic monotonic, but + // there might be some numerical issues with the starting and ending + // tangents. Fix them up. + p1.y = p0.y.max(p1.y).min(p2.y); + let seg = Segment::monotonic_quadratic(p0.into(), p1.into(), p2.into()); + Some((seg, orient)) + } else { + None + } + } + + // The derivative is 2(1 - t) (p1 - p0) + 2t (p2 - p1), and we want to solve + // for when the y coordinate of this derivative is zero. + let t = (q.p0.y - q.p1.y) / (q.p2.y - 2.0 * q.p1.y + q.p0.y); + if 0.0 < t && t < 1.0 { + let q0 = q.subsegment(0.0..t); + let q1 = q.subsegment(t..1.0); + if let (Some((piece0, orient0)), Some((piece1, orient1))) = (to_seg(q0), to_seg(q1)) { + let mut ret = ArrayVec::new(); + ret.push(MonotonicPiece { + start_t: 0.0, + end_t: t, + piece: piece0, + positively_oriented: orient0, + }); + ret.push(MonotonicPiece { + start_t: t, + end_t: 1.0, + piece: piece1, + positively_oriented: orient1, + }); + return ret; + } + } + + let mut ret = ArrayVec::new(); + if let Some((piece, positively_oriented)) = to_seg(q) { + ret.push(MonotonicPiece { + start_t: 0.0, + end_t: 1.0, + piece, + positively_oriented, + }); + } + ret +} + +pub(crate) fn monotonic_cubic_pieces(cub: CubicBez) -> ArrayVec { + fn to_seg(c: CubicBez) -> Option<(Segment, bool)> { + let (p0, mut p1, mut p2, p3, orient) = if (c.p0.y, c.p0.x) <= (c.p3.y, c.p3.x) { + (c.p0, c.p1, c.p2, c.p3, true) + } else { + (c.p3, c.p2, c.p1, c.p0, false) + }; + if p0 != p3 { + // Our derivative-solving should have made this cubic monotonic, but + // there might be some numerical issues with the starting and ending + // tangents. Fix them up. + p1.y = p0.y.max(p1.y); + p2.y = p3.y.min(p2.y); + let seg = Segment::monotonic_cubic(p0.into(), p1.into(), p2.into(), p3.into()); + Some((seg, orient)) + } else { + None + } + } + + let mut ret = ArrayVec::new(); + let q0 = cub.p1.y - cub.p0.y; + let q1 = cub.p2.y - cub.p1.y; + let q2 = cub.p3.y - cub.p2.y; + + // Convert to the representation a t^2 + b t + c. + let c = q0; + let b = (q1 - q0) * 2.0; + let a = q0 - q1 * 2.0 + q2; + let q = Quadratic::new([c, b, a]); + let (_, r0, r1) = quadratic_signs(q); + + let mut roots: ArrayVec = ArrayVec::new(); + if r0 > 0.0 && r0 < 1.0 { + roots.push(r0); + } + if r1 > r0 && r1 > 0.0 && r1 < 1.0 { + roots.push(r1); + } + + let mut last_r = 0.0; + // We could try to remove very short segments, which could easily happen + // if the input curve has an initial or final tangent that's very close + // to horizontal. For now, we only remove segments that are exactly + // zero-length. + for r in roots { + let piece_before = cub.subsegment(last_r..r); + if let Some((piece, positively_oriented)) = to_seg(piece_before) { + ret.push(MonotonicPiece { + piece, + positively_oriented, + start_t: last_r, + end_t: r, + }); + last_r = r; + } + } + + let piece_before = cub.subsegment(last_r..1.0); + if let Some((piece, positively_oriented)) = to_seg(piece_before) { + ret.push(MonotonicPiece { + piece, + positively_oriented, + start_t: last_r, + end_t: 1.0, + }); + } else if let Some(last_piece) = ret.last_mut() { + last_piece.end_t = 1.0; + } + + ret +} + +impl Segment { + /// Create a new cubic segment that must be increasing in `y`. + pub fn monotonic_cubic(p0: Point, p1: Point, p2: Point, p3: Point) -> Self { + if p3.y == p0.y { + // Ensure that horizontal segments are just represented by straight lines. + // TODO: maybe we should do something about degenerate S-shaped curves? + Self::straight(p0, p3) + } else { + Self { + inner: SegmentInner::Cubic { p0, p1, p2, p3 }, + } + } + } + + /// Create a new quadratic segment that must be increasing in `y`. + pub fn monotonic_quadratic(p0: Point, p1: Point, p2: Point) -> Self { + if p2.y == p0.y { + // Ensure that horizontal segments are just represented by straight lines. + // TODO: maybe we should do something about degenerate C-shaped curves? + Self::straight(p0, p2) + } else { + Self { + inner: SegmentInner::Quadratic { p0, p1, p2 }, + } + } + } + + /// Create a new segment that's just a straight line. + /// + /// `start` must be less than `end`. + pub fn straight(start: Point, end: Point) -> Self { + debug_assert!(start <= end); + Self { + inner: SegmentInner::Line { p0: start, p1: end }, + } + } + + /// The starting point (smallest in the sweep-line order) of this segment. + pub fn start(&self) -> Point { + match self.inner { + SegmentInner::Line { p0, .. } + | SegmentInner::Quadratic { p0, .. } + | SegmentInner::Cubic { p0, .. } => p0, + } + } + + /// The ending point (largest in the sweep-line order) of this segment. + pub fn end(&self) -> Point { + match self.inner { + SegmentInner::Line { p1, .. } => p1, + SegmentInner::Quadratic { p2, .. } => p2, + SegmentInner::Cubic { p3, .. } => p3, + } + } + + /// Compatibility with `kurbo`'s cubics. + pub fn to_kurbo_cubic(&self) -> kurbo::CubicBez { + match self.inner { + SegmentInner::Line { p0: q0, p1: q1 } => { + let p0 = q0.to_kurbo(); + let p3 = q1.to_kurbo(); + let p1 = p0 + (1. / 3.) * (p3 - p0); + let p2 = p0 + (2. / 3.) * (p3 - p0); + kurbo::CubicBez { p0, p1, p2, p3 } + } + SegmentInner::Quadratic { + p0: q0, + p1: q1, + p2: q2, + } => { + let p0 = q0.to_kurbo(); + let p3 = q2.to_kurbo(); + let v1 = q1.to_kurbo().to_vec2(); + let p1 = ((p0 + 2.0 * v1).to_vec2() / 3.0).to_point(); + let p2 = ((p3 + 2.0 * v1).to_vec2() / 3.0).to_point(); + kurbo::CubicBez { p0, p1, p2, p3 } + } + SegmentInner::Cubic { p0, p1, p2, p3 } => kurbo::CubicBez { + p0: p0.to_kurbo(), + p1: p1.to_kurbo(), + p2: p2.to_kurbo(), + p3: p3.to_kurbo(), + }, + } + } + + /// Compatibility with kurbo. + pub fn to_kurbo_seg(&self) -> kurbo::PathSeg { + match self.inner { + SegmentInner::Line { p0, p1 } => kurbo::Line { + p0: p0.to_kurbo(), + p1: p1.to_kurbo(), + } + .into(), + SegmentInner::Quadratic { p0, p1, p2 } => kurbo::QuadBez { + p0: p0.to_kurbo(), + p1: p1.to_kurbo(), + p2: p2.to_kurbo(), + } + .into(), + SegmentInner::Cubic { p0, p1, p2, p3 } => kurbo::CubicBez { + p0: p0.to_kurbo(), + p1: p1.to_kurbo(), + p2: p2.to_kurbo(), + p3: p3.to_kurbo(), + } + .into(), + } + } + + /// TODO: maybe From instead? + pub fn from_kurbo_cubic(c: kurbo::CubicBez) -> Self { + Self::monotonic_cubic( + Point::from_kurbo(c.p0), + Point::from_kurbo(c.p1), + Point::from_kurbo(c.p2), + Point::from_kurbo(c.p3), + ) + } + + /// Is this segment just a straight line? + pub fn is_line(&self) -> bool { + matches!(self.inner, SegmentInner::Line { .. }) + } + + /// If this segment is a line, returns it. + pub fn as_kurbo_line(&self) -> Option { + match self.inner { + SegmentInner::Line { p0, p1 } => Some(kurbo::Line::new(p0.to_kurbo(), p1.to_kurbo())), + _ => None, + } + } + + /// A crude lower bound on our minimum horizontal position. + pub fn min_x(&self) -> f64 { + match self.inner { + SegmentInner::Line { p0, p1 } => p0.x.min(p1.x), + SegmentInner::Quadratic { p0, p1, p2 } => p0.x.min(p1.x).min(p2.x), + SegmentInner::Cubic { p0, p1, p2, p3 } => p0.x.min(p1.x).min(p2.x).min(p3.x), + } + } + + /// A crude upper bound on our maximum horizontal position. + pub fn max_x(&self) -> f64 { + match self.inner { + SegmentInner::Line { p0, p1 } => p0.x.max(p1.x), + SegmentInner::Quadratic { p0, p1, p2 } => p0.x.max(p1.x).max(p2.x), + SegmentInner::Cubic { p0, p1, p2, p3 } => p0.x.max(p1.x).max(p2.x).max(p3.x), + } + } + + /// Our `x` coordinate at the given `y` coordinate. + /// + /// Horizontal segments will return their largest `x` coordinate. + /// + /// # Panics + /// + /// Panics if `y` is outside the `y` range of this segment. + pub fn at_y(&self, y: f64) -> f64 { + debug_assert!( + (self.start().y..=self.end().y).contains(&y), + "segment {self:?}, y={y:?}" + ); + + match self.inner { + SegmentInner::Line { p0, p1 } => { + if self.is_horizontal() { + p1.x + } else { + let t = (y - p0.y) / (p1.y - p0.y); + p0.x + t * (p1.x - p0.x) + } + } + // We could have a more optimized implementation for qudratics. + SegmentInner::Quadratic { .. } | SegmentInner::Cubic { .. } => { + solve_x_for_y(self.to_kurbo_cubic(), y) + } + } + } + + fn local_bbox(&self, y: f64, eps: f64) -> kurbo::Rect { + let start_y = (y - eps).max(self.start().y); + let end_y = (y + eps).min(self.end().y); + + match self.inner { + SegmentInner::Line { .. } => { + let start_x = self.at_y(start_y); + let end_x = self.at_y(end_y); + kurbo::Rect::from_points((start_x, start_y), (end_x, end_y)) + } + SegmentInner::Quadratic { .. } | SegmentInner::Cubic { .. } => { + let c = self.to_kurbo_cubic(); + let t_min = solve_t_for_y(c, start_y); + let t_max = solve_t_for_y(c, end_y); + + c.subsegment(t_min..t_max).bounding_box() + } + } + } + + /// Returns a lower bound on the `x` coordinate near `y`. + /// + /// More precisely, take a Minkowski sum between this curve + /// and a small square. This returns a lower bound on the `x` + /// coordinate of the resulting set at height `y`. + pub fn lower(&self, y: f64, eps: f64) -> f64 { + self.local_bbox(y, eps).min_x() - eps + } + + /// Returns an upper bound on the `x` coordinate near `y`. + /// + /// More precisely, take a Minkowski sum between this curve + /// and a small square. This returns a upper bound on the `x` + /// coordinate of the resulting set at height `y`. + pub fn upper(&self, y: f64, eps: f64) -> f64 { + self.local_bbox(y, eps).max_x() + eps + } + + /// Returns true if this segment is exactly horizontal. + pub fn is_horizontal(&self) -> bool { + match self.inner { + SegmentInner::Line { p0, p1 } => p0.y == p1.y, + SegmentInner::Cubic { .. } | SegmentInner::Quadratic { .. } => { + debug_assert_ne!( + self.start().y, + self.end().y, + "horizontal segments should be represented by lines" + ); + false + } + } + } +} diff --git a/vendor/linesweeper/src/lib.rs b/vendor/linesweeper/src/lib.rs new file mode 100644 index 00000000..7d2500d0 --- /dev/null +++ b/vendor/linesweeper/src/lib.rs @@ -0,0 +1,336 @@ +#![deny(missing_docs)] +#![doc = include_str!("../README.md")] + +#[macro_use] +mod typed_vec; + +#[cfg(any(test, feature = "arbitrary"))] +pub mod arbitrary; +pub mod curve; +mod geom; +mod num; +pub mod order; +mod position; +mod segments; +pub mod sweep; +pub mod topology; + +#[cfg(feature = "generators")] +pub mod generators; + +pub use geom::{Point, Segment}; +use kurbo::Shape; +pub use segments::{SegIdx, Segments}; + +// pub so that we can use it in fuzz tests, but it's really private +#[doc(hidden)] +pub mod treevec; + +use topology::{BinaryWindingNumber, Topology}; + +use crate::segments::NonClosedPath; + +#[cfg(test)] +pub mod perturbation; + +/// A fill rule tells us how to decide whether a point is "inside" a polyline. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum FillRule { + /// The point is "inside" if its winding number is odd. + EvenOdd, + /// The point is "inside" if its winding number is non-zero. + NonZero, +} + +impl FillRule { + /// According to this fill rule, is the winding number inside the shape? + pub fn is_inside(self, winding: i32) -> bool { + match self { + FillRule::EvenOdd => winding % 2 != 0, + FillRule::NonZero => winding != 0, + } + } +} + +/// Binary operations between sets. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum BinaryOp { + /// A point is in the union of two sets if it is in either one. + Union, + /// A point is in the intersection of two sets if it is in both. + Intersection, + /// A point is in the difference of two sets if it is in the first but not the second. + Difference, + /// A point is in the exclusive-or of two sets if it is in one or the other, but not both. + Xor, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +/// The input points were faulty. +pub enum Error { + /// At least one of the inputs was infinite. + Infinity, + /// At least one of the inputs was not a number. + NaN, + /// One of the inputs had a non-closed path. + NonClosedPath(NonClosedPath), +} + +impl From for Error { + fn from(ncp: NonClosedPath) -> Self { + Error::NonClosedPath(ncp) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Infinity => write!(f, "one of the inputs was infinite"), + Error::NaN => write!(f, "one of the inputs had a NaN"), + Error::NonClosedPath(_) => write!(f, "one of the inputs had a non-closed path"), + } + } +} + +impl std::error::Error for Error {} + +/// Computes a boolean operation between two sets, each of which is described as +/// a collection of closed curves. +/// +/// This function calls [`binary_op_with_eps`] with an automatically-chosen +/// accuracy parameter. The automatically-chosen accuracy is fairly +/// conservative, so you may get better performance by choosing your own. +pub fn binary_op( + set_a: &kurbo::BezPath, + set_b: &kurbo::BezPath, + fill_rule: FillRule, + op: BinaryOp, +) -> Result { + // Find the extremal values, to figure out how much precision we can support. + let bbox = set_a.bounding_box().union(set_b.bounding_box()); + let min = bbox.min_x().min(bbox.min_y()); + let max = bbox.max_x().max(bbox.max_y()); + if min.is_infinite() || max.is_infinite() { + return Err(Error::Infinity); + } + // If there was any NaN in the input, it should have propagated to the min and max. + if min.is_nan() || max.is_nan() { + return Err(Error::NaN); + } + + // TODO: we did some analysis for error bounds in the case of polylines. + // Think more about what makes sense for curves. + let m_2 = min.abs().max(max.abs()); + let eps = m_2 * (f64::EPSILON * 64.0); + let eps = eps.max(1e-6); + + debug_assert!(eps.is_finite()); + binary_op_with_eps(set_a, set_b, fill_rule, op, eps) +} + +/// Computes a boolean operation between two sets, each of which is described as +/// a collection of closed curves. +/// +/// The output paths will not exactly agree with the input paths in general. +/// The accuracy parameter `eps` bounds how much they can differ. +pub fn binary_op_with_eps( + set_a: &kurbo::BezPath, + set_b: &kurbo::BezPath, + fill_rule: FillRule, + op: BinaryOp, + eps: f64, +) -> Result { + let top = Topology::from_paths_binary(set_a, set_b, eps)?; + #[cfg(feature = "debug-svg")] + { + svg::save( + "out.svg", + &top.dump_svg(|tag| { + if tag == topology::OneOfTwo::A { + "red".to_owned() + } else { + "blue".to_owned() + } + }), + ) + .unwrap(); + } + + let inside = |windings: &BinaryWindingNumber| { + let inside_one = |winding| match fill_rule { + FillRule::EvenOdd => winding % 2 != 0, + FillRule::NonZero => winding != 0, + }; + + match op { + BinaryOp::Union => inside_one(windings.shape_a) || inside_one(windings.shape_b), + BinaryOp::Intersection => inside_one(windings.shape_a) && inside_one(windings.shape_b), + BinaryOp::Xor => inside_one(windings.shape_a) != inside_one(windings.shape_b), + BinaryOp::Difference => inside_one(windings.shape_a) && !inside_one(windings.shape_b), + } + }; + + Ok(top.contours(inside)) +} + +#[cfg(test)] +mod tests { + use insta::assert_ron_snapshot; + use kurbo::BezPath; + + use super::*; + + #[test] + fn two_squares() { + fn to_bez(mut points: impl Iterator) -> BezPath { + let p = points.next().unwrap(); + let mut ret = BezPath::default(); + ret.move_to(p); + for q in points { + ret.line_to(q); + } + ret.line_to(p); + ret + } + let a = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]; + let b = vec![(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)]; + let output = binary_op( + &to_bez(a.into_iter()), + &to_bez(b.into_iter()), + FillRule::EvenOdd, + BinaryOp::Intersection, + ) + .unwrap(); + + insta::assert_ron_snapshot!(output); + } + + // This example has a union of two not-quite-axis-aligned crosses. It + // suffers from some extra quadratic segments near the intersection points, + // but it used to be worse. + #[test] + fn path_blowup() { + let path1 = "M-90.03662872314453,-212 L-90.03662872314453,212 L90.03565216064453,212 L90.03565216064453,-212 L-90.03662872314453,-212 Z"; + let path2 = "M211.99964904785156,-90.03582000732422 L-212.00035095214844,-90.03646087646484 L-212.00062561035156,90.03582000732422 L211.99937438964844,90.03646087646484 L211.99964904785156,-90.03582000732422 Z"; + if let Ok(output) = binary_op( + &BezPath::from_svg(path1).unwrap(), + &BezPath::from_svg(path2).unwrap(), + FillRule::NonZero, + BinaryOp::Union, + ) { + let output = output.contours().next().unwrap(); + let path_length = output.path.elements().len(); + // This should ideally be more like 12, but there + // are some small horizontal segments in the output. + // It used to be 77, though... + assert!(path_length <= 16); + } + } + + #[test] + fn test_empty_horizontals() { + let square = kurbo::Rect::from_origin_size((0.0, 0.0), (10.0, 10.0)).to_path(0.0); + let line_and_back = { + let mut path = kurbo::Line::new((-1.0, 0.0), (1.0, 0.0)).to_path(0.0); + path.close_path(); + path + }; + let line_and_back_2 = { + let mut path = kurbo::Line::new((2.0, 1.0), (12.0, 1.0)).to_path(0.0); + path.close_path(); + path + }; + + Topology::from_path(&line_and_back, 1e-3).unwrap(); + Topology::from_path(&line_and_back_2, 1e-3).unwrap(); + Topology::::from_paths([(&line_and_back, ()), (&line_and_back_2, ())], 1e-3).unwrap(); + Topology::::from_paths( + [(&line_and_back, ()), (&line_and_back_2, ()), (&square, ())], + 1e-3, + ) + .unwrap(); + + let output = binary_op( + &square, + &line_and_back_2, + FillRule::EvenOdd, + BinaryOp::Difference, + ) + .unwrap(); + assert_ron_snapshot!(output); + } + + #[test] + fn monotonicity_violation() { + let path = "M-16.252847470703124,8.977497100830078 C-8.126453262439954,8.977497100830078 -3.724613933559983,8.977497100830078 4.401851380682135,8.977497100830078 C4.401851380682135,8.977497100830078 16.252847872314454,8.977497100830075 16.252847872314454,-0.5280563794708262 C16.252847872314454,-8.977497100830078 4.401851380682135,-8.977497100830075 4.401851380682135,-8.977497100830075 L4.401851380682135,-8.977497100830075 C-5.078945812623724,-8.977497100830075 -1.5236468651340243,-8.977497100830075 -11.004562568404802,-8.977497100830075 Z"; + let path = BezPath::from_svg(path).unwrap(); + Topology::from_path(&path, 0.001).unwrap(); + } + + #[test] + fn empty_paths() { + let contours = binary_op( + &BezPath::new(), + &BezPath::new(), + FillRule::EvenOdd, + BinaryOp::Xor, + ) + .unwrap(); + assert!(contours.contours().next().is_none()); + } + + #[test] + fn repeated_points() { + Topology::from_polylines_binary( + [[ + Point::new(0.0, 0.0), + Point::new(0.0, 0.0), + Point::new(1.0, 0.0), + Point::new(1.0, 1.0), + ]], + [[ + Point::new(0.0, 0.0), + Point::new(0.0, 1.0), + Point::new(1.0, 1.0), + ]], + 1e-6, + ); + } + + #[test] + fn quadratics_in_quadratics_out() { + let mut quad_path = BezPath::new(); + quad_path.move_to((0.0, 0.0)); + quad_path.quad_to((1.0, -5.0), (2.0, 0.0)); + quad_path.quad_to((1.0, 5.0), (0.0, 0.0)); + + let mut rectangle = BezPath::new(); + rectangle.move_to((-1.0, -1.0)); + rectangle.line_to((-1.0, 1.0)); + rectangle.line_to((3.0, 1.0)); + rectangle.line_to((3.0, -1.0)); + rectangle.line_to((-1.0, -1.0)); + + let mut segs = Segments::default(); + segs.add_bez_path(&quad_path).unwrap(); + + let contours = binary_op( + &quad_path, + &rectangle, + FillRule::EvenOdd, + BinaryOp::Intersection, + ) + .unwrap(); + assert_eq!(contours.contours().count(), 1); + let contour = contours.contours().next().unwrap(); + assert_eq!( + contour + .path + .elements() + .iter() + .filter(|el| matches!(el, &&kurbo::PathEl::QuadTo(..))) + .count(), + 4 + ); + } +} diff --git a/vendor/linesweeper/src/num.rs b/vendor/linesweeper/src/num.rs new file mode 100644 index 00000000..b906b671 --- /dev/null +++ b/vendor/linesweeper/src/num.rs @@ -0,0 +1,132 @@ +//! Some numerical utilities. + +use std::hash::Hash; + +/// A wrapper for `f64` that implements `Ord`. +/// +/// Unlike the more principled wrappers in the `ordered_float` crate, this one +/// just breaks the `Ord` rules when comparing NaNs -- it doesn't order them, +/// nor does it panic or guard against them on construction. This makes things +/// substantially faster: I measured a 20% improvement to some benchmarks by +/// switching from `OrderedFloat` to `CheapOrderedFloat`. +/// +/// Ignoring NaNs should be ok: we check input for NaNs and infinities, and +/// then we make some effort not to introduce them. +#[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(test, serde(transparent))] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CheapOrderedFloat(f64); + +impl std::ops::Add for CheapOrderedFloat { + type Output = Self; + + fn add(self, rhs: CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 + rhs.0) + } +} + +impl<'a> std::ops::Add<&'a CheapOrderedFloat> for CheapOrderedFloat { + type Output = Self; + + fn add(self, rhs: &'a CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 + rhs.0) + } +} + +impl std::ops::Sub for CheapOrderedFloat { + type Output = Self; + + fn sub(self, rhs: CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 - rhs.0) + } +} + +impl<'a> std::ops::Sub<&'a CheapOrderedFloat> for CheapOrderedFloat { + type Output = Self; + + fn sub(self, rhs: &'a CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 - rhs.0) + } +} + +impl std::ops::Mul for CheapOrderedFloat { + type Output = Self; + + fn mul(self, rhs: CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 * rhs.0) + } +} + +impl<'a> std::ops::Mul<&'a CheapOrderedFloat> for CheapOrderedFloat { + type Output = Self; + + fn mul(self, rhs: &'a CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 * rhs.0) + } +} + +impl std::ops::Div for CheapOrderedFloat { + type Output = Self; + + fn div(self, rhs: CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 / rhs.0) + } +} + +impl<'a> std::ops::Div<&'a CheapOrderedFloat> for CheapOrderedFloat { + type Output = Self; + + fn div(self, rhs: &'a CheapOrderedFloat) -> Self::Output { + CheapOrderedFloat(self.0 / rhs.0) + } +} + +impl std::ops::Neg for CheapOrderedFloat { + type Output = Self; + + fn neg(self) -> Self::Output { + CheapOrderedFloat(-self.0) + } +} + +impl Hash for CheapOrderedFloat { + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state) + } +} + +impl CheapOrderedFloat { + /// Retrieve the inner `f64`. + pub fn into_inner(self) -> f64 { + self.0 + } +} + +// Now comes the fishy stuff. +impl Eq for CheapOrderedFloat {} + +impl PartialOrd for CheapOrderedFloat { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CheapOrderedFloat { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self.0 < other.0 { + std::cmp::Ordering::Less + } else if self.0 > other.0 { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal + } + } +} + +impl From for CheapOrderedFloat { + fn from(value: f64) -> Self { + CheapOrderedFloat(value) + } +} diff --git a/vendor/linesweeper/src/order.rs b/vendor/linesweeper/src/order.rs new file mode 100644 index 00000000..b421bd88 --- /dev/null +++ b/vendor/linesweeper/src/order.rs @@ -0,0 +1,130 @@ +//! Curve order comparisons, with caching. + +use rustc_hash::FxHashMap; + +use crate::{ + SegIdx, Segments, + curve::{self, CurveOrder}, +}; + +/// A cache for curve comparisons, so that each pair of curves needs to be compared at most once. +#[derive(Clone, Debug)] +pub struct ComparisonCache { + inner: FxHashMap<(SegIdx, SegIdx), (CurveOrder, CurveOrder)>, + accuracy: f64, + tolerance: f64, +} + +impl ComparisonCache { + /// Creates a new comparison cache. + /// + /// `tolerance` tells us how close two curves can be to be declared "ish", and + /// `accuracy` tells us how closely we need to evaluate the tolerance. For + /// example, if `accuracy` is `tolerance / 2.0` then we'll guarantee (up to + /// some floating-point error) that if the two curves are further than + /// `1.5 * tolerance` apart then we'll give them a strict order, and if they're + /// less than `tolerance / 2.0` apart then we'll give then an "ish" order. + pub fn new(tolerance: f64, accuracy: f64) -> Self { + ComparisonCache { + inner: FxHashMap::default(), + accuracy, + tolerance, + } + } + + /// Creates a new comparison cache. + /// + /// `tolerance` tells us how close two curves can be to be declared "ish", and + /// `accuracy` tells us how closely we need to evaluate the tolerance. For + /// example, if `accuracy` is `tolerance / 2.0` then we'll guarantee (up to + /// some floating-point error) that if the two curves are further than + /// `1.5 * tolerance` apart then we'll give them a strict order, and if they're + /// less than `tolerance / 2.0` apart then we'll give then an "ish" order. + pub fn new_without_y_slop(tolerance: f64, accuracy: f64) -> Self { + ComparisonCache { + inner: FxHashMap::default(), + accuracy, + tolerance, + } + } + + /// Compares two segments, returning their order. + pub fn compare_segments( + &mut self, + segments: &Segments, + i: SegIdx, + j: SegIdx, + ) -> &mut CurveOrder { + use std::collections::hash_map::Entry; + + let (i, j, flipped) = if i.0 < j.0 { + (i, j, false) + } else { + (j, i, true) + }; + + match self.inner.entry((i, j)) { + Entry::Occupied(o) => { + if flipped { + &mut o.into_mut().1 + } else { + &mut o.into_mut().0 + } + } + Entry::Vacant(v) => { + let segi = &segments[i]; + let segj = &segments[j]; + + let forward = + if let (Some(l0), Some(l1)) = (segi.as_kurbo_line(), segj.as_kurbo_line()) { + curve::line::intersect_lines(l0, l1, self.tolerance) + } else { + let c0 = segi.to_kurbo_cubic(); + let c0_div = segments.horiz_decomp(i); + let c1 = segj.to_kurbo_cubic(); + let c1_div = segments.horiz_decomp(j); + curve::intersect_cubics_with_precomputed_decomp( + c0, + c0_div, + c1, + c1_div, + self.accuracy, + self.tolerance, + ) + }; + debug_assert_eq!( + forward.iter().last().unwrap().1, + segi.end().y.min(segj.end().y) + ); + let reverse = forward.flip(); + let v = v.insert((forward, reverse)); + if flipped { &mut v.1 } else { &mut v.0 } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{SegIdx, Segments, curve::Order}; + + use super::ComparisonCache; + + #[test] + fn slop_regression() { + let mut segments = Segments::default(); + let eps = 0.1; + segments.add_points([(-0.5, -0.5), (-0.5, 0.5)]); + segments.add_points([(0.0, 0.0), (0.0, 1.0)]); + let mut cmp_cache = ComparisonCache::new(eps, eps / 2.0); + assert_eq!( + cmp_cache + .compare_segments(&segments, SegIdx(0), SegIdx(1)) + .iter() + .next() + .unwrap() + .2, + Order::Left + ); + } +} diff --git a/vendor/linesweeper/src/perturbation.rs b/vendor/linesweeper/src/perturbation.rs new file mode 100644 index 00000000..ffb4aea5 --- /dev/null +++ b/vendor/linesweeper/src/perturbation.rs @@ -0,0 +1,200 @@ +#![allow(missing_docs)] + +//! Functionality for constructing perturbations for testing. +// TODO: add support for curves +// TODO: see how this looks in arbtest + +use proptest::{arbitrary::any, prop_oneof, strategy::Strategy}; + +use crate::geom::Point; + +#[derive(Clone, Copy, Debug)] +pub enum F64Perturbation { + /// Perturb by between -128 and 127 ulps. + Ulp(i8), + /// Perturb by a bounded additive amount. + Eps(f64), +} + +// The Debug bound is because some of the proptest strategy builders want it. Having it here is +// easier than copying it everywhere. +pub trait FloatPerturbation: std::fmt::Debug { + type Float; + + fn apply(&self, f: &Self::Float) -> Self::Float; +} + +impl FloatPerturbation for F64Perturbation { + type Float = f64; + + fn apply(&self, f: &f64) -> f64 { + match self { + F64Perturbation::Ulp(n) => { + let same_sign = (*n > 0) == (*f > 0.0); + let sign_bit = 1 << 63; + match f.classify() { + std::num::FpCategory::Nan => unreachable!(), + std::num::FpCategory::Infinite => *f, + std::num::FpCategory::Zero => { + let mut bits = n.unsigned_abs().into(); + if *n < 0 { + bits |= sign_bit; + } + f64::from_bits(bits) + } + std::num::FpCategory::Subnormal => { + let bits = f.abs().to_bits(); + let bits = if same_sign { + bits + u64::from(n.unsigned_abs()) + } else { + bits.abs_diff(u64::from(n.unsigned_abs())) + }; + f.signum() * f64::from_bits(bits) + } + std::num::FpCategory::Normal => { + let delta = if same_sign { + (*n as i64).abs() + } else { + -(*n as i64).abs() + }; + // Taking the absolute value sets the sign bit to zero, so the + // addition should never overflow. + let bits = f.abs().to_bits().checked_add_signed(delta).unwrap(); + let x = if bits & sign_bit != 0 { + f64::INFINITY + } else { + f64::from_bits(bits) + }; + f.signum() * x + } + } + } + F64Perturbation::Eps(x) => f + x, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct PointPerturbation { + pub x: P, + pub y: P, +} + +impl> PointPerturbation

{ + pub fn apply(&self, p: Point) -> Point { + Point { + x: self.x.apply(&p.x), + y: self.y.apply(&p.y), + } + } +} + +#[derive(Clone, Debug)] +pub enum Perturbation { + Base { + idx: usize, + }, + Point { + perturbation: PointPerturbation

, + idx: usize, + next: Box>, + }, + Subdivision { + // Between 0.0 and 1.0 + t: P::Float, + idx: usize, + next: Box>, + }, + Superimposition { + left: Box>, + right: Box>, + }, +} + +pub fn f64_perturbation(eps: f64) -> impl Strategy + Clone { + prop_oneof![ + any::().prop_map(F64Perturbation::Ulp), + (-eps..=eps).prop_map(F64Perturbation::Eps) + ] +} + +pub fn point_perturbation( + fp: impl Strategy + Clone, +) -> impl Strategy> { + (fp.clone(), fp).prop_map(|(x, y)| PointPerturbation { x, y }) +} + +pub fn perturbation( + fp: impl Strategy + Clone + 'static, +) -> impl Strategy> { + let leaf = any::().prop_map(|idx| Perturbation::Base { idx }); + leaf.prop_recursive(3, 16, 8, move |inner| { + prop_oneof![ + ( + point_perturbation(fp.clone()), + any::(), + inner.clone() + ) + .prop_map(|(perturbation, idx, next)| { + Perturbation::Point { + perturbation, + idx, + next: Box::new(next), + } + }), + (0.0f64..1.0, any::(), inner.clone()).prop_map(|(t, idx, next)| { + Perturbation::Subdivision { + t, + idx, + next: Box::new(next), + } + }), + (inner.clone(), inner.clone()).prop_map(|(left, right)| { + Perturbation::Superimposition { + left: Box::new(left), + right: Box::new(right), + } + }) + ] + }) +} + +fn index(arr: &[T], idx: usize) -> &T { + &arr[idx % arr.len()] +} + +fn index_mut(arr: &mut [T], idx: usize) -> &mut T { + &mut arr[idx % arr.len()] +} + +pub fn realize_perturbation( + base_cases: &[Vec], + pert: &Perturbation, +) -> Vec { + match pert { + Perturbation::Base { idx } => index(base_cases, *idx).to_owned(), + Perturbation::Point { + perturbation, + idx, + next, + } => { + let mut next = realize_perturbation(base_cases, next); + let p = index_mut(&mut next, *idx); + *p = perturbation.apply(*p); + next + } + Perturbation::Subdivision { t, idx, next } => { + let mut next = realize_perturbation(base_cases, next); + let idx = *idx % next.len(); + let p0 = *index(&next, idx); + let p1 = *index(&next, idx + 1); + next.insert(idx + 1, p0.affine(&p1, *t)); + next + } + Perturbation::Superimposition { left, right } => { + let mut next = realize_perturbation(base_cases, left); + next.extend(realize_perturbation(base_cases, right)); + next + } + } +} diff --git a/vendor/linesweeper/src/position.rs b/vendor/linesweeper/src/position.rs new file mode 100644 index 00000000..f4cbd773 --- /dev/null +++ b/vendor/linesweeper/src/position.rs @@ -0,0 +1,561 @@ +//! An algorithm for laying out curves in a given order. +//! +//! Our boolean ops algorithms work by first deciding horizontal orderings for curves +//! (with some guarantees about being approximately correct) +//! and then laying out the curves in a way that has the horizontal orders we decided +//! on. For this second step, which is the task of this module, we really +//! guarantee correctness: the curves that we output really have the specified +//! orders. + +use std::collections::BinaryHeap; + +use kurbo::{BezPath, CubicBez, ParamCurve}; +use polycool::Quadratic; + +use crate::{ + SegIdx, Segment, Segments, + curve::{ + Order, QuadraticApprox, solve_t_for_y, + transverse::{transversal_after, transversal_before}, + y_subsegment, + }, + num::CheapOrderedFloat, + order::ComparisonCache, + topology::{HalfOutputSegVec, OutputSegIdx, OutputSegVec, ScanLineOrder}, +}; + +// TODO: maybe a Context type to keep the parameters in check? +#[allow(clippy::too_many_arguments)] +fn ordered_curves_all_close( + segs: &Segments, + order: &[SegIdx], + output_order: &[OutputSegIdx], + out: &mut OutputSegVec, + mut y0: f64, + y1: f64, + endpoints: &HalfOutputSegVec, + accuracy: f64, +) { + if order.iter().all(|&s| segs[s].is_line()) { + // If everything is a line, since our endpoints are already in the right order + // the segments between them are also guaranteed to be in the right order. + return; + } + + if order.len() == 2 { + let s0 = order[0]; + let s1 = order[1]; + let o0 = output_order[0]; + let o1 = output_order[1]; + + let p0 = bez_end(&out[o0].path); + let p1 = bez_end(&out[o1].path); + let q0 = endpoints[o0.second_half()]; + let q1 = endpoints[o1.second_half()]; + + if p0.y == y0 && p1.y == y0 { + let c0 = next_subsegment(&segs[s0], &out[o0].path, y1, q0); + let c1 = next_subsegment(&segs[s1], &out[o1].path, y1, q1); + + if transversal_after(c0, c1, y1) { + return; + } + } + + if q0.y == y1 && q1.y == y1 { + let c0 = next_subsegment(&segs[s0], &out[o0].path, y1, q0); + let c1 = next_subsegment(&segs[s1], &out[o1].path, y1, q1); + + if transversal_before(c0, c1, y0) { + return; + } + } + } + + // Ensure everything in `out` goes up to `y0`. Anything that doesn't go up to `y0` is + // an output where we can just copy from the input. + for (&seg_idx, &out_idx) in order.iter().zip(output_order) { + let out = &mut out[out_idx]; + if bez_end_y(&out.path) < y0 { + out.copied_idx = Some(out.path.elements().len() - 1); + let endpoint = endpoints[out_idx.second_half()]; + let c = next_subsegment(&segs[seg_idx], &out.path, y0, endpoint); + if segs[seg_idx].is_line() { + out.path.line_to(c.p3) + } else { + out.path.curve_to(c.p1, c.p2, c.p3); + } + } + } + + let mut approxes = Vec::new(); + + // The recentering helps, but there are still some issues with approximation when almost horizontal + while y0 < y1 { + let next_y0 = approximate(y0, y1, order, segs, accuracy, &mut approxes); + + let y_mid = (y0 + next_y0) / 2.0; + + let mut x_mid_max_so_far = f64::NEG_INFINITY; + let mut x1_max_so_far = f64::NEG_INFINITY; + for (quad, out_idx) in approxes.iter().zip(output_order) { + let &[c0, c1, c2] = quad.q.coeffs(); + // These would be the control points, but since `approximate` recenters the y interval, + // they're different... + // let x_mid = c0 + y_mid * c1 + y0 * next_y0 * c2; + // let x1 = c0 + c1 * next_y0 + c2 * next_y0 * next_y0; + let x_mid = c0 + (y0 - y_mid) * (next_y0 - y_mid) * c2; + let x1 = c0 + c1 * (next_y0 - y_mid) + c2 * (next_y0 - y_mid) * (next_y0 - y_mid); + + x_mid_max_so_far = x_mid_max_so_far.max(x_mid); + x1_max_so_far = x1_max_so_far.max(x1); + + out[*out_idx] + .path + .quad_to((x_mid_max_so_far, y_mid), (x1_max_so_far, next_y0)); + } + approxes.clear(); + + y0 = next_y0; + } +} + +fn horizontal_error_weight(c: CubicBez) -> f64 { + let tangents = [c.p1 - c.p0, c.p2 - c.p1, c.p3 - c.p2]; + let mut weight = 0.0f64; + + fn different_signs(x: f64, y: f64) -> bool { + (x > 0.0 && y < 0.0) || (x < 0.0) && (y > 0.0) + } + + if different_signs(tangents[0].x, tangents[1].x) + || different_signs(tangents[1].x, tangents[2].x) + { + return 1.0; + } + + for v in tangents { + let denom = v.hypot2().sqrt(); + if denom != 0.0 { + let w = v.y.abs() / denom; + weight = weight.max(w); + } + } + weight +} + +// Pushes approximating parabolas into `out`, and returns the final y position. +fn approximate( + y0: f64, + mut y1: f64, + order: &[SegIdx], + segs: &Segments, + accuracy: f64, + out: &mut Vec, +) -> f64 { + let orig_len = out.len(); + + 'retry: loop { + let y_mid = (y0 + y1) / 2.0; + for seg_idx in order { + let cubic = segs[*seg_idx].to_kurbo_cubic(); + let mut cubic = y_subsegment(cubic, y0, y1); + cubic.p0.y -= y_mid; + cubic.p1.y -= y_mid; + cubic.p2.y -= y_mid; + cubic.p3.y -= y_mid; + let approx = QuadraticApprox::from_cubic(cubic, None); + + let factor = horizontal_error_weight(cubic); + + let too_short = y1 <= y0 + accuracy; + let accurate = approx.dmax.max(-approx.dmin) * factor < accuracy; + if !too_short && !accurate { + // TODO: can we be smarter about this? + y1 = (y0 + y1) / 2.0; + out.truncate(orig_len); + continue 'retry; + } + + out.push(approx); + } + + return y1; + } +} + +#[derive(Debug, PartialEq, Eq)] +struct HeapEntry { + y: CheapOrderedFloat, + idx: OutputSegIdx, +} + +impl Ord for HeapEntry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other.y.cmp(&self.y).then(self.idx.cmp(&other.idx)) + } +} + +impl PartialOrd for HeapEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn bez_end(path: &BezPath) -> kurbo::Point { + match path.elements().last().unwrap() { + kurbo::PathEl::MoveTo(p) + | kurbo::PathEl::LineTo(p) + | kurbo::PathEl::QuadTo(_, p) + | kurbo::PathEl::CurveTo(_, _, p) => *p, + kurbo::PathEl::ClosePath => unreachable!(), + } +} + +fn bez_end_y(path: &BezPath) -> f64 { + bez_end(path).y +} + +fn next_subsegment(seg: &Segment, out: &BezPath, y1: f64, endpoint: kurbo::Point) -> CubicBez { + let p0 = bez_end(out); + let mut c = y_subsegment(seg.to_kurbo_cubic(), p0.y, y1); + c.p0 = p0; + if endpoint.y == y1 { + c.p3 = endpoint; + } + c +} + +/// A positioned output segment, possibly perturbed from the original. +/// +/// We perturb output segments in order to strictly enforce ordering. +/// A single segment might be expanded into a longer path. +#[derive(Default, Debug)] +pub struct PositionedOutputSeg { + /// The path, which is guaranteed to start and end at the same points as the + /// output segment did. + pub path: BezPath, + /// Sometimes (often, even) a large part of the output segment didn't need + /// to be perturbed. Our sweep-line invariants guarantee that unperturbed + /// part can only be a single contiguous interval: this stores the index + /// of the unperturbed part within `path.segments()`. + pub copied_idx: Option, +} + +impl PositionedOutputSeg { + /// Creates an output segment from a kurbo segment, truncated and perturbed at the endpoints. + pub fn from_seg(s: kurbo::PathSeg, start: kurbo::Point, end: kurbo::Point) -> Self { + if start.y == end.y { + // Horizontal segments are always straight. + PositionedOutputSeg::from_line((start, end)) + } else { + match s { + kurbo::PathSeg::Line(_) => PositionedOutputSeg::from_line((start, end)), + kurbo::PathSeg::Quad(q) => PositionedOutputSeg::from_quad(q, start, end), + kurbo::PathSeg::Cubic(c) => PositionedOutputSeg::from_cubic(c, start, end), + } + } + } + + pub fn from_line(ell: impl Into) -> Self { + let mut path = BezPath::with_capacity(2); + let ell = ell.into(); + path.move_to(ell.p0); + path.line_to(ell.p1); + PositionedOutputSeg { + path, + copied_idx: Some(0), + } + } + + /// Creates an output segment from a cubic, truncated and perturbed at the endpoints. + /// + /// The cubic is assumed to be monotonic in `y`. We assume that `p0.y < p3.y`, and + /// both must be in the `y`-range of the cubic. The cubic is truncated to be between + /// the heights `p0.y` and `p3.y`, and then perturbed horizontally so that it starts + /// exactly and at `p0` and ends exactly at `p3`. + pub fn from_cubic(c: kurbo::CubicBez, p0: kurbo::Point, p3: kurbo::Point) -> Self { + debug_assert!(p0.y >= c.p0.y); + debug_assert!(p3.y <= c.p3.y); + debug_assert!(p0.y < p3.y); + + let t0 = solve_t_for_y(c, p0.y); + let t3 = solve_t_for_y(c, p3.y); + let mut c = c.subsegment(t0..t3); + + let dx0 = p0.x - c.p0.x; + let dx3 = p3.x - c.p3.x; + + let s1 = ((c.p1.y - p0.y) / (p3.y - p0.y)).clamp(0.0, 1.0); + let s2 = ((c.p2.y - p0.y) / (p3.y - p0.y)).clamp(0.0, 1.0); + + c.p1.x += (1.0 - s1) * dx0 + s1 * dx3; + c.p2.x += (1.0 - s2) * dx0 + s2 * dx3; + + let mut path = BezPath::with_capacity(2); + path.move_to(p0); + path.curve_to(c.p1, c.p2, p3); + PositionedOutputSeg { + path, + copied_idx: Some(0), + } + } + + /// Creates an output segment from a quadratic, truncated and perturbed at the endpoints. + /// + /// The cubic is assumed to be monotonic in `y`. We assume that `p0.y < p2.y`, and + /// both must be in the `y`-range of the cubic. The cubic is truncated to be between + /// the heights `p0.y` and `p2.y`, and then perturbed horizontally so that it starts + /// exactly and at `p0` and ends exactly at `p2`. + pub fn from_quad(q: kurbo::QuadBez, p0: kurbo::Point, p2: kurbo::Point) -> Self { + debug_assert!(p0.y >= q.p0.y); + debug_assert!(p2.y <= q.p2.y); + debug_assert!(p0.y < p2.y); + + fn solve_t_for_y(q: kurbo::QuadBez, y: f64) -> f64 { + // Write the y coordinate of `q - y` as `at^2 + bt + c`. + let c = q.p0.y - y; + let b = 2.0 * (q.p1.y - q.p0.y); + let a = q.p0.y - 2.0 * q.p1.y + q.p2.y; + let roots = Quadratic::new([c, b, a]).roots(); + for r in roots { + if (0.0..=1.0).contains(&r) { + return r; + } + } + // If we land here, y should be close to one of the endpoints and it + // was just numerical error that pushed the root out of [0.0, 1.0]. + // The only question is which endpoint. + if y >= 0.5 * (q.p0.y + q.p2.y) { + 1.0 + } else { + 0.0 + } + } + let t0 = solve_t_for_y(q, p0.y); + let t2 = solve_t_for_y(q, p2.y); + let mut q = q.subsegment(t0..t2); + + let dx0 = p0.x - q.p0.x; + let dx2 = p2.x - q.p2.x; + + let s = ((q.p1.y - p0.y) / (p2.y - p0.y)).clamp(0.0, 1.0); + + q.p1.x += (1.0 - s) * dx0 + s * dx2; + + let mut path = BezPath::with_capacity(2); + path.move_to(p0); + path.quad_to(q.p1, p2); + PositionedOutputSeg { + path, + copied_idx: Some(0), + } + } +} + +/// Compute positions for all of the output segments. +/// +/// The orders between the output segments is specified by `order`. The endpoints +/// should have been already computed (in a way that satisfies the order), and +/// are provided in `endpoints`. For each output segment, we return a Bézier +/// path. +pub(crate) fn compute_positions( + segs: &Segments, + orig_seg_map: &OutputSegVec, + cmp: &mut ComparisonCache, + endpoints: &HalfOutputSegVec, + scan_order: &ScanLineOrder, + accuracy: f64, +) -> OutputSegVec { + let mut out = OutputSegVec::::with_size(orig_seg_map.len()); + // We try to build `out` lazily, by avoiding copying input segments to outputs segments + // until they're needed (by copying in one go, we avoid excess subdivisions). That means + // we need to separately keep track of how far down we've looked at each output. If + // we weren't lazy, that would just be the last `y` coordinate in `out`. + let mut last_y = OutputSegVec::::with_size(orig_seg_map.len()); + let mut queue = BinaryHeap::::new(); + for idx in out.indices() { + let p = endpoints[idx.first_half()]; + let q = endpoints[idx.second_half()]; + if p.y == q.y { + out[idx].path.move_to(p); + out[idx].path.line_to(q); + out[idx].copied_idx = Some(0); + continue; + } + out[idx].path.move_to(p); + queue.push(HeapEntry { y: p.y.into(), idx }); + } + + while let Some(entry) = queue.pop() { + // Maybe this entry was already handled by one of its neighbors, in which case we skip it. + let y0 = entry.y.into_inner(); + if last_y[entry.idx] > y0 { + continue; + } + + let mut y1 = endpoints[entry.idx.second_half()].y; + let mut west_scan = vec![entry.idx]; + let mut cur = entry.idx; + while let Some(nbr) = scan_order.west_neighbor_after(cur, y0) { + let order = cmp.compare_segments(segs, orig_seg_map[nbr], orig_seg_map[cur]); + let (_, cmp_end_y, cmp_order) = order.iter().find(|(_, end_y, _)| *end_y > y0).unwrap(); + + if cmp_order == Order::Left { + let next_close_y = scan_order + .close_west_neighbor_height_after(cur, y0, orig_seg_map, segs, cmp) + .unwrap_or(f64::INFINITY); + y1 = y1.min(next_close_y.max(cmp_end_y)); + break; + } else { + y1 = y1.min(cmp_end_y).min(endpoints[nbr.second_half()].y); + west_scan.push(nbr); + } + cur = nbr; + } + + let mut east_scan = vec![]; + let mut cur = entry.idx; + while let Some(nbr) = scan_order.east_neighbor_after(cur, y0) { + let order = cmp.compare_segments(segs, orig_seg_map[cur], orig_seg_map[nbr]); + let (_, cmp_end_y, cmp_order) = order.iter().find(|(_, end_y, _)| *end_y > y0).unwrap(); + + if cmp_order == Order::Left { + let next_close_y = scan_order + .close_east_neighbor_height_after(cur, y0, orig_seg_map, segs, cmp) + .unwrap_or(f64::INFINITY); + y1 = y1.min(next_close_y.max(cmp_end_y)); + break; + } else { + y1 = y1.min(cmp_end_y).min(endpoints[nbr.second_half()].y); + east_scan.push(nbr); + } + cur = nbr; + } + + let mut neighbors = west_scan; + neighbors.reverse(); + neighbors.extend(east_scan); + if neighbors.len() == 1 { + let idx = entry.idx; + // We're far from everything, so we can just copy the input bezier to the output. + // We only actually do this eagerly for horizontal segments: for other segments + // we'll hold off on the copying because it might allow us to avoid further + // subdivision. + if y0 == y1 { + out[idx].copied_idx = Some(out[idx].path.elements().len() - 1); + out[idx].path.line_to(endpoints[idx.second_half()]); + } + } else { + let orig_neighbors = neighbors + .iter() + .map(|idx| orig_seg_map[*idx]) + .collect::>(); + + ordered_curves_all_close( + segs, + &orig_neighbors, + &neighbors, + &mut out, + y0, + y1, + endpoints, + accuracy, + ); + } + for idx in neighbors { + let y_end = endpoints[idx.second_half()].y; + if y1 < y_end { + queue.push(HeapEntry { y: y1.into(), idx }); + } + last_y[idx] = y1; + } + } + + for out_idx in out.indices() { + let out = &mut out[out_idx]; + let endpoint = endpoints[out_idx.second_half()]; + if bez_end_y(&out.path) < endpoint.y { + let seg_idx = orig_seg_map[out_idx]; + out.copied_idx = Some(out.path.elements().len() - 1); + if segs[seg_idx].is_line() { + out.path.line_to(endpoint) + } else { + let c = next_subsegment(&segs[seg_idx], &out.path, endpoint.y, endpoint); + out.path.curve_to(c.p1, c.p2, c.p3); + } + } else { + // The quadratic approximations don't respect the fixed endpoints, so tidy them + // up. Since both the quadratic approximations and the endpoints satisfy + // the ordering, this doesn't mess up the ordering. + match out.path.elements_mut().last_mut().unwrap() { + kurbo::PathEl::MoveTo(p) + | kurbo::PathEl::LineTo(p) + | kurbo::PathEl::QuadTo(_, p) + | kurbo::PathEl::CurveTo(_, _, p) => { + *p = endpoints[out_idx.second_half()]; + } + kurbo::PathEl::ClosePath => unreachable!(), + } + } + } + out +} + +#[cfg(test)] +mod tests { + use kurbo::{BezPath, CubicBez, Line, PathSeg}; + + use crate::Segments; + + use super::horizontal_error_weight; + + #[test] + fn error_weight() { + // Vertical lines have a weight of 1. + let c: CubicBez = PathSeg::from(Line::new((0.0, 0.0), (0.0, 1.0))).to_cubic(); + assert_eq!(horizontal_error_weight(c), 1.0); + + // Horizontal lines have a weight of 0. + let c: CubicBez = PathSeg::from(Line::new((1.0, 0.0), (0.0, 0.0))).to_cubic(); + assert_eq!(horizontal_error_weight(c), 0.0); + + let c: CubicBez = PathSeg::from(Line::new((0.0, 0.0), (1.0, 0.0))).to_cubic(); + assert_eq!(horizontal_error_weight(c), 0.0); + + // S-shaped curves have a weight of 1 + let c = CubicBez::new((0.0, 0.0), (1.0, 0.0), (-1.0, 1.0), (0.0, 1.0)); + assert_eq!(horizontal_error_weight(c), 1.0); + + // Diagonal lines have a weight of about 1/sqrt(2). + let c: CubicBez = PathSeg::from(Line::new((0.0, 0.0), (1.0, 1.0))).to_cubic(); + assert_eq!(horizontal_error_weight(c), 1.0 / 2.0f64.sqrt()); + } + + // Here's an example of lines where we used to have a bad + // approximation. Check that they only require a single + // approximation step. + #[test] + fn linear_approx() { + let mut p0 = BezPath::new(); + p0.move_to((-212.00062561035156, 90.03582000732422)); + p0.line_to((211.99937438964844, 90.03646087646484)); + p0.close_path(); + + let mut p1 = BezPath::new(); + p1.move_to((211.99964904785153, -90.0358200073242)); + p1.line_to((211.99937438964844, 90.03646087646484)); + p1.close_path(); + + let mut segs = Segments::default(); + segs.add_bez_path(&p0).unwrap(); + segs.add_bez_path(&p1).unwrap(); + let s0 = segs.indices().next().unwrap(); + let s1 = segs.indices().nth(2).unwrap(); + + let mut out = Vec::new(); + let y1 = 90.03646087646484; + let next_y0 = super::approximate(90.03645987646233, y1, &[s0, s1], &segs, 2.5e-7, &mut out); + assert_eq!(next_y0, y1); + } +} diff --git a/vendor/linesweeper/src/segments.rs b/vendor/linesweeper/src/segments.rs new file mode 100644 index 00000000..909eb01d --- /dev/null +++ b/vendor/linesweeper/src/segments.rs @@ -0,0 +1,482 @@ +use std::iter::Peekable; + +use kurbo::BezPath; + +use crate::{ + curve::AlmostHorizontalDecomposition, + geom::{Point, Segment, monotonic_pieces}, + num::CheapOrderedFloat, +}; + +/// An index into our segment arena. +/// +/// Throughout this library, we assign identities to segments, so that we may +/// consider segments as different even if they have the same start- and end-points. +/// +/// This index is used to identify a segment, whose data can be retrieved by looking +/// it up in [`Segments`]. (Of course, this index-as-identifier breaks down if there are +/// multiple `Segments` in flight. Just be careful not to mix them up.) +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct SegIdx(pub(crate) usize); + +/// A vector indexed by `SegIdx`. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Hash, PartialEq, Eq)] +#[cfg_attr(test, serde(transparent))] +pub struct SegVec { + inner: Vec, +} + +impl_typed_vec!(SegVec, SegIdx, "s"); + +/// An arena of segments, each of which is a cubic Bézier. +/// +/// Segments are indexed by [`SegIdx`] and can be retrieved by indexing (i.e. with square brackets). +#[derive(Clone, Default)] +pub struct Segments { + segs: SegVec, + horizontal_decomps: SegVec, + contour_prev: SegVec>, + contour_next: SegVec>, + /// For each segment, stores true if the sweep-line order (small y to big y) + /// is the same as the orientation in its original contour. + orientation: SegVec, + /// For each segment, stores its parameter range in the original input + /// segment (which may have been split into monotonic sub-segments). Keeping + /// track of where the splits happened allows us to potentially merge things + /// back at the end. + pub(crate) split_from_predecessor: SegVec<(f64, f64)>, + /// The original input segments, for reconstructing/merging. (TODO: this + /// representation is wasteful, since we only need it for segments that + /// got split.) + pub(crate) input_segs: SegVec, + + /// All the entrance heights, of segments, ordered by height. + /// This includes horizontal segments. + enter: Vec<(f64, SegIdx)>, + /// All the exit heights of segments, ordered by height. + /// This does not include horizontal segments. + exit: Vec<(f64, SegIdx)>, +} + +/// Certain operations expect closed paths, and return this as an error when a path is not closed. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct NonClosedPath { + /// The point at the start of the non-closed path (always generated by a [`kurbo::PathEl::MoveTo`]). + pub start_point: kurbo::Point, + /// The point at the end of the non-closed path. + pub end_point: kurbo::Point, +} + +impl std::fmt::Display for NonClosedPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "non-closed path starting at {} and ending at {}", + self.start_point, self.end_point, + ) + } +} + +impl std::error::Error for NonClosedPath {} + +struct SegmentEntryFormatter<'a> { + idx: SegIdx, + seg: &'a Segment, + prev: Option, + next: Option, + oriented: bool, +} + +impl std::fmt::Debug for SegmentEntryFormatter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let seg_idx = self.idx; + let seg = self.seg; + let prefix = if self.oriented { + self.prev.map(|i| format!("{i:?} -> ")).unwrap_or_default() + } else { + self.next.map(|i| format!("{i:?} <- ")).unwrap_or_default() + }; + let suffix = if self.oriented { + self.next.map(|i| format!(" -> {i:?}")).unwrap_or_default() + } else { + self.prev.map(|i| format!(" <- {i:?}")).unwrap_or_default() + }; + write!(f, "{seg_idx:?}: {prefix}{seg:?}{suffix}") + } +} + +impl std::fmt::Debug for Segments { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut list = f.debug_list(); + for (idx, seg) in self.segs.iter() { + list.entry(&SegmentEntryFormatter { + idx, + seg, + prev: self.contour_prev[idx], + next: self.contour_next[idx], + oriented: self.orientation[idx], + }); + } + list.finish() + } +} + +fn cyclic_pairs(xs: &[T]) -> impl Iterator { + pairs(xs).chain(xs.last().zip(xs.first())) +} + +fn pairs(xs: &[T]) -> impl Iterator { + xs.windows(2).map(|pair| (&pair[0], &pair[1])) +} + +struct SubpathIter<'a, I: Iterator> { + inner: &'a mut Peekable, + started: bool, +} + +impl Iterator for SubpathIter<'_, I> +where + I: Iterator, +{ + type Item = kurbo::PathEl; + + fn next(&mut self) -> Option { + let ret = self.inner.peek()?; + if matches!(ret, kurbo::PathEl::MoveTo(_)) && self.started { + None + } else { + self.started = true; + self.inner.next() + } + } +} + +struct Subpaths { + inner: Peekable, +} + +impl Subpaths +where + I: Iterator, +{ + fn next(&mut self) -> Option> { + if self.inner.peek().is_none() { + None + } else { + Some(SubpathIter { + inner: &mut self.inner, + started: false, + }) + } + } +} + +impl Segments { + /// The number of segments in this arena. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.segs.len() + } + + /// Iterate over all indices that can be used to index into this arena. + pub fn indices(&self) -> impl Iterator + use<> { + (0..self.segs.len()).map(SegIdx) + } + + /// Iterate over all segments in this arena. + pub fn segments(&self) -> impl Iterator { + self.segs.iter().map(|(_, s)| s) + } + + /// Returns the starting point of the segment at `idx`, relative to the segment's original orientation. + /// + /// The segment itself is stored in sweep-line order (i.e. its starting + /// point has the smaller y coordinate) regardless of the original + /// orientation of the segment. Use this method to retrieve the segment's + /// original start point. + pub fn oriented_start(&self, idx: SegIdx) -> Point { + if self.orientation[idx] { + self[idx].start() + } else { + self[idx].end() + } + } + + /// Returns the ending point of the segment at `idx`, relative to the segment's original orientation. + /// + /// The segment itself is stored in sweep-line order (i.e. its starting + /// point has the smaller y coordinate) regardless of the original + /// orientation of the segment. Use this method to retrieve the segment's + /// original end point. + pub fn oriented_end(&self, idx: SegIdx) -> Point { + if self.orientation[idx] { + self[idx].end() + } else { + self[idx].start() + } + } + + /// Returns the index of the segment following `idx`. + /// + /// If `idx` is part of a non-closed path and it is the last segment, + /// this returns `None`. If `idx` is part of a closed path, this will + /// always return `Some`, and you might need to be careful to avoid looping + /// infinitely. + pub fn contour_next(&self, idx: SegIdx) -> Option { + self.contour_next[idx] + } + + /// Returns the index of the segment preceding `idx`. + /// + /// If `idx` is part of a non-closed path and it is the first segment, + /// this returns `None`. If `idx` is part of a closed path, this will + /// always return `Some`, and you might need to be careful to avoid looping + /// infinitely. + pub fn contour_prev(&self, idx: SegIdx) -> Option { + self.contour_prev[idx] + } + + /// Does the sweep-line orientation of `idx` agree with its original orientation? + pub fn positively_oriented(&self, idx: SegIdx) -> bool { + self.orientation[idx] + } + + /// Add a (non-closed) polyline to this arena. + pub fn add_points>(&mut self, ps: impl IntoIterator) { + let old_len = self.segs.len(); + + let ps: Vec<_> = ps.into_iter().map(|p| p.into()).collect(); + if ps.len() <= 1 { + return; + } + + for (p, q) in pairs(&ps) { + let (a, b, orient) = if p < q { (p, q, true) } else { (q, p, false) }; + self.push_seg(Segment::straight(*a, *b)); + self.orientation.push(orient); + self.split_from_predecessor.push((0.0, 1.0)); + self.input_segs + .push(kurbo::PathSeg::Line((p.to_kurbo(), q.to_kurbo()).into())); + self.contour_prev + .push(Some(SegIdx(self.segs.len().saturating_sub(2)))); + self.contour_next.push(Some(SegIdx(self.segs.len()))); + } + + if old_len < self.segs.len() { + self.contour_prev[SegIdx(old_len)] = None; + // unwrap: contour_next has the same length as `segs`, which is + // non-empty because we checked its length + *self.contour_next.inner.last_mut().unwrap() = None; + } + + self.update_enter_exit(old_len); + } + + /// Add a collection of closed polylines to this arena. + /// + /// This can be much faster than calling `add_cycles` repeatedly. + pub fn add_closed_polylines>( + &mut self, + ps: impl IntoIterator>, + ) { + let old_len = self.segs.len(); + for p in ps { + self.add_polyline_without_updating_enter_exit(p); + } + self.update_enter_exit(old_len); + } + + /// Add a closed polyline to this arena. + pub fn add_closed_polyline>(&mut self, ps: impl IntoIterator) { + let old_len = self.segs.len(); + self.add_polyline_without_updating_enter_exit(ps); + self.update_enter_exit(old_len); + } + + /// Add a Bézier path to this arena. + /// + /// The path can contain multiple subpaths, and each of them must be closed. + pub fn add_bez_path(&mut self, p: &BezPath) -> Result<(), NonClosedPath> { + let old_len = self.segs.len(); + self.add_path_without_updating_enter_exit(p, true)?; + self.update_enter_exit(old_len); + Ok(()) + } + + /// Add a Bézier path to this arena. + /// + /// The path can contain multiple subpaths, and each of them may or may not be closed. + pub fn add_non_closed_bez_path(&mut self, p: &BezPath) -> Result<(), NonClosedPath> { + let old_len = self.segs.len(); + self.add_path_without_updating_enter_exit(p, false)?; + self.update_enter_exit(old_len); + Ok(()) + } + + pub(crate) fn add_path_without_updating_enter_exit( + &mut self, + p: &BezPath, + must_be_closed: bool, + ) -> Result<(), NonClosedPath> { + let mut subpaths = Subpaths { + inner: p.iter().peekable(), + }; + while let Some(subpath) = subpaths.next() { + let old_len = self.segs.len(); + for seg in kurbo::segments(subpath) { + let pieces = monotonic_pieces(seg); + for monotonic in pieces { + self.push_seg(monotonic.piece); + self.orientation.push(monotonic.positively_oriented); + self.split_from_predecessor + .push((monotonic.start_t, monotonic.end_t)); + self.input_segs.push(seg); + self.contour_prev + .push(Some(SegIdx(self.segs.len().saturating_sub(2)))); + self.contour_next.push(Some(SegIdx(self.segs.len()))); + } + } + if old_len < self.segs.len() { + let start_idx = SegIdx(old_len); + let end_idx = SegIdx(self.segs.len() - 1); + + // unwrap: contour_next has the same length as `segs`, which is + // non-empty because we checked its length + let start_point = self.oriented_start(start_idx).to_kurbo(); + let end_point = self.oriented_end(end_idx).to_kurbo(); + if start_point == end_point { + self.contour_prev[start_idx] = Some(end_idx); + self.contour_next[end_idx] = Some(start_idx); + } else if must_be_closed { + return Err(NonClosedPath { + start_point, + end_point, + }); + } else { + self.contour_prev[start_idx] = None; + self.contour_next[end_idx] = None; + } + } + } + Ok(()) + } + + fn add_polyline_without_updating_enter_exit>( + &mut self, + ps: impl IntoIterator, + ) { + let old_len = self.segs.len(); + + let ps: Vec<_> = ps.into_iter().map(|p| p.into()).collect(); + if ps.len() <= 1 { + return; + } + + for (p, q) in cyclic_pairs(&ps) { + if p == q { + continue; + } + let (a, b, orient) = if p < q { (p, q, true) } else { (q, p, false) }; + self.push_seg(Segment::straight(*a, *b)); + self.orientation.push(orient); + self.split_from_predecessor.push((0.0, 1.0)); + self.input_segs + .push(kurbo::PathSeg::Line((p.to_kurbo(), q.to_kurbo()).into())); + self.contour_prev + .push(Some(SegIdx(self.segs.len().saturating_sub(2)))); + self.contour_next.push(Some(SegIdx(self.segs.len()))); + } + + if old_len < self.segs.len() { + self.contour_prev[SegIdx(old_len)] = Some(SegIdx(self.segs.len() - 1)); + // unwrap: contour_next has the same length as `segs`, which is + // non-empty because we checked its length + *self.contour_next.inner.last_mut().unwrap() = Some(SegIdx(old_len)); + } + } + + fn push_seg(&mut self, seg: Segment) { + let decomp = if seg.is_horizontal() { + AlmostHorizontalDecomposition::default() + } else { + AlmostHorizontalDecomposition::from_monotone_cubic(seg.to_kurbo_cubic()) + }; + self.horizontal_decomps.push(decomp); + self.segs.push(seg); + } + + /// Construct a segment arena from a single closed polyline. + pub fn from_closed_polyline>(ps: impl IntoIterator) -> Self { + let mut ret = Self::default(); + ret.add_closed_polyline(ps); + ret + } + + pub(crate) fn update_enter_exit(&mut self, old_len: usize) { + for idx in old_len..self.len() { + let seg_idx = SegIdx(idx); + let seg = &self.segs[seg_idx]; + + self.enter.push((seg.start().y, seg_idx)); + if !seg.is_horizontal() { + self.exit.push((seg.end().y, seg_idx)); + } + } + + // We sort the enter segments by y position, and then by horizontal + // start position so that they're fairly likely to get inserted in the + // sweep-line in order (which makes the indexing fix-ups faster). + self.enter.sort_by(|(y1, seg1), (y2, seg2)| { + CheapOrderedFloat::from(*y1) + .cmp(&CheapOrderedFloat::from(*y2)) + .then_with(|| { + CheapOrderedFloat::from(self.segs[*seg1].at_y(*y1)) + .cmp(&CheapOrderedFloat::from(self.segs[*seg2].at_y(*y1))) + }) + }); + self.exit.sort_by(|(y1, _), (y2, _)| { + CheapOrderedFloat::from(*y1).cmp(&CheapOrderedFloat::from(*y2)) + }); + } + + /// All the entrance heights of segments, ordered by height. + /// + /// Includes horizontal segments. + pub fn entrances(&self) -> &[(f64, SegIdx)] { + &self.enter + } + + /// All the exit heights of segments, ordered by height. + /// + /// Does not include horizontal segments. + pub fn exits(&self) -> &[(f64, SegIdx)] { + &self.exit + } + + /// Checks that we satisfy our internal invariants. For testing only. + pub fn check_invariants(&self) { + for (idx, seg) in self.segs.iter() { + assert!(seg.start() <= seg.end()); + assert_ne!(seg.start(), seg.end()); + if let Some(next_idx) = self.contour_next(idx) { + assert_eq!(self.oriented_end(idx), self.oriented_start(next_idx)); + assert_eq!(self.contour_prev(next_idx), Some(idx)); + } + } + } + + pub(crate) fn horiz_decomp(&self, idx: SegIdx) -> &AlmostHorizontalDecomposition { + debug_assert!(!self.segs[idx].is_horizontal()); + &self.horizontal_decomps[idx] + } +} + +impl std::ops::Index for Segments { + type Output = Segment; + + fn index(&self, index: SegIdx) -> &Self::Output { + &self.segs[index] + } +} diff --git a/vendor/linesweeper/src/snapshots/linesweeper__tests__empty_horizontals.snap b/vendor/linesweeper/src/snapshots/linesweeper__tests__empty_horizontals.snap new file mode 100644 index 00000000..0ed2ddcd --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__tests__empty_horizontals.snap @@ -0,0 +1,39 @@ +--- +source: src/lib.rs +expression: output +--- +Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: 0.0, + y: 10.0, + )), + LineTo(Point( + x: 0.0, + y: 0.0, + )), + LineTo(Point( + x: 10.0, + y: 0.0, + )), + LineTo(Point( + x: 10.0, + y: 1.0, + )), + LineTo(Point( + x: 10.0, + y: 10.0, + )), + LineTo(Point( + x: 0.0, + y: 10.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + ], +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__tests__two_squares.snap b/vendor/linesweeper/src/snapshots/linesweeper__tests__two_squares.snap new file mode 100644 index 00000000..53cedc3d --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__tests__two_squares.snap @@ -0,0 +1,35 @@ +--- +source: src/lib.rs +expression: output +--- +Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: 0.0, + y: 0.5, + )), + LineTo(Point( + x: 0.0, + y: 0.0, + )), + LineTo(Point( + x: 0.5, + y: 0.0, + )), + LineTo(Point( + x: 0.5, + y: 0.5, + )), + LineTo(Point( + x: 0.0, + y: 0.5, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + ], +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__diamond.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__diamond.snap new file mode 100644 index 00000000..2549d694 --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__diamond.snap @@ -0,0 +1,225 @@ +--- +source: src/topology.rs +expression: top +--- +Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + ], + open_segs: [ + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: 0.0, + x: 0.0, + ), + Point( + y: 1.0, + x: -1.0, + ), + Point( + y: 1.0, + x: 1.0, + ), + Point( + y: 2.0, + x: 0.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + ], + end: [ + PointIdx(1), + PointIdx(2), + PointIdx(3), + PointIdx(3), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + None, + Some(OutputSegIdx(2)), + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + ], + ), + scan_after: [], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(0), + SegIdx(2), + SegIdx(1), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + false, + true, + ], + ), +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__inner_loop.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__inner_loop.snap new file mode 100644 index 00000000..452a9c7e --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__inner_loop.snap @@ -0,0 +1,596 @@ +--- +source: src/topology.rs +expression: "(top, contours)" +--- +(Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + B, + B, + B, + B, + B, + B, + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -2, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -2, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: -2, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: -2.0, + x: -2.0, + ), + Point( + y: -2.0, + x: 2.0, + ), + Point( + y: -1.0, + x: -1.5, + ), + Point( + y: -1.0, + x: 1.5, + ), + Point( + y: 0.0, + x: -0.1, + ), + Point( + y: 0.0, + x: 0.1, + ), + Point( + y: 2.0, + x: -2.0, + ), + Point( + y: 2.0, + x: 0.0, + ), + Point( + y: 2.0, + x: 2.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(3), + PointIdx(4), + PointIdx(4), + PointIdx(5), + PointIdx(6), + PointIdx(7), + ], + end: [ + PointIdx(6), + PointIdx(1), + PointIdx(8), + PointIdx(7), + PointIdx(3), + PointIdx(7), + PointIdx(7), + PointIdx(5), + PointIdx(7), + PointIdx(7), + PointIdx(8), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(6)), + Some(OutputSegIdx(6)), + None, + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + Some(OutputSegIdx(2)), + None, + None, + Some(OutputSegIdx(5)), + None, + None, + ], + ), + scan_after: [], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(0), + SegIdx(1), + SegIdx(4), + SegIdx(6), + SegIdx(5), + SegIdx(7), + SegIdx(9), + SegIdx(8), + SegIdx(2), + SegIdx(2), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + true, + true, + false, + false, + true, + false, + false, + false, + false, + ], + ), +), Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: -2.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: 2.0, + )), + LineTo(Point( + x: 0.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: 2.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: 0.0, + y: 2.0, + )), + LineTo(Point( + x: 1.5, + y: -1.0, + )), + LineTo(Point( + x: -1.5, + y: -1.0, + )), + LineTo(Point( + x: 0.0, + y: 2.0, + )), + ClosePath, + ]), + parent: Some(ContourIdx(0)), + outer: false, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: 0.0, + y: 2.0, + )), + LineTo(Point( + x: -0.1, + y: 0.0, + )), + LineTo(Point( + x: 0.1, + y: 0.0, + )), + LineTo(Point( + x: 0.0, + y: 2.0, + )), + ClosePath, + ]), + parent: Some(ContourIdx(1)), + outer: true, + ), + ], +)) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs.snap new file mode 100644 index 00000000..cd300ba9 --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs.snap @@ -0,0 +1,313 @@ +--- +source: src/topology.rs +expression: "(top, contours)" +--- +(Topology( + eps: 0.000001, + correctness: GoodEnough, + tag: [ + (), + (), + (), + (), + (), + (), + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 0, + clockwise: 1, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 0, + clockwise: 1, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: -0.2886751345948129, + x: -0.769800358919501, + ), + Point( + y: 0.0, + x: -1.0, + ), + Point( + y: 0.0, + x: 1.0, + ), + Point( + y: 0.28867513459481287, + x: 0.7698003589195007, + ), + Point( + y: 4.0, + x: -2.0, + ), + Point( + y: 4.0, + x: 2.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(4), + ], + end: [ + PointIdx(1), + PointIdx(3), + PointIdx(4), + PointIdx(3), + PointIdx(5), + PointIdx(5), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + None, + Some(OutputSegIdx(1)), + Some(OutputSegIdx(3)), + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + Some(OutputSegIdx(1)), + None, + None, + None, + ], + ), + scan_after: [ + (0.28867513459481287, Some(OutputSegIdx(2)), Some(OutputSegIdx(4))), + ], + orig_seg: OutputSegVec( + inner: [ + SegIdx(0), + SegIdx(1), + SegIdx(5), + SegIdx(2), + SegIdx(3), + SegIdx(4), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + false, + false, + true, + false, + ], + ), +), Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: -1.0, + y: 0.0, + )), + CurveTo(Point( + x: -1.0, + y: -1.0, + ), Point( + x: 1.0, + y: 1.0, + ), Point( + x: 1.0, + y: 0.0, + )), + LineTo(Point( + x: 2.0, + y: 4.0, + )), + LineTo(Point( + x: -2.0, + y: 4.0, + )), + LineTo(Point( + x: -1.0, + y: 0.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + ], +)) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs_with_cut.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs_with_cut.snap new file mode 100644 index 00000000..f2e0cce2 --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__merge_monotonic_segs_with_cut.snap @@ -0,0 +1,320 @@ +--- +source: src/topology.rs +expression: "(top, contours)" +--- +(Topology( + eps: 0.000001, + correctness: GoodEnough, + tag: [ + (), + (), + (), + (), + ], + open_segs: [ + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 0, + clockwise: 1, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: -1, + clockwise: 0, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 0, + clockwise: -1, + ), + HalfSegmentWindingNumbers( + counter_clockwise: 0, + clockwise: -1, + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: -0.2886751345948129, + x: -0.769800358919501, + ), + Point( + y: 0.0, + x: -1.0, + ), + Point( + y: 0.0, + x: 0.00000000000000016653345369377348, + ), + Point( + y: 0.0, + x: 1.0, + ), + Point( + y: 0.28867513459481287, + x: 0.7698003589195007, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(3), + ], + end: [ + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(4), + PointIdx(3), + PointIdx(4), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + None, + None, + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + None, + ], + ), + scan_after: [], + orig_seg: OutputSegVec( + inner: [ + SegIdx(0), + SegIdx(1), + SegIdx(3), + SegIdx(1), + SegIdx(3), + SegIdx(2), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + false, + true, + false, + false, + ], + ), +), Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: -1.0, + y: 0.0, + )), + CurveTo(Point( + x: -1.0, + y: -0.5, + ), Point( + x: -0.5, + y: -0.25, + ), Point( + x: 0.0, + y: 0.0, + )), + LineTo(Point( + x: -1.0, + y: 0.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: 0.00000000000000016653345369377348, + y: 0.0, + )), + LineTo(Point( + x: 1.0, + y: 0.0, + )), + CurveTo(Point( + x: 1.0, + y: 0.5, + ), Point( + x: 0.5, + y: 0.25, + ), Point( + x: 0.0, + y: 0.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + ], +)) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__nested_squares.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__nested_squares.snap new file mode 100644 index 00000000..3b86b379 --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__nested_squares.snap @@ -0,0 +1,501 @@ +--- +source: src/topology.rs +expression: "(&top, contours, top.build_scan_line_orders())" +--- +(Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + B, + B, + B, + B, + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: -2.0, + x: -2.0, + ), + Point( + y: -2.0, + x: 2.0, + ), + Point( + y: -1.0, + x: -1.0, + ), + Point( + y: -1.0, + x: 1.0, + ), + Point( + y: 1.0, + x: -1.0, + ), + Point( + y: 1.0, + x: 1.0, + ), + Point( + y: 2.0, + x: -2.0, + ), + Point( + y: 2.0, + x: 2.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(3), + PointIdx(4), + PointIdx(6), + ], + end: [ + PointIdx(6), + PointIdx(1), + PointIdx(7), + PointIdx(4), + PointIdx(3), + PointIdx(5), + PointIdx(5), + PointIdx(7), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(0)), + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + Some(OutputSegIdx(2)), + None, + None, + ], + ), + scan_after: [ + (1.0, Some(OutputSegIdx(0)), Some(OutputSegIdx(2))), + ], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(0), + SegIdx(1), + SegIdx(7), + SegIdx(4), + SegIdx(5), + SegIdx(6), + SegIdx(2), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + true, + false, + true, + true, + false, + false, + ], + ), +), Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: -2.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: 2.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: -1.0, + y: -1.0, + )), + LineTo(Point( + x: -1.0, + y: 1.0, + )), + LineTo(Point( + x: 1.0, + y: 1.0, + )), + LineTo(Point( + x: 1.0, + y: -1.0, + )), + LineTo(Point( + x: -1.0, + y: -1.0, + )), + ClosePath, + ]), + parent: Some(ContourIdx(0)), + outer: false, + ), + ], +), ScanLineOrder( + west_map: HalfScanLineOrder( + inner: OutputSegVec( + inner: [ + [], + [], + [ + (-2.0, Some(OutputSegIdx(0))), + (-1.0, Some(OutputSegIdx(5))), + (1.0, Some(OutputSegIdx(0))), + ], + [ + (-1.0, Some(OutputSegIdx(0))), + ], + [], + [ + (-1.0, Some(OutputSegIdx(3))), + ], + [], + [], + ], + ), + ), + east_map: HalfScanLineOrder( + inner: OutputSegVec( + inner: [ + [ + (-2.0, Some(OutputSegIdx(2))), + (-1.0, Some(OutputSegIdx(3))), + (1.0, Some(OutputSegIdx(2))), + ], + [], + [], + [ + (-1.0, Some(OutputSegIdx(5))), + ], + [], + [ + (-1.0, Some(OutputSegIdx(2))), + ], + [], + [], + ], + ), + ), +)) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square.snap new file mode 100644 index 00000000..f7faeb17 --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square.snap @@ -0,0 +1,225 @@ +--- +source: src/topology.rs +expression: top +--- +Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + ], + open_segs: [ + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: 0.0, + x: 0.0, + ), + Point( + y: 0.0, + x: 1.0, + ), + Point( + y: 1.0, + x: 0.0, + ), + Point( + y: 1.0, + x: 1.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + ], + end: [ + PointIdx(2), + PointIdx(1), + PointIdx(3), + PointIdx(3), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + ], + ), + scan_after: [], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(0), + SegIdx(1), + SegIdx(2), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + true, + false, + ], + ), +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_and_diamond.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_and_diamond.snap new file mode 100644 index 00000000..07e060ba --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_and_diamond.snap @@ -0,0 +1,508 @@ +--- +source: src/topology.rs +expression: top +--- +Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + B, + B, + B, + B, + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: 0.0, + x: 0.0, + ), + Point( + y: 0.0, + x: 1.0, + ), + Point( + y: 0.005, + x: 0.0, + ), + Point( + y: 0.005, + x: 0.005, + ), + Point( + y: 1.0, + x: -1.0, + ), + Point( + y: 1.0, + x: 0.0, + ), + Point( + y: 1.0, + x: 1.0, + ), + Point( + y: 2.0, + x: 0.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(3), + PointIdx(4), + PointIdx(5), + PointIdx(6), + ], + end: [ + PointIdx(2), + PointIdx(2), + PointIdx(3), + PointIdx(1), + PointIdx(6), + PointIdx(4), + PointIdx(5), + PointIdx(6), + PointIdx(7), + PointIdx(6), + PointIdx(7), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(1)), + Some(OutputSegIdx(2)), + Some(OutputSegIdx(2)), + None, + Some(OutputSegIdx(5)), + Some(OutputSegIdx(6)), + None, + Some(OutputSegIdx(8)), + Some(OutputSegIdx(8)), + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + None, + None, + Some(OutputSegIdx(4)), + None, + None, + None, + ], + ), + scan_after: [], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(7), + SegIdx(4), + SegIdx(0), + SegIdx(1), + SegIdx(7), + SegIdx(3), + SegIdx(4), + SegIdx(6), + SegIdx(2), + SegIdx(5), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + false, + true, + true, + true, + false, + false, + true, + false, + false, + true, + ], + ), +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_with_double_back.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_with_double_back.snap new file mode 100644 index 00000000..1ca10e4a --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__square_with_double_back.snap @@ -0,0 +1,352 @@ +--- +source: src/topology.rs +expression: top +--- +Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + A, + A, + A, + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 2, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: 0.0, + x: 0.0, + ), + Point( + y: 0.0, + x: 0.5, + ), + Point( + y: 0.0, + x: 1.0, + ), + Point( + y: 0.5, + x: 0.5, + ), + Point( + y: 1.0, + x: 0.0, + ), + Point( + y: 1.0, + x: 1.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(1), + PointIdx(1), + PointIdx(2), + PointIdx(4), + ], + end: [ + PointIdx(4), + PointIdx(1), + PointIdx(3), + PointIdx(3), + PointIdx(2), + PointIdx(5), + PointIdx(5), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + true, + true, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + Some(OutputSegIdx(2)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + None, + None, + ], + ), + scan_after: [ + (0.5, Some(OutputSegIdx(0)), Some(OutputSegIdx(5))), + ], + orig_seg: OutputSegVec( + inner: [ + SegIdx(6), + SegIdx(0), + SegIdx(2), + SegIdx(1), + SegIdx(3), + SegIdx(4), + SegIdx(5), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + false, + true, + true, + true, + false, + ], + ), +) diff --git a/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__squares_with_gaps.snap b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__squares_with_gaps.snap new file mode 100644 index 00000000..9ebc976f --- /dev/null +++ b/vendor/linesweeper/src/snapshots/linesweeper__topology__tests__squares_with_gaps.snap @@ -0,0 +1,931 @@ +--- +source: src/topology.rs +expression: "(&top, contours, top.build_scan_line_orders())" +--- +(Topology( + eps: 0.01, + correctness: GoodEnough, + tag: [ + A, + A, + A, + A, + B, + B, + B, + B, + B, + B, + B, + B, + B, + B, + B, + B, + ], + open_segs: [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + ], + winding: OutputSegVec( + inner: [ + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: -1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: -1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: -1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: -1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 1, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + HalfSegmentWindingNumbers( + counter_clockwise: BinaryWindingNumber( + shape_a: 1, + shape_b: 0, + ), + clockwise: BinaryWindingNumber( + shape_a: 0, + shape_b: 0, + ), + ), + ], + ), + points: PointVec( + inner: [ + Point( + y: -2.0, + x: -2.0, + ), + Point( + y: -2.0, + x: 2.0, + ), + Point( + y: -1.0, + x: -4.0, + ), + Point( + y: -1.0, + x: -3.0, + ), + Point( + y: -1.0, + x: 3.0, + ), + Point( + y: -1.0, + x: 4.0, + ), + Point( + y: -0.5, + x: 3.0, + ), + Point( + y: -0.5, + x: 4.0, + ), + Point( + y: 0.5, + x: 3.0, + ), + Point( + y: 0.5, + x: 4.0, + ), + Point( + y: 1.0, + x: -4.0, + ), + Point( + y: 1.0, + x: -3.0, + ), + Point( + y: 1.0, + x: 3.0, + ), + Point( + y: 1.0, + x: 4.0, + ), + Point( + y: 2.0, + x: -2.0, + ), + Point( + y: 2.0, + x: 2.0, + ), + ], + ), + point_idx: HalfOutputSegVec( + start: [ + PointIdx(0), + PointIdx(0), + PointIdx(1), + PointIdx(2), + PointIdx(2), + PointIdx(3), + PointIdx(4), + PointIdx(4), + PointIdx(5), + PointIdx(6), + PointIdx(8), + PointIdx(8), + PointIdx(9), + PointIdx(10), + PointIdx(12), + PointIdx(14), + ], + end: [ + PointIdx(14), + PointIdx(1), + PointIdx(15), + PointIdx(10), + PointIdx(3), + PointIdx(11), + PointIdx(6), + PointIdx(5), + PointIdx(7), + PointIdx(7), + PointIdx(12), + PointIdx(9), + PointIdx(13), + PointIdx(11), + PointIdx(13), + PointIdx(15), + ], + ), + point_neighbors: HalfOutputSegVec( + start: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(1), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(4), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(7), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(6), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(11), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(11), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(11), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(11), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(3), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(10), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(0), + first_half: false, + ), + ), + ], + end: [ + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(15), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(15), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(15), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(15), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(13), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(13), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(13), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(13), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(9), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(8), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(14), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(14), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(12), + first_half: true, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(12), + first_half: true, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(14), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(14), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(5), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(12), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(12), + first_half: false, + ), + ), + PointNeighbors( + clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + counter_clockwise: HalfOutputSegIdx( + idx: OutputSegIdx(2), + first_half: false, + ), + ), + ], + ), + deleted: OutputSegVec( + inner: [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ], + ), + scan_west: OutputSegVec( + inner: [ + None, + Some(OutputSegIdx(0)), + Some(OutputSegIdx(0)), + None, + Some(OutputSegIdx(3)), + Some(OutputSegIdx(3)), + Some(OutputSegIdx(2)), + Some(OutputSegIdx(6)), + Some(OutputSegIdx(6)), + Some(OutputSegIdx(2)), + Some(OutputSegIdx(2)), + Some(OutputSegIdx(10)), + Some(OutputSegIdx(10)), + None, + Some(OutputSegIdx(2)), + None, + ], + ), + scan_east: OutputSegVec( + inner: [ + None, + None, + None, + None, + None, + Some(OutputSegIdx(0)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ], + ), + scan_after: [ + (-0.5, Some(OutputSegIdx(2)), None), + (1.0, None, Some(OutputSegIdx(0))), + (1.0, Some(OutputSegIdx(2)), None), + ], + orig_seg: OutputSegVec( + inner: [ + SegIdx(3), + SegIdx(0), + SegIdx(1), + SegIdx(7), + SegIdx(4), + SegIdx(5), + SegIdx(9), + SegIdx(8), + SegIdx(11), + SegIdx(10), + SegIdx(13), + SegIdx(14), + SegIdx(15), + SegIdx(6), + SegIdx(12), + SegIdx(2), + ], + ), + positively_oriented: OutputSegVec( + inner: [ + false, + true, + true, + false, + true, + true, + true, + false, + false, + true, + false, + true, + true, + false, + false, + false, + ], + ), +), Contours( + contours: [ + Contour( + path: BezPath([ + MoveTo(Point( + x: -2.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: -2.0, + )), + LineTo(Point( + x: 2.0, + y: 2.0, + )), + LineTo(Point( + x: -2.0, + y: 2.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: -4.0, + y: 1.0, + )), + LineTo(Point( + x: -4.0, + y: -1.0, + )), + LineTo(Point( + x: -3.0, + y: -1.0, + )), + LineTo(Point( + x: -3.0, + y: 1.0, + )), + LineTo(Point( + x: -4.0, + y: 1.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: 3.0, + y: -0.5, + )), + LineTo(Point( + x: 3.0, + y: -1.0, + )), + LineTo(Point( + x: 4.0, + y: -1.0, + )), + LineTo(Point( + x: 4.0, + y: -0.5, + )), + LineTo(Point( + x: 3.0, + y: -0.5, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + Contour( + path: BezPath([ + MoveTo(Point( + x: 3.0, + y: 1.0, + )), + LineTo(Point( + x: 3.0, + y: 0.5, + )), + LineTo(Point( + x: 4.0, + y: 0.5, + )), + LineTo(Point( + x: 4.0, + y: 1.0, + )), + LineTo(Point( + x: 3.0, + y: 1.0, + )), + ClosePath, + ]), + parent: None, + outer: true, + ), + ], +), ScanLineOrder( + west_map: HalfScanLineOrder( + inner: OutputSegVec( + inner: [ + [ + (-1.0, Some(OutputSegIdx(5))), + (1.0, None), + ], + [], + [ + (-2.0, Some(OutputSegIdx(0))), + ], + [], + [], + [ + (-1.0, Some(OutputSegIdx(3))), + ], + [ + (-1.0, Some(OutputSegIdx(2))), + ], + [], + [ + (-1.0, Some(OutputSegIdx(6))), + ], + [], + [ + (0.5, Some(OutputSegIdx(2))), + ], + [], + [ + (0.5, Some(OutputSegIdx(10))), + ], + [], + [], + [], + ], + ), + ), + east_map: HalfScanLineOrder( + inner: OutputSegVec( + inner: [ + [ + (-2.0, Some(OutputSegIdx(2))), + ], + [], + [ + (-1.0, Some(OutputSegIdx(6))), + (-0.5, None), + (0.5, Some(OutputSegIdx(10))), + (1.0, None), + ], + [ + (-1.0, Some(OutputSegIdx(5))), + ], + [], + [ + (-1.0, Some(OutputSegIdx(0))), + ], + [ + (-1.0, Some(OutputSegIdx(8))), + ], + [], + [], + [], + [ + (0.5, Some(OutputSegIdx(12))), + ], + [], + [], + [], + [], + [], + ], + ), + ), +)) diff --git a/vendor/linesweeper/src/sweep/mod.rs b/vendor/linesweeper/src/sweep/mod.rs new file mode 100644 index 00000000..ab5134d4 --- /dev/null +++ b/vendor/linesweeper/src/sweep/mod.rs @@ -0,0 +1,33 @@ +//! The sweep-line implementation. +//! +//! The details of this implementation are described in the `docs` directory. +//! The main entry point is [`Sweeper`], which computes all the intersection +//! points between a collection of line segments, and makes them available +//! sweep-line by sweep-line. + +mod output_event; +mod range; +mod sweep_line; + +pub use output_event::OutputEvent; +pub use range::{SegmentsConnectedAtX, SweepLineRange, SweepLineRangeBuffers}; +pub use sweep_line::{ChangedInterval, Correctness, SweepLine, SweepLineBuffers, Sweeper}; + +use crate::Segments; + +/// Runs the sweep-line algorithm, calling the provided callback on every output point. +pub fn sweep(segments: &Segments, eps: f64, mut callback: C) { + let mut state = Sweeper::new(segments, eps); + let mut range_bufs = SweepLineRangeBuffers::default(); + let mut line_bufs = SweepLineBuffers::default(); + while let Some(mut line) = state.next_line(&mut line_bufs) { + let y = line.y(); + while let Some(mut range) = line.next_range(&mut range_bufs, segments) { + while let Some(events) = range.events() { + for ev in events { + callback(y, ev); + } + } + } + } +} diff --git a/vendor/linesweeper/src/sweep/output_event.rs b/vendor/linesweeper/src/sweep/output_event.rs new file mode 100644 index 00000000..1574406b --- /dev/null +++ b/vendor/linesweeper/src/sweep/output_event.rs @@ -0,0 +1,105 @@ +use crate::{SegIdx, num::CheapOrderedFloat}; + +/// Describes the interaction between a line segment and a sweep-line. +/// +/// In exact math, a non-horizontal line segment can interact with a sweep-line +/// in exactly one way: by intersecting it at a point. When dealing with inexact +/// math, intersections and re-orderings between line segments might force +/// our sweep-line algorithm to perturb the line segment. In that case, even +/// a non-horizontal line segment might enter and leave the sweep-line at two +/// different points. +/// +/// `OutputEvent` is ordered by the smallest horizontal coordinate where +/// it intersects the sweep-line (i.e. [`OutputEvent::smaller_x`]). +/// +/// The two points, `x0` and `x1`, are in sweep-line order. This doesn't +/// necessarily mean that `x0` is smaller than `x1`! Instead, it means that +/// when traversing the segment in sweep-line order (i.e. in increasing `y`, +/// and increasing `x` if the segment is horizontal) then it visits `x0` +/// before `x1`. +#[derive(Clone, Debug, PartialEq)] +pub struct OutputEvent { + /// The first horizontal coordinate on this sweep-line that we'd hit if + /// we were traversing the segment in sweep-line orientation. + pub x0: f64, + /// Does this line segment extend "above" (i.e. smaller `y`) this sweep-line? + /// + /// If so, it will extend up from `x0`, because that's what the "sweep-line order" + /// means. + pub connected_above: bool, + /// The last horizontal coordinate on this sweep-line that we'd hit if + /// we were traversing the segment in sweep-line orientation. + pub x1: f64, + /// Does this line segment extend "below" (i.e. larger `y`) this sweep-line? + /// + /// If so, it will extend down from `x1`, because that's what the "sweep-line order" + /// means. + pub connected_below: bool, + /// The segment that's interacting with the sweep line. + pub seg_idx: SegIdx, + /// The segment's index in the new sweep line (`None` for horizontal segments). + pub sweep_idx: Option, + /// The segment's index in the old sweep line (`None` for horizontal segments). + pub old_sweep_idx: Option, +} + +impl Eq for OutputEvent {} + +impl OutputEvent { + /// The smallest `x` coordinate at which the line segment touches the sweep-line. + pub fn smaller_x(&self) -> f64 { + self.x0.min(self.x1) + } + + /// The largest `x` coordinate at which the line segment touches the sweep-line. + pub fn larger_x(&self) -> f64 { + self.x0.max(self.x1) + } + + pub(crate) fn new( + seg_idx: SegIdx, + x0: f64, + connected_above: bool, + x1: f64, + connected_below: bool, + sweep_idx: Option, + old_sweep_idx: Option, + ) -> Self { + Self { + x0, + connected_above, + x1, + connected_below, + seg_idx, + sweep_idx, + old_sweep_idx, + } + } + + /// Does the line segment extend up from the horizontal coordinate `x`? + pub fn connected_above_at(&self, x: f64) -> bool { + x == self.x0 && self.connected_above + } + + /// Does the line segment extend down from the horizontal coordinate `x`? + pub fn connected_below_at(&self, x: f64) -> bool { + x == self.x1 && self.connected_below + } +} + +impl Ord for OutputEvent { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + CheapOrderedFloat::from(self.smaller_x()) + .cmp(&CheapOrderedFloat::from(other.smaller_x())) + .then_with(|| { + CheapOrderedFloat::from(self.larger_x()) + .cmp(&CheapOrderedFloat::from(other.larger_x())) + }) + } +} + +impl PartialOrd for OutputEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/vendor/linesweeper/src/sweep/range.rs b/vendor/linesweeper/src/sweep/range.rs new file mode 100644 index 00000000..60c7ff25 --- /dev/null +++ b/vendor/linesweeper/src/sweep/range.rs @@ -0,0 +1,445 @@ +use crate::{SegIdx, num::CheapOrderedFloat}; + +use super::{ChangedInterval, OutputEvent, SweepLine}; + +/// A horizontal fragment. +/// +/// Represents a horizontal part of a segment, which could be either an actual +/// horizontal segment or a little horizontal connector of a segment in a +/// sweep-line. +#[derive(Clone, Debug, PartialEq)] +struct HFrag { + /// The segment this horizontal fragment is a part of. + pub seg: SegIdx, + /// The first (smallest) horizontal position of this fragment. + pub start: f64, + /// The last (largest) horizontal position of this fragment. + pub end: f64, + /// Does this segment continue out of the sweep-line at `start`? + /// + /// For a horizontal segment, this will always be false. On its own, + /// this doesn't tell you whether the segment points up or down at + /// `start`; for that, see `enter_first`. + pub connected_at_start: bool, + /// Does this segment continue out of the sweep-line at `end`? + /// + /// For a horizontal segment, this will always be false. + pub connected_at_end: bool, + /// When traversing the segment in sweep-line order, does it visit + /// `start` first? + /// + /// For example: + /// + /// ```text + /// s_1 s_2 s_3 + /// ╲ ╱ ╲ + /// ╲ ╱ ╲ + /// ─ ─ ─ + /// ╲ ╱ ╲ + /// ╲ ╱ ╲ + /// ``` + /// + /// When moving from top to bottom (i.e. sweep-line order), s_1 and s_2 + /// visit the larger horizontal position of the fragment before the smaller + /// one, so `enter_first` is false. On the other hand, s_3 has `enter_first` + /// as true. + pub enter_first: bool, + /// The position of the segment in the current sweep-line. + /// + /// This will be `None` if, and only if, the segment is horizontal. + pub sweep_idx: Option, + /// The position of the segment in the old sweep-line. + /// + /// This will be `None` if, and only if, the segment is horizontal. + pub old_sweep_idx: Option, +} + +impl HFrag { + /// Given a segment's interaction with the sweep-line, returns the corresponding + /// horizontal fragment if there is one. + pub fn from_position(pos: OutputEvent) -> Option { + let OutputEvent { + x0, + connected_above, + x1, + connected_below, + .. + } = pos; + + if x0 == x1 { + return None; + } + + let enter_first = x0 < x1; + let (start, end, connected_at_start, connected_at_end) = if enter_first { + (x0, x1, connected_above, connected_below) + } else { + (x1, x0, connected_below, connected_above) + }; + Some(HFrag { + end, + start, + enter_first, + seg: pos.seg_idx, + connected_at_start, + connected_at_end, + sweep_idx: pos.sweep_idx, + old_sweep_idx: pos.old_sweep_idx, + }) + } + + /// Does this horizontal fragment's segment stick up from the sweep-line at `x`? + pub fn connected_above_at(&self, x: f64) -> bool { + (x == self.start && self.enter_first && self.connected_at_start) + || (x == self.end && !self.enter_first && self.connected_at_end) + } + + /// Does this horizontal fragment's segment stick down from the sweep-line at `x`? + pub fn connected_below_at(&self, x: f64) -> bool { + (x == self.start && !self.enter_first && self.connected_at_start) + || (x == self.end && self.enter_first && self.connected_at_end) + } +} + +/// Emits output events for a single sub-range of a single sweep-line. +/// +/// This is constructed using [`SweepLine::next_range`]. By repeatedly +/// calling `SweepLineRange::increase_x` you can iterate over all +/// interesting horizontal positions, left to right (i.e. smaller `x` to larger +/// `x`). +#[derive(Debug)] +pub struct SweepLineRange<'bufs, 'state, 'segs> { + last_x: Option, + line: &'state SweepLine<'state, 'state, 'segs>, + bufs: &'bufs mut SweepLineRangeBuffers, + changed_interval: ChangedInterval, + output_events: &'state [OutputEvent], +} + +impl<'bufs, 'state, 'segs> SweepLineRange<'bufs, 'state, 'segs> { + pub(crate) fn new( + line: &'state SweepLine<'state, 'state, 'segs>, + output_events: &'state [OutputEvent], + bufs: &'bufs mut SweepLineRangeBuffers, + changed_interval: ChangedInterval, + ) -> Self { + let ret = Self { + last_x: None, + output_events, + changed_interval, + line, + bufs, + }; + + #[cfg(feature = "slow-asserts")] + ret.check_invariants(); + + ret + } + + fn output_events(&self) -> &[OutputEvent] { + self.output_events + } + + /// The current horizontal position, or `None` if we're finished. + pub fn x(&self) -> Option { + match ( + self.bufs.active_horizontals.first(), + self.output_events().first(), + ) { + (None, None) => None, + (None, Some(pos)) => Some(pos.smaller_x()), + (Some(h), None) => Some(h.end), + (Some(h), Some(pos)) => Some((h.end).min(pos.smaller_x())), + } + } + + fn positions_at_x<'c, 'b: 'c>(&'b self, x: f64) -> impl Iterator + 'c { + self.output_events() + .iter() + .take_while(move |p| p.smaller_x() == x) + } + + /// Updates a [`SegmentsConnectedAtX`] to reflect the current horizontal position. + pub fn update_segments_at_x(&self, segs: &mut SegmentsConnectedAtX) { + segs.connected_up.clear(); + segs.connected_down.clear(); + + let Some(x) = self.x() else { + return; + }; + + for hseg in &self.bufs.active_horizontals { + if hseg.connected_above_at(x) { + segs.connected_up + .push((hseg.seg, hseg.old_sweep_idx.unwrap())); + } + + if hseg.connected_below_at(x) { + segs.connected_down + .push((hseg.seg, hseg.sweep_idx.unwrap())); + } + } + + for pos in self.positions_at_x(x) { + if pos.connected_above_at(x) { + segs.connected_up + .push((pos.seg_idx, pos.old_sweep_idx.unwrap())); + } + + if pos.connected_below_at(x) { + segs.connected_down + .push((pos.seg_idx, pos.sweep_idx.unwrap())); + } + } + + segs.connected_up + .sort_by_key(|(_seg_idx, sweep_idx)| *sweep_idx); + segs.connected_down + .sort_by_key(|(_seg_idx, sweep_idx)| *sweep_idx); + } + + /// Iterates over the horizontal segments that are active at the current position. + /// + /// This includes the segments that end here, but does not include the ones + /// that start here. + pub fn active_horizontals(&self) -> impl Iterator + '_ { + self.bufs.active_horizontals.iter().map(|hseg| hseg.seg) + } + + /// Iterates over the horizontal segments that are active at the current position, along + /// with a boolean telling you whether the horizontal segment has the same orientation + /// as the segment that it belongs to. + /// + /// This includes the segments that end here, but does not include the ones + /// that start here. + pub fn active_horizontals_and_orientations(&self) -> impl Iterator + '_ { + self.bufs + .active_horizontals + .iter() + .map(|hseg| (hseg.seg, hseg.enter_first)) + } + + /// Returns the collection of all output events that end at the current + /// position, or `None` if this batcher is finished. + /// + /// All the returned events start at the previous `x` position and end + /// at the current `x` position. In particular, if you alternate between + /// calling [`SweepLineRange::increase_x`] and this method, you'll + /// receive non-overlapping batches of output events. + pub fn events(&mut self) -> Option> { + let next_x = self.x()?; + + let mut ret = Vec::new(); + for h in &self.bufs.active_horizontals { + // unwrap: on the first event of this sweep line, active_horizontals is empty. So + // we only get here after last_x is populated. + let x0 = self.last_x.unwrap(); + let x1 = next_x.min(h.end); + let connected_end = x1 == h.end && h.connected_at_end; + let connected_start = x0 == h.start && h.connected_at_start; + if h.enter_first { + ret.push(OutputEvent::new( + h.seg, + x0, + connected_start, + x1, + connected_end, + h.sweep_idx, + h.old_sweep_idx, + )); + } else { + ret.push(OutputEvent::new( + h.seg, + x1, + connected_end, + x0, + connected_start, + h.sweep_idx, + h.old_sweep_idx, + )); + } + } + + // Drain the active horizontals, throwing away horizontal segments that end before + // the new x position. + self.drain_active_horizontals(next_x); + + // Move along to the next horizontal position, processing the x events at the current + // position and either emitting them immediately or saving them as horizontals. + while let Some(ev) = self.output_events.first() { + if ev.smaller_x() > next_x { + break; + } + self.output_events = &self.output_events[1..]; + + if ev.x0 == ev.x1 { + // We push output event for points immediately. + ret.push(ev.clone()); + } else if let Some(hseg) = HFrag::from_position(ev.clone()) { + // For horizontal segments, we don't output anything straight + // away. When we update the horizontal position and visit our + // horizontal segments, we'll output something. + self.bufs.active_horizontals.push(hseg); + } + } + self.bufs + .active_horizontals + .sort_by_key(|a| CheapOrderedFloat::from(a.end)); + self.last_x = Some(next_x); + Some(ret) + } + + /// Move along to the next horizontal position. + pub fn increase_x(&mut self) { + if let Some(x) = self.x() { + self.drain_active_horizontals(x); + + while let Some(ev) = self.output_events.first() { + if ev.smaller_x() > x { + break; + } + self.output_events = &self.output_events[1..]; + + if let Some(hseg) = HFrag::from_position(ev.clone()) { + self.bufs.active_horizontals.push(hseg); + } + } + } + self.bufs + .active_horizontals + .sort_by_key(|a| CheapOrderedFloat::from(a.end)); + } + + fn drain_active_horizontals(&mut self, x: f64) { + let new_start = self + .bufs + .active_horizontals + .iter() + .position(|h| h.end > x) + .unwrap_or(self.bufs.active_horizontals.len()); + self.bufs.active_horizontals.drain(..new_start); + } + + /// The indices within the sweep line represented by this range. + pub fn seg_range(&self) -> ChangedInterval { + self.changed_interval.clone() + } + + /// Returns an iterator over the segments in this range, ordered according + /// to the "old" sweep-line. + /// + /// In addition to appearing in a different order, the set of segments returned + /// by this method and [`Self::segment_range`] may differ: segments that exit at the + /// current sweep line will be returned here and not there, while segments that + /// enter at the current sweep line will be returned there and not here. + pub fn old_segment_range(&self) -> impl Iterator + '_ { + let range = self.changed_interval.segs.clone(); + self.line().old_segment_range(range) + } + + /// Returns an iterator over the segments in this range, ordered according + /// to the "new" sweep-line. + /// + /// In addition to appearing in a different order, the set of segments returned + /// by this method and [`Self::old_segment_range`] may differ: segments that exit at the + /// current sweep line will be returned there and not here, while segments that + /// enter at the current sweep line will be returned here and not there. + pub fn segment_range(&self) -> impl Iterator + '_ { + let range = self.changed_interval.segs.clone(); + self.line().segment_range(range) + } + + /// The sweep line that this is a range of. + pub fn line(&self) -> &SweepLine<'_, '_, 'segs> { + self.line + } + + #[cfg(feature = "slow-asserts")] + fn check_invariants(&self) { + for ev in self.output_events() { + let seg = &self.line.segments()[ev.seg_idx]; + let y = self.line.y(); + let eps = self.line.eps(); + + // The curve comparison is guaranteed to give a strong + // ordering when the curves are 1.5 eps apart in ell_infty. + // That means the perturbed points should always be within + // 1.5 * sqrt(2) eps in ell_2, which is less than 2.5 eps. We + // compute distance to accuracy 0.5 eps, so we'll assert + // that our number is less than 3 eps. + + // Ok, this doesn't work because of https://github.com/linebender/kurbo/issues/446 + // For now, we'll use ell_infty comparisons. + // + // let n0 = seg.nearest(p0, eps / 2.0); + // let n1 = seg.nearest(p1, eps / 2.0); + + // assert!(n0.distance_sq <= eps * eps * 9.0); + // assert!(n1.distance_sq <= eps * eps * 9.0); + + let (lower, upper) = if seg.is_horizontal() { + (seg.start().x - 2.0 * eps, seg.end().x + 2.0 * eps) + } else { + (seg.lower(y, 2.0 * eps), seg.upper(y, 2.0 * eps)) + }; + assert!(ev.smaller_x() <= upper && lower <= ev.larger_x()); + } + } +} + +/// A re-usable struct for collecting segments at a single position on a sweep-line. +/// +/// See [`SweepLineRange::update_segments_at_x`] for where this is used. At +/// any given time, this struct is implicitly associated to a single horizontal +/// position: the position of the `SweepLineRange` last time we were updated. +#[derive(Debug, Default)] +pub struct SegmentsConnectedAtX { + connected_up: Vec<(SegIdx, usize)>, + connected_down: Vec<(SegIdx, usize)>, +} + +impl SegmentsConnectedAtX { + /// The segments that are connected up to a previous sweep-line at the + /// current horizontal position. + /// + /// The returned iterator is sorted by the old sweep-line order. In other + /// words, it will return segments clockwise when viewed from the current + /// position. + pub fn connected_up(&self) -> impl Iterator + '_ { + self.connected_up.iter().map(|x| x.0) + } + + /// The segments that are connected down to a subsequent sweep-line at the + /// current horizontal position. + /// + /// The returned iterator is sorted by the new sweep-line order. In other + /// words, it will return segments counter-clockwise when viewed from the current + /// position. + pub fn connected_down(&self) -> impl Iterator + '_ { + self.connected_down.iter().map(|x| x.0) + } +} + +/// Holds some buffers that are used when iterating over a sweep-line. +/// +/// Save on re-allocation by allocating this once and reusing it in multiple calls to +/// [`SweepLine::next_range`]. +#[derive(Clone, Debug, Default)] +pub struct SweepLineRangeBuffers { + /// All the horizontal segments overlapping with the sweep-line-range's current + /// horizontal position, ordered by ending position. + /// + /// We could keep this in an ordered data structure, but it turns out to + /// be faster to just sort it regularly: every time we modify this, we also + /// advance the horizontal position and iterate over this entire collection. + /// Therefore, there's no asymptotic run-time to be gained by having a fast + /// way to insert/delete a single element. + active_horizontals: Vec, +} + +impl SweepLineRangeBuffers { + pub(crate) fn clear(&mut self) { + self.active_horizontals.clear(); + } +} diff --git a/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__conservative_line_order.snap b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__conservative_line_order.snap new file mode 100644 index 00000000..2c6530af --- /dev/null +++ b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__conservative_line_order.snap @@ -0,0 +1,136 @@ +--- +source: src/sweep/sweep_line.rs +expression: "snapshot_outputs(segs, 0.25)" +--- +[ + Output( + y: 0.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: false, + enter: true, + lower_bound: -1.25, + upper_bound: 0.8500000000000001, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: true, + lower_bound: -0.25, + upper_bound: 0.25, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(2), + exit: false, + enter: true, + lower_bound: 0.04999999999999999, + upper_bound: 0.55, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 3, + ), + horizontals: None, + ), + ], + ), + Output( + y: 0.5, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: false, + enter: false, + lower_bound: -1.25, + upper_bound: 0.8500000000000001, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: -0.25, + upper_bound: 0.25, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(2), + exit: false, + enter: false, + lower_bound: 0.04999999999999999, + upper_bound: 0.55, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: true, + enter: false, + lower_bound: -1.25, + upper_bound: 0.8500000000000001, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: true, + enter: false, + lower_bound: -0.25, + upper_bound: 0.25, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(2), + exit: true, + enter: false, + lower_bound: 0.04999999999999999, + upper_bound: 0.55, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 3, + ), + horizontals: None, + ), + ], + ), +] diff --git a/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__regression_position_state.snap b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__regression_position_state.snap new file mode 100644 index 00000000..e941b664 --- /dev/null +++ b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__regression_position_state.snap @@ -0,0 +1,361 @@ +--- +source: src/sweep/sweep_line.rs +expression: "snapshot_outputs(segs, 0.1)" +--- +[ + Output( + y: -1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(5), + exit: false, + enter: true, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: true, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: true, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(2), + exit: false, + enter: true, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: true, + old_idx: Some(3), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 4, + ), + horizontals: None, + ), + ], + ), + Output( + y: -0.00000000000000005551115123125783, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(5), + exit: false, + enter: false, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(2), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 0.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: true, + enter: true, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(0), + old_seg: Some(SegIdx(5)), + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: true, + enter: true, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: true, + old_idx: Some(3), + old_seg: Some(SegIdx(2)), + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 1, + ), + horizontals: None, + ), + ChangedInterval( + segs: Range( + start: 3, + end: 4, + ), + horizontals: None, + ), + ], + ), + Output( + y: 0.95, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: false, + enter: false, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 0.9749999999999999, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: false, + enter: false, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 0.9999999999999999, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(0), + exit: true, + enter: false, + lower_bound: -0.1, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: true, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: true, + old_idx: Some(3), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 4, + ), + horizontals: None, + ), + ], + ), + Output( + y: 1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(4), + exit: true, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: true, + enter: false, + lower_bound: 0.9, + upper_bound: 2.1, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: None, + ), + ], + ), +] diff --git a/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square.snap b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square.snap new file mode 100644 index 00000000..6c53bd17 --- /dev/null +++ b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square.snap @@ -0,0 +1,84 @@ +--- +source: src/sweep/sweep_line.rs +expression: "snapshot_outputs(segs, 0.01)" +--- +[ + Output( + y: 0.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: true, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: true, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(3), + exit: true, + enter: false, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: true, + enter: false, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), +] diff --git a/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square_and_diamond.snap b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square_and_diamond.snap new file mode 100644 index 00000000..4e71932a --- /dev/null +++ b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__square_and_diamond.snap @@ -0,0 +1,292 @@ +--- +source: src/sweep/sweep_line.rs +expression: "snapshot_outputs(segs, eps)" +--- +[ + Output( + y: 0.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: true, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(7), + exit: false, + enter: true, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: true, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: true, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(3), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 4, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 0.005, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(7), + exit: false, + enter: false, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 3, + ), + horizontals: None, + ), + ], + ), + Output( + y: 0.995, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(7), + exit: false, + enter: false, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(4), + exit: false, + enter: false, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(6), + exit: true, + enter: true, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: Some(SegIdx(7)), + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: true, + enter: false, + lower_bound: -0.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: true, + enter: true, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(2), + old_seg: Some(SegIdx(4)), + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: true, + enter: false, + lower_bound: 0.99, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(3), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 4, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 1.9975, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(6), + exit: false, + enter: false, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: false, + enter: false, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [], + ), + Output( + y: 2.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(6), + exit: true, + enter: false, + lower_bound: -1.01, + upper_bound: 0.01, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: true, + enter: false, + lower_bound: -0.01, + upper_bound: 1.01, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: None, + ), + ], + ), +] diff --git a/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__two_squares.snap b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__two_squares.snap new file mode 100644 index 00000000..ae3c6fdc --- /dev/null +++ b/vendor/linesweeper/src/sweep/snapshots/linesweeper__sweep__sweep_line__tests__two_squares.snap @@ -0,0 +1,202 @@ +--- +source: src/sweep/sweep_line.rs +expression: "snapshot_outputs(segs, 0.1)" +--- +[ + Output( + y: -0.5, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(7), + exit: false, + enter: true, + lower_bound: -0.6, + upper_bound: -0.4, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: false, + enter: true, + lower_bound: 0.4, + upper_bound: 0.6, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 0.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(7), + exit: false, + enter: false, + lower_bound: -0.6, + upper_bound: -0.4, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: true, + lower_bound: -0.1, + upper_bound: 0.1, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: false, + enter: false, + lower_bound: 0.4, + upper_bound: 0.6, + in_changed_interval: false, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: true, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(3), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 1, + end: 4, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 0.5, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(7), + exit: true, + enter: false, + lower_bound: -0.6, + upper_bound: -0.4, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(3), + exit: false, + enter: false, + lower_bound: -0.1, + upper_bound: 0.1, + in_changed_interval: false, + old_idx: Some(1), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(5), + exit: true, + enter: false, + lower_bound: 0.4, + upper_bound: 0.6, + in_changed_interval: true, + old_idx: Some(2), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: false, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: false, + old_idx: None, + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 3, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), + Output( + y: 1.0, + order: SegmentOrder( + segs: [ + SegmentOrderEntry( + seg: SegIdx(3), + exit: true, + enter: false, + lower_bound: -0.1, + upper_bound: 0.1, + in_changed_interval: true, + old_idx: Some(0), + old_seg: None, + ), + SegmentOrderEntry( + seg: SegIdx(1), + exit: true, + enter: false, + lower_bound: 0.9, + upper_bound: 1.1, + in_changed_interval: true, + old_idx: Some(1), + old_seg: None, + ), + ], + ), + changed: [ + ChangedInterval( + segs: Range( + start: 0, + end: 2, + ), + horizontals: Some(Range( + start: 0, + end: 1, + )), + ), + ], + ), +] diff --git a/vendor/linesweeper/src/sweep/sweep_line.rs b/vendor/linesweeper/src/sweep/sweep_line.rs new file mode 100644 index 00000000..b4b1883e --- /dev/null +++ b/vendor/linesweeper/src/sweep/sweep_line.rs @@ -0,0 +1,2102 @@ +//! A sweep-line implementation using weak orderings. +//! +//! This algorithm is documented in `docs/sweep.typ`. + +use std::cell::{RefCell, RefMut}; + +#[cfg(feature = "slow-asserts")] +use std::ops::DerefMut; + +use crate::{ + curve::{self, CurveInteraction, CurveOrder, Order}, + geom::Segment, + num::CheapOrderedFloat, + order::ComparisonCache, + segments::{SegIdx, Segments}, + treevec::TreeVec, +}; + +use super::{OutputEvent, SweepLineRange, SweepLineRangeBuffers}; + +/// Because we associate curve touches and crossings as ranges of +/// heights (and not just a single height), we have some choice as to +/// where we actually handle the crossing. In the write-up, there are +/// a few requirements for the intersection event depending on the +/// context where it was calculated (e.g. whether it was for an +/// intersection event or an exit event). +/// +/// In the actual code, we're a little less careful about the strict +/// requirements than the write-up. But we do need to distinguish a +/// couple of cases, at least. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum WhichTouch { + /// Choose a touch height that's sort of in the middle of the + /// allowed range. + Mid, + /// Choose a touch height that's sort of at the end of the allowed + /// range. + Late, +} + +/// How hard we should try to make the output actually correct. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(test, derive(serde::Serialize))] +pub enum Correctness { + /// The output contours are only guaranteed to be approximately + /// correctly ordered. If you evaluate them with exact math, you + /// may find, for example, that two sets we claim to be disjoint + /// in fact have a small overlap. The size of this hypothetical + /// overlap is bounded by the tolerance parameter. + /// + /// This is usually the right choice, especially for graphics + /// applications. + GoodEnough, + /// The output contours are guaranteed to be correctly ordered: if + /// you evaluate the output contours using exact math, you'll find + /// that that satisfy the claimed disjointness and containment + /// properties. This sounds like a good thing, but it comes at a + /// substantial performance cost, and it usually produces output + /// contours with many more subdivisions than necessary. + ReallyCorrect, +} + +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy)] +pub(crate) struct SegmentOrderEntry { + pub(crate) seg: SegIdx, + /// True if this segment is about to leave the sweep-line. + /// + /// We handle enter/exits like this: + /// + /// 1. insert the newly entered segments + /// 2. mark the about-to-exit segments + /// 3. process intersections and re-shuffle things + /// 4. output the geometry + /// 5. remove the exited segments that we marked in step 2. + /// + /// The reason we don't remove about-to-exit segments immediately in + /// step 2 is to make it easier to compare the old and new orderings. + /// We keep track of the re-shuffling in step 3 (see `old_idx` below), + /// and the lack of deletions means that we don't need to worry about + /// indices shifting. + exit: bool, + enter: bool, + /// This is epsilon below this segment's smallest horizontal position. All + /// horizontal positions smaller than this are guaranteed not to interact + /// with this segment. + lower_bound: f64, + /// This is epsilon above this segment's largest horizontal position. All + /// horizontal positions larger than this are guaranteed not to interact + /// with this segment. + upper_bound: f64, + /// This is filled out during `compute_changed_intervals`, where we use it as + /// a sort of "dirty" flag to avoid processing an entry twice. + in_changed_interval: bool, + /// We need to keep track of two sweep-line orders at once: the previous one + /// and the current one. And if a large sweep-line has only one small change, + /// we want the cost of this tracking to be small. We do this by keeping the + /// sweep line in the "current" order, and then whenever some segments have + /// their order changed, we remember their old positions. + /// + /// So, for example, say we're in a situation like this, where the dashed + /// horizontal line is the position of the sweep: + /// + /// ```text + /// s_1 s_3 s_5 s_7 + /// │ ╲ ╱ ╲ + /// │ ╲ ╱ ╲ + /// ╌│╌╌╌╌╌╌╌╳╌╌╌╌╌╌╌╌╌╲ + /// │ ╱ ╲ ╲ + /// ``` + /// + /// The old order is [s_1, s_3, s_5, s_7], and we start off with all the old_idx + /// fields set to `None`. Then we swap `s_3` and `s_5`, so the current order + /// is [s_1, s_5, s_3, s_7] and we set the old_idx fields to be + /// [None, Some(2), Some(1), None]. This allows us to reconstruct the original + /// order when we need to. + old_idx: Option, + /// When two segments are contour-adjacent, we allow them to "share" the same + /// sweep-line slot. This helps performance (because we aren't constantly + /// removing and inserting segments in the middle of the sweep-line), and makes + /// it easier to generate good output (because if we have both contour-adjacent + /// segments handy, it's easy to avoid unnecessary horizontal segments). + /// + /// So in a situation like this, for example: + /// + /// ```text + /// s_1 s_3 s_7 + /// │ ╲ ╲ + /// │ ╲ ╲ + /// │ ╱ ╲ + /// │ ╱ ╲ + /// s_5 + /// ``` + /// + /// when the sweep-line hits the "kink" the new order will be [s_1, s_5, s_7] + /// and the old_seg values will be [None, Some(s_3), None]. + /// + /// Note that `old_seg` is not guaranteed to get used for all contour-adjacent + /// segments, even if they're monotonic in y: if there are some other annoying + /// segments nearby, the new segment and the old segment might get separated + /// in the sweep-line. In this case, they will get their own entries. + /// + /// If this is `Some`, both `enter` and `exit` will be true. + old_seg: Option, +} + +impl std::fmt::Debug for SegmentOrderEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO: maybe a verbose mode to show all the state? + if let Some(old_seg) = self.old_seg { + write!(f, "{old_seg:?}->{:?}", self.seg) + } else { + write!(f, "{:?}", self.seg) + } + } +} + +impl SegmentOrderEntry { + fn new(seg: SegIdx, segments: &Segments, eps: f64) -> Self { + let x0 = segments[seg].min_x(); + let x1 = segments[seg].max_x(); + Self { + seg, + exit: false, + enter: false, + lower_bound: x0.min(x1) - eps, + upper_bound: x0.max(x1) + eps, + in_changed_interval: false, + old_idx: None, + old_seg: None, + } + } + + fn reset_state(&mut self) { + self.exit = false; + self.enter = false; + self.in_changed_interval = false; + self.old_idx = None; + self.old_seg = None; + } + + fn set_old_idx_if_unset(&mut self, i: usize) { + if self.old_idx.is_none() { + self.old_idx = Some(i); + } + } + + fn old_seg(&self) -> SegIdx { + self.old_seg.unwrap_or(self.seg) + } + + pub(crate) fn is_in_changed_interval(&self) -> bool { + self.in_changed_interval + } + + pub(crate) fn will_really_exit(&self) -> bool { + self.exit && self.old_seg.is_none() + } +} + +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Default)] +pub(crate) struct SegmentOrder { + pub(crate) segs: TreeVec, +} + +impl std::fmt::Debug for SegmentOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.segs.iter()).finish() + } +} + +impl SegmentOrder { + fn seg(&self, i: usize) -> SegIdx { + self.segs[i].seg + } + + #[cfg(feature = "slow-asserts")] + fn is_exit(&self, i: usize) -> bool { + let seg = &self.segs[i]; + seg.will_really_exit() + } +} + +#[derive(Clone, Debug, PartialEq)] +struct IntersectionEvent { + pub y: f64, + /// This segment used to be to the left, and after the intersection it will be to the right. + /// + /// In our sweep line intersection, this segment might have already been moved to the right by + /// some other constraints. That's ok. + pub left: SegIdx, + /// This segment used to be to the right, and after the intersection it will be to the left. + pub right: SegIdx, +} + +impl Eq for IntersectionEvent {} + +impl Ord for IntersectionEvent { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (CheapOrderedFloat::from(self.y), self.left, self.right).cmp(&( + CheapOrderedFloat::from(other.y), + other.left, + other.right, + )) + } +} + +impl PartialOrd for IntersectionEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Clone, Debug, Default)] +struct EventQueue { + /// The enter events are stored in `Segments`; this is the index of the first + /// one that we haven't processed yet. + next_enter_idx: usize, + /// The index of the first exit event that we haven't processed yet. + next_exit_idx: usize, + intersection: std::collections::BTreeSet, +} + +impl EventQueue { + pub fn push(&mut self, ev: IntersectionEvent) { + self.intersection.insert(ev); + } + + pub fn next_y<'a>(&'a self, segments: &'a Segments) -> Option { + let enter_y = segments + .entrances() + .get(self.next_enter_idx) + .map(|(y, _)| *y); + let exit_y = segments.exits().get(self.next_exit_idx).map(|(y, _)| *y); + let int_y = self.intersection.first().map(|i| i.y); + + [enter_y, exit_y, int_y] + .into_iter() + .flatten() + .min_by_key(|y| CheapOrderedFloat::from(*y)) + } + + pub fn entrances_at_y<'a>(&mut self, y: &f64, segments: &'a Segments) -> &'a [(f64, SegIdx)] { + let entrances = &segments.entrances()[self.next_enter_idx..]; + let count = entrances + .iter() + .position(|(enter_y, _)| enter_y > y) + .unwrap_or(entrances.len()); + self.next_enter_idx += count; + &entrances[..count] + } + + pub fn exits_at_y<'a>(&mut self, y: &f64, segments: &'a Segments) -> &'a [(f64, SegIdx)] { + let exits = &segments.exits()[self.next_exit_idx..]; + let count = exits + .iter() + .position(|(exit_y, _)| exit_y > y) + .unwrap_or(exits.len()); + self.next_exit_idx += count; + &exits[..count] + } + + pub fn next_intersection_at_y(&mut self, y: f64) -> Option { + if self.intersection.first().map(|i| i.y) == Some(y) { + self.intersection.pop_first() + } else { + None + } + } +} + +/// Holds some buffers that are used when iterating over a sweep-line. +/// +/// Save on re-allocation by allocating this once and reusing it in multiple calls to +/// [`Sweeper::next_line`]. +#[derive(Clone, Debug, Default)] +pub struct SweepLineBuffers { + /// A subset of the old sweep-line. + old_line: Vec, + /// A vector of (segment, min allowable horizontal position, max allowable horizontal position). + positions: Vec<(SegmentOrderEntry, f64, f64)>, + output_events: Vec, +} + +/// Encapsulates the state of the sweep-line algorithm and allows iterating over sweep lines. +#[derive(Clone, Debug)] +pub struct Sweeper<'a> { + y: f64, + eps: f64, + correctness: Correctness, + pub(crate) line: SegmentOrder, + events: EventQueue, + segments: &'a Segments, + + horizontals: Vec, + + // The collection of segments that we know need to be given explicit + // positions in the current sweep line. + // + // These include: + // - any segments that changed order with any other segments + // - any segments that entered or exited + // + // These segments are identified by their index in the current order, so that + // it's fast to find them. It means that we need to do some fixing-up of indices after + // inserting all the new segments. + segs_needing_positions: Vec, + changed_intervals: Vec, + + comparisons: RefCell, + conservative_comparisons: RefCell, +} + +impl<'segs> Sweeper<'segs> { + /// Creates a new sweeper for a collection of segments, and with a + /// given tolerance. + /// + /// The really correct mode doesn't actually change any of the + /// output orders, but it does emit extra events whenever lines + /// touch without crossing. + pub fn new_with_correctness( + segments: &'segs Segments, + eps: f64, + correctness: Correctness, + ) -> Self { + let events = EventQueue::default(); + Sweeper { + eps, + line: SegmentOrder::default(), + correctness, + y: events.next_y(segments).unwrap_or(f64::INFINITY), + events, + segments, + segs_needing_positions: Vec::new(), + changed_intervals: Vec::new(), + horizontals: Vec::new(), + comparisons: RefCell::new(ComparisonCache::new(eps, eps / 2.0)), + conservative_comparisons: RefCell::new(ComparisonCache::new(4.0 * eps, eps / 2.0)), + } + } + + /// Creates a new sweeper for a collection of segments, and with a + /// given tolerance. + pub fn new(segments: &'segs Segments, eps: f64) -> Self { + Self::new_with_correctness(segments, eps, Correctness::GoodEnough) + } + + fn compare_segments(&self, i: SegIdx, j: SegIdx) -> RefMut<'_, CurveOrder> { + RefMut::map(self.comparisons.borrow_mut(), |c| { + c.compare_segments(self.segments, i, j) + }) + } + + fn compare_segments_conservatively(&self, i: SegIdx, j: SegIdx) -> RefMut<'_, CurveOrder> { + RefMut::map(self.conservative_comparisons.borrow_mut(), |c| { + c.compare_segments(self.segments, i, j) + }) + } + + /// Moves the sweep forward, returning the next sweep line. + /// + /// Returns `None` when sweeping is complete. + pub fn next_line<'slf, 'buf>( + &'slf mut self, + bufs: &'buf mut SweepLineBuffers, + ) -> Option> { + self.check_invariants(); + + let y = self.events.next_y(self.segments)?; + debug_assert!(y >= self.y); + self.advance(y); + self.check_invariants(); + + // Process all the enter events at this y. + { + let enters = self.events.entrances_at_y(&y, self.segments); + for (enter_y, idx) in enters { + debug_assert_eq!(enter_y, &y); + self.handle_enter(*idx); + self.check_invariants(); + } + } + + // Process all the exit events. + { + let exits = self.events.exits_at_y(&y, self.segments); + for (exit_y, idx) in exits { + debug_assert_eq!(exit_y, &y); + self.handle_exit(*idx); + self.check_invariants(); + } + } + + // Process all the intersection events at this y. + while let Some(intersection) = self.events.next_intersection_at_y(y) { + self.handle_intersection(intersection.left, intersection.right); + self.check_invariants(); + } + + self.compute_changed_intervals(); + Some(SweepLine { + state: self, + next_changed_interval: 0, + bufs, + }) + } + + fn advance(&mut self, y: f64) { + // All the exiting segments should be in segs_needing_positions, so find them all and remove them. + self.segs_needing_positions + .retain(|idx| self.line.segs[*idx].exit && self.line.segs[*idx].old_seg.is_none()); + + // Reset the state flags for all segments. All segments with non-trivial state flags should + // belong to the changed intervals. This needs to go before we remove the exiting segments, + // because that messes up the indices. + for r in self.changed_intervals.drain(..) { + for seg in self.line.segs.range_mut(r.segs) { + seg.reset_state(); + } + } + + // Remove the exiting segments in reverse order, so the indices stay good. + self.segs_needing_positions.sort(); + self.segs_needing_positions.dedup(); + for &idx in self.segs_needing_positions.iter().rev() { + self.line.segs.remove(idx); + } + self.y = y; + self.segs_needing_positions.clear(); + self.horizontals.clear(); + } + + fn intersection_scan_right(&mut self, start_idx: usize, which: WhichTouch) { + let seg_idx = self.line.seg(start_idx); + let y = self.y; + + // We're allowed to take a potentially-smaller height bound by taking + // into account the current queue. A larger height bound is still ok, + // just a little slower. + let seg = &self.segments[seg_idx]; + // Looking explicitly at the ending `y` coordinate is more reliable than + // checking `is_exit`, because this method can get called before exits + // get processed. (TODO: do we actually need to store exit and enter + // as separate states? Can't we just always look at the position?) + if seg.end().y == y { + return; + } + let mut height_bound = seg.end().y; + + for j in (start_idx + 1)..self.line.segs.len() { + let other_idx = self.line.seg(j); + let other = &self.segments[other_idx]; + if other.end().y == y { + continue; + } + + // Our basic comparisons use eps tolerance and eps/2 accuracy, so if + // our max_x is 1.5 eps less than their min_x then we're guaranteed + // to always compare Order::Left of them. + if seg.max_x() + 2.0 * self.eps < other.min_x() { + break; + } + height_bound = height_bound.min(other.end().y); + + // TODO: there's a choice to be made here: do we distinguish in the + // event queue between actual intersections and near-touches? For + // now, no. We will distinguish them in handle_intersection + + let touch = self + .compare_segments(seg_idx, other_idx) + .next_touch_after(y); + if let Some(int_y) = touch.and_then(|touch| choose_cross_height(touch, y, which)) { + let (left_idx, right_idx) = match touch { + Some(curve::CurveInteraction::Touch(..)) => (j, start_idx), + _ => (start_idx, j), + }; + // We only keep intersections that happen before the end + // of a segment. This is allowed because the ordering + // invariants are closed and so they won't be violated right + // at the end. And keeping out intersections-at-the-end + // out works better with the boundary conditions of + // [CurveOrder::entry_at]. + if int_y < seg.end().y && int_y < other.end().y { + self.events.push(IntersectionEvent { + y: int_y, + left: self.line.seg(left_idx), + right: self.line.seg(right_idx), + }); + height_bound = height_bound.min(int_y); + } + } + + let cmp = self.compare_segments_conservatively(seg_idx, other_idx); + if let Some((_y0, y1, order)) = cmp.iter().find(|(_, y1, _)| *y1 > y) + && order == Order::Left + && y1 >= height_bound + { + break; + } + } + } + + // This is basically the same as `intersection_scan_right`, so maybe it's worth combining them. + fn intersection_scan_left(&mut self, start_idx: usize, which: WhichTouch) { + let seg_idx = self.line.seg(start_idx); + let y = self.y; + + let seg = &self.segments[seg_idx]; + if seg.end().y == y { + return; + } + let mut height_bound = seg.end().y; + + for j in (0..start_idx).rev() { + let other_idx = self.line.seg(j); + let other = &self.segments[other_idx]; + if other.end().y == y { + continue; + } + + if seg.min_x() - 2.0 * self.eps > other.max_x() { + break; + } + height_bound = height_bound.min(other.end().y); + + let touch = self + .compare_segments(other_idx, seg_idx) + .next_touch_after(y); + if let Some(int_y) = touch.and_then(|touch| choose_cross_height(touch, y, which)) { + let (left_idx, right_idx) = match touch { + Some(curve::CurveInteraction::Touch(..)) => (start_idx, j), + _ => (j, start_idx), + }; + if int_y < seg.end().y && int_y < other.end().y { + self.events.push(IntersectionEvent { + y: int_y, + left: self.line.seg(left_idx), + right: self.line.seg(right_idx), + }); + height_bound = height_bound.min(int_y); + } + } + + let cmp = self.compare_segments_conservatively(other_idx, seg_idx); + if let Some((_y0, y1, order)) = cmp.iter().find(|(_, y1, _)| *y1 > y) + && order == Order::Left + && y1 >= height_bound + { + break; + } + } + } + + fn scan_for_removal(&mut self, pos: usize) { + if pos > 0 { + self.intersection_scan_right(pos - 1, WhichTouch::Mid); + self.intersection_scan_left(pos - 1, WhichTouch::Mid); + } + } + + fn insert(&mut self, pos: usize, seg: SegmentOrderEntry) { + self.line.segs.insert(pos, seg); + self.intersection_scan_right(pos, WhichTouch::Mid); + self.intersection_scan_left(pos, WhichTouch::Mid); + } + + fn handle_enter(&mut self, seg_idx: SegIdx) { + let new_seg = &self.segments[seg_idx]; + + if new_seg.is_horizontal() { + self.horizontals.push(seg_idx); + return; + } + + // Do we have a contour neighbor? If so, use it for the initial position + // (which may be adjusted by intersections later). If the contour neighbor + // ends here, we just take their slot. If the contour neighbor starts here, + // we take a neighboring slot. + let contour_prev_idx = if self.segments.positively_oriented(seg_idx) { + self.segments.contour_prev(seg_idx) + } else { + self.segments.contour_next(seg_idx) + }; + + let mut pos = None; + if let Some(contour_prev_idx) = contour_prev_idx { + let contour_prev = &self.segments[contour_prev_idx]; + if contour_prev.start().y < self.y { + let pos = self.line.position(contour_prev_idx).unwrap(); + debug_assert!(contour_prev.end() == new_seg.start()); + self.handle_contour_continuation(seg_idx, new_seg, pos); + return; + } else if let Some(contour_prev_pos) = self.line.position(contour_prev_idx) { + // Both of us entered on this sweep-line. + debug_assert!(contour_prev.start() == new_seg.start()); + pos = match self + .compare_segments(seg_idx, contour_prev_idx) + .order_after(self.y) + { + Order::Left => Some(contour_prev_pos), + Order::Right => Some(contour_prev_pos + 1), + Order::Ish => { + // Maybe this is overkill, but it gives better results for our snapshot + // tests (which admittedly have a very large "ish" parameter). If two + // segments enter at the same `y` and they are always close to one another, + // choose their ordering according to their average slope. We could also + // go by the initial slope, but that's less numerically stable. + let diff = new_seg.end().to_kurbo() - new_seg.start().to_kurbo(); + let contour_prev_diff = + contour_prev.end().to_kurbo() - contour_prev.start().to_kurbo(); + // Assuming the y-diffs are non-negative, this is basically + // `if diff.x / diff.y < contour_prev_diff.x / contour_prev_diff.y` + if diff.x * contour_prev_diff.y < contour_prev_diff.x * diff.y { + Some(contour_prev_pos) + } else { + Some(contour_prev_pos + 1) + } + } + }; + } + } + + let pos = pos.unwrap_or_else(|| { + self.line.insertion_idx( + self.y, + self.segments, + seg_idx, + &mut self.comparisons.borrow_mut(), + ) + }); + + let mut entry = SegmentOrderEntry::new(seg_idx, self.segments, self.eps); + entry.enter = true; + entry.exit = false; + self.insert(pos, entry); + self.add_seg_needing_position(pos, true); + } + + fn add_seg_needing_position(&mut self, pos: usize, insert: bool) { + // Fix up the index of any other segments that we got inserted before + // (at this point, segs_needing_positions only contains newly-inserted + // segments, and it's sorted increasing). + // + // We sorted all the to-be-inserted segments by horizontal position + // before inserting them, so we expect these two loops to be short most + // of the time. + if insert { + for other_pos in self.segs_needing_positions.iter_mut().rev() { + if *other_pos >= pos { + *other_pos += 1; + } else { + break; + } + } + } + let insert_pos = self + .segs_needing_positions + .iter() + .rposition(|p| *p < pos) + .map(|p| p + 1) + .unwrap_or(0); + self.segs_needing_positions.insert(insert_pos, pos); + debug_assert!(self.segs_needing_positions.is_sorted()); + } + + // A special case of handle-enter, in which the entering segment is + // continuing the contour of an exiting segment. + fn handle_contour_continuation(&mut self, seg_idx: SegIdx, seg: &Segment, pos: usize) { + let x0 = seg.min_x(); + let x1 = seg.max_x(); + self.line.segs[pos].old_seg = Some(self.line.segs[pos].seg); + self.line.segs[pos].seg = seg_idx; + self.line.segs[pos].enter = true; + self.line.segs[pos].exit = true; + self.line.segs[pos].lower_bound = x0.min(x1) - self.eps; + self.line.segs[pos].upper_bound = x0.max(x1) + self.eps; + self.intersection_scan_right(pos, WhichTouch::Mid); + self.intersection_scan_left(pos, WhichTouch::Mid); + self.add_seg_needing_position(pos, false); + } + + /// Marks a segment as needing to exit, but doesn't actually remove it + /// from the sweep-line. See `SegmentOrderEntry::exit` for an explanation. + fn handle_exit(&mut self, seg_idx: SegIdx) { + let pos = self.line.position(seg_idx).unwrap(); + + if self.line.segs[pos].old_seg == Some(seg_idx) { + return; + } + + // It's important that this goes before `scan_for_removal`, so that + // the scan doesn't get confused by the segment that should be marked + // for exit. + self.line.segs[pos].exit = true; + self.scan_for_removal(pos); + self.segs_needing_positions.push(pos); + } + + fn handle_intersection(&mut self, left: SegIdx, right: SegIdx) { + let left_idx = self.line.position(left).unwrap(); + let right_idx = self.line.position(right).unwrap(); + if left_idx < right_idx { + // We're going to put `left_seg` after `right_seg` in the + // sweep line, and while doing so we need to "push" along + // all segments that are strictly bigger than `left_seg` + // (slight false positives are allowed; no false negatives). + let mut to_move = vec![left_idx]; + for j in (left_idx + 1)..right_idx { + let seg_j_entry = &self.line.segs[j]; + if seg_j_entry.will_really_exit() { + continue; + } + + let seg_j_idx = seg_j_entry.seg; + let (_, y1, left_j_order) = self.compare_segments(left, seg_j_idx).entry_at(self.y); + if left_j_order == Order::Left { + // Because the order isn't transitive, it's possible that + // we've been asked to push `seg_j` to the right of `right`, + // even though `seg_j` compares left of `right`. If this + // happens, we delay the intersection. This delay won't cause + // a bad ordering between `left` and `right`, because that + // would imply an ordering cycle + // (`left` < `seg_j` < `right` < `left`). + let (_, y2, j_right_order) = + self.compare_segments(seg_j_idx, right).entry_at(self.y); + if j_right_order == Order::Left { + self.events.push(IntersectionEvent { + y: y1.min(y2), + left, + right, + }); + return; + } + + to_move.push(j); + } + } + + // Keep track of segment movement. TODO: reference a more detailed write-up + self.segs_needing_positions.extend(left_idx..=right_idx); + for (i, entry) in self.line.segs.range_mut(left_idx..=right_idx).enumerate() { + if entry.old_idx.is_none() { + entry.old_idx = Some(left_idx + i); + } + } + + // Remove them in reverse to make indexing easier. + let mut removed = Vec::with_capacity(to_move.len()); + for &j in to_move.iter().rev() { + removed.push(self.line.segs.remove(j)); + self.scan_for_removal(j); + } + + // We want to insert them at what was previously `right_idx + 1`, but the + // index changed because of the removal. + let insertion_pos = right_idx + 1 - to_move.len(); + + for seg in removed { + // This is almost the same as + // self.insert(insertion_pos, seg), except that we use + // the "late" touching point in some + // circumstances. The late touching point is allowed + // here (because the removal scans above have ensured + // the crossing invariant for everything except the + // segment that we're "inserting"). + // + // Choosing the late touching point ensures that our y + // keeps moving forwards, because if our shuffling in + // this `handle_intersection` has caused some new + // intersection event and we choose that event's + // latest possible `y`, that event must have strictly + // larger `y` than our current `y` (if not, it would + // violate the cyclic order property). + let which = WhichTouch::Late; + self.line.segs.insert(insertion_pos, seg); + self.intersection_scan_right(insertion_pos, which); + self.intersection_scan_left(insertion_pos, which); + } + } else { + // This is for handling the "touch but don't cross" case. + // As discussed in `intersection_scan_right`, maybe we could handle + // this with a different kind of intersection event. + // + // Currently, this leads to over-division for intersections + // that were already handled. + if self.correctness == Correctness::ReallyCorrect { + self.segs_needing_positions.extend(right_idx..=left_idx); + } + + // We haven't changed the order of any segments, but the "touch" event + // we just processed might have been a witness for the crossing invariant. + // So we need to scan. + self.intersection_scan_right(right_idx, WhichTouch::Mid); + self.intersection_scan_left(left_idx, WhichTouch::Mid); + } + } + + // If we have a segment in a changed interval, then every other segment + // that it compares "Ish" to should also be in that changed interval. + #[cfg(feature = "slow-asserts")] + fn check_changed_interval_closeness(&self) { + let y = self.y; + for iv in &self.changed_intervals { + for j in iv.segs.clone() { + let seg_j = self.line.seg(j); + for i in 0..iv.segs.start { + let seg_i = self.line.seg(i); + let cmp = self.compare_segments(seg_i, seg_j); + if self.blocking_segment(i, j).is_none() { + assert_ne!(cmp.order_at(y), Order::Right); + } + } + + for k in iv.segs.end..self.line.segs.len() { + let seg_k = self.line.seg(k); + let cmp = self.compare_segments(seg_j, seg_k); + if self.blocking_segment(j, k).is_none() { + assert_ne!(cmp.order_at(y), Order::Right); + } + } + } + } + } + + #[cfg(feature = "slow-asserts")] + fn blocking_segment(&self, i: usize, j: usize) -> Option { + let seg_i = self.line.seg(i); + let seg_j = self.line.seg(j); + self.line + .segs + .range((i + 1)..j) + .position(|entry| { + let mid_seg = entry.seg; + self.compare_segments_conservatively(seg_i, mid_seg) + .order_at(self.y) + == Order::Left + || self + .compare_segments_conservatively(mid_seg, seg_j) + .order_at(self.y) + == Order::Left + }) + .map(|idx| idx + i) + } + + #[cfg(feature = "slow-asserts")] + fn check_invariants(&self) { + for seg_entry in self.line.segs.iter() { + let seg_idx = seg_entry.seg; + let seg = &self.segments[seg_idx]; + assert!( + (&seg.start().y..=&seg.end().y).contains(&&self.y), + "segment {seg:?} out of range at y={:?}", + self.y + ); + } + + // All segments marked as entering or exiting must be in `self.segs_needing_positions` + for (idx, seg_entry) in self.line.segs.iter().enumerate() { + if seg_entry.exit || seg_entry.enter { + assert!(self.segs_needing_positions.contains(&idx)); + } + } + + let invalid_order = self.line.find_invalid_order( + self.y, + self.segments, + self.comparisons.borrow_mut().deref_mut(), + ); + if let Some(order) = invalid_order { + // There are two cases in which we allow invalid orders: + // - if there's an intersection event queued exactly at this `y` value. + // - if there's some "blocking" segment between the two segments + // with invalid orders. + let has_matching_event = self + .events + .intersection + .iter() + .take_while(|ev| ev.y == self.y) + .any(|ev| ev.left == order.0 && ev.right == order.1); + + let left_idx = self.line.position(order.0).unwrap(); + let right_idx = self.line.position(order.1).unwrap(); + let has_blocking_segment = self.blocking_segment(left_idx, right_idx).is_some(); + if !has_matching_event && !has_blocking_segment { + dbg!(&self.line); + panic!("invalid order {order:?}"); + } + } + + for i in 0..self.line.segs.len() { + if self.line.is_exit(i) { + continue; + } + for j in (i + 1)..self.line.segs.len() { + if self.line.is_exit(j) { + continue; + } + let segi = self.line.seg(i); + let segj = self.line.seg(j); + + let touch = self.compare_segments(segi, segj).next_touch_after(self.y); + if let Some(y_int) = + touch.and_then(|touch| choose_cross_height(touch, self.y, WhichTouch::Late)) + { + let is_cross = matches!(touch, Some(CurveInteraction::Cross(..))); + if y_int >= self.y && is_cross { + // Find an event between i and j. + let is_between = |idx: SegIdx| -> bool { + self.line + .position(idx) + .is_some_and(|pos| i <= pos && pos <= j) + }; + let has_exit_witness = self + .line + .segs + .range(i..=j) + .any(|seg_entry| self.segments[seg_entry.seg].end().y <= y_int); + + let has_intersection_witness = self.events.intersection.iter().any(|ev| { + let is_between = is_between(ev.left) && is_between(ev.right); + let before_y = ev.y <= y_int; + is_between && before_y + }); + let has_witness = has_exit_witness || has_intersection_witness; + let has_blocking_segment = self.blocking_segment(i, j).is_some(); + assert!( + has_witness || has_blocking_segment, + "segments {:?} and {:?} cross at {:?}, but there is no witness. y={}, intersections={:?}, line={:?}", + self.line.segs[i].seg, + self.line.segs[j].seg, + y_int, + self.y, + self.events.intersection, + &self.line, + ); + } + } + } + } + + self.check_changed_interval_closeness(); + } + + #[cfg(not(feature = "slow-asserts"))] + fn check_invariants(&self) {} + + fn compute_horizontal_changed_intervals(&mut self) { + self.horizontals + .sort_by_key(|seg_idx| CheapOrderedFloat::from(self.segments[*seg_idx].start().x)); + let mut last_end = f64::NEG_INFINITY; + + for (idx, &seg_idx) in self.horizontals.iter().enumerate() { + let seg = &self.segments[seg_idx]; + + // Find the index of some segment that might overlap with this + // horizontal segment, but the index before definitely doesn't. + let start_idx = self.line.segs.partition_point(|other_entry| { + let other_seg = &self.segments[other_entry.seg]; + + other_seg.upper(self.y, self.eps) + self.eps <= seg.start().x + }); + + let mut end_idx = start_idx; + for j in start_idx..self.line.segs.len() { + let other_entry = &mut self.line.segs[j]; + let other_seg = &self.segments[other_entry.seg]; + + if other_seg.lower(self.y, self.eps) - self.eps <= seg.end().x { + // Ensure that every segment in the changed interval has `old_idx` set; + // see also `compute_changed_intervals`. + self.line.segs[j].set_old_idx_if_unset(j); + end_idx = j + 1; + } else { + break; + } + } + + // If this horizontal interval overlaps with the previous one, extend + // the changed interval instead of pushing a new one. + if seg.start().x <= last_end { + // unwrap: since our segments are finite, we can only get in this + // branch if we already passed through the main loop and added something + // to changed_intervals + let last = self.changed_intervals.last_mut().unwrap(); + // unwrap: all our changed intervals in this function have horizontals. + last.horizontals.as_mut().unwrap().end = idx + 1; + last.segs.end = last.segs.end.max(end_idx); + } else { + let changed = ChangedInterval { + segs: start_idx..end_idx, + horizontals: Some(idx..(idx + 1)), + }; + self.changed_intervals.push(changed); + } + + last_end = last_end.max(seg.end().x); + } + } + + /// When a segment enters or exits, we insert a horizontal line from + /// its negotiated position to its original endpoint position. To ensure + /// that the topology gets correctly computed for segments between these + /// two horizontal positions, we need to make them all part of a changed + /// interval. + /// + /// This method adds all the changed intervals that are needed to handle this case. + fn compute_contour_adjacent_changed_intervals(&mut self) { + for &j in &self.segs_needing_positions { + let seg_entry = self.line.segs[j]; + + if seg_entry.old_seg.is_some() { + // We ignore contour continuations that share a slot. This + // might not be quite correct right now, because in this case + // we currently still insert horizontal segments to the original + // endpoints. But those two horizontal segments "cancel out" + // topologically (and we'd like to remove them eventually). + continue; + } + if !(seg_entry.enter || seg_entry.exit) { + // This only applies to entering or exiting segments. + continue; + } + + // TODO: this logic is pretty much copied from the horizontals segments + // method. + let seg = &self.segments[seg_entry.seg]; + let p = if seg_entry.enter { + kurbo::Point::new(seg.start().x, seg.start().y) + } else { + kurbo::Point::new(seg.end().x, seg.end().y) + }; + let mut start_idx = j; + let mut end_idx = j; + for i in (0..j).rev() { + let other_entry = &mut self.line.segs[i]; + let other_seg = &self.segments[other_entry.seg]; + + if other_seg.upper(self.y, self.eps) + self.eps >= p.x { + // Ensure that every segment in the changed interval has `old_idx` set; + // see also `compute_changed_intervals`. + self.line.segs[i].set_old_idx_if_unset(i); + start_idx = i; + } else { + break; + } + } + for k in (j + 1)..self.line.segs.len() { + let other_entry = &mut self.line.segs[k]; + let other_seg = &self.segments[other_entry.seg]; + + if other_seg.lower(self.y, self.eps) - self.eps <= p.x { + // Ensure that every segment in the changed interval has `old_idx` set; + // see also `compute_changed_intervals`. + self.line.segs[k].set_old_idx_if_unset(k); + end_idx = k; + } else { + break; + } + } + + if end_idx > start_idx { + self.changed_intervals.push(ChangedInterval { + segs: start_idx..(end_idx + 1), + horizontals: None, + }); + } + } + } + + /// Updates our internal `changed_intervals` state based on the segments marked + /// as needing positions. + /// + /// For each segment marked as needing a position, we expand it to a range of + /// physically nearby segments, and then we deduplicate and merge those ranges. + fn compute_changed_intervals(&mut self) { + debug_assert!(self.changed_intervals.is_empty()); + self.compute_horizontal_changed_intervals(); + self.compute_contour_adjacent_changed_intervals(); + + for &idx in &self.segs_needing_positions { + if self.line.segs[idx].in_changed_interval { + continue; + } + // Ensure that every segment in the changed interval has `old_idx` set. This + // isn't strictly necessary (because segments without `old_idx` set haven't + // changed their indices), but it's more convenient to just say that all + // segments in a changed interval must have `old_idx` set. + self.line.segs[idx].set_old_idx_if_unset(idx); + + self.line.segs[idx].in_changed_interval = true; + let mut start_idx = idx; + let mut end_idx = idx + 1; + + for i in (idx + 1)..self.line.segs.len() { + // Note that end_idx is mutated in the loop, so this points to the segment + // that we most recently added to the changed interval. Modulo some subtleties + // involving conservative/non-conservative comparisons, this will usually be + // `i - 1`. + let seg_idx = self.line.seg(end_idx - 1); + let next_seg_idx = self.line.seg(i); + let strong_order = self + .compare_segments_conservatively(seg_idx, next_seg_idx) + .order_at(self.y); + if strong_order == Order::Left { + break; + } else { + // TODO: re-enable this (and below) once our strong + // comparisons are implemented properly with minkowski sums + //debug_assert_eq!(strong_cmp.order_at(self.y), Order::Ish); + let order = self + .compare_segments(seg_idx, next_seg_idx) + .order_at(self.y); + if order != Order::Left { + for j in end_idx..=i { + self.line.segs[j].in_changed_interval = true; + self.line.segs[j].set_old_idx_if_unset(j); + } + + end_idx = i + 1; + } + } + } + + for i in (0..start_idx).rev() { + let prev_seg_idx = self.line.seg(i); + // Note that start_idx is mutated in the loop, so this points to the segment + // that we most recently added to the changed interval. + let seg_idx = self.line.seg(start_idx); + let strong_order = self + .compare_segments_conservatively(prev_seg_idx, seg_idx) + .order_at(self.y); + if strong_order == Order::Left { + break; + } else { + //debug_assert_eq!(strong_cmp.order_at(self.y), Order::Ish); + let order = self + .compare_segments(prev_seg_idx, seg_idx) + .order_at(self.y); + if order != Order::Left { + for j in i..start_idx { + self.line.segs[j].in_changed_interval = true; + self.line.segs[j].set_old_idx_if_unset(j); + } + start_idx = i; + } + } + } + self.changed_intervals + .push(ChangedInterval::from_seg_interval(start_idx..end_idx)); + } + self.changed_intervals.sort_by_key(|r| r.segs.start); + + // By merging adjacent intervals, we ensure that there is no horizontal segment + // that spans two ranges. That's because horizontal segments mark everything they + // cross as needing a position. Any collection of subranges that are crossed by + // a horizontal segment are therefore adjacent and will be merged here. + merge_adjacent(&mut self.changed_intervals); + } +} + +/// Represents a sub-interval of the sweep-line where some subdivisions need to happen. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Debug, Clone)] +pub struct ChangedInterval { + // Indices into the sweep-line's segment array. + pub(crate) segs: std::ops::Range, + // Indices into the array of horizontal segments. + pub(crate) horizontals: Option>, +} + +impl ChangedInterval { + fn merge(&mut self, other: &ChangedInterval) { + self.segs.end = self.segs.end.max(other.segs.end); + self.segs.start = self.segs.start.min(other.segs.start); + + self.horizontals = match (self.horizontals.clone(), other.horizontals.clone()) { + (None, None) => None, + (None, Some(r)) | (Some(r), None) => Some(r), + (Some(a), Some(b)) => Some(a.start.min(b.start)..a.end.max(b.end)), + }; + } + + fn from_seg_interval(segs: std::ops::Range) -> Self { + Self { + segs, + horizontals: None, + } + } +} + +impl SegmentOrder { + // If the ordering invariants fail, returns a pair of indices witnessing that failure. + // Used in tests, and when enabling slow-asserts + #[allow(dead_code)] + fn find_invalid_order( + &self, + y: f64, + segments: &Segments, + cmp: &mut ComparisonCache, + ) -> Option<(SegIdx, SegIdx)> { + for i in 0..self.segs.len() { + let segi = self.seg(i); + // We ignore segments ending at the current y, because our intersection + // scans also ignore such segments. + // + // TODO: I think this is correct enough for continuous contours + // (because the adjacent starting contour will get a validly-ordered + // position and then we'll insert a horizontal segment joining + // them), but if the input is just a collection of segments then it + // can mess up the endpoints. + if segments[segi].end().y == y { + continue; + } + for j in (i + 1)..self.segs.len() { + let segj = self.seg(j); + if segments[segj].end().y == y { + continue; + } + + let order = cmp.compare_segments(segments, segi, segj); + if order.order_at(y) == Order::Right { + return Some((segi, segj)); + } + } + } + + None + } + + // Finds an index into this sweep line where it's ok to insert this new segment. + fn insertion_idx( + &self, + y: f64, + segments: &Segments, + seg_idx: SegIdx, + comparer: &mut ComparisonCache, + ) -> usize { + let seg = &segments[seg_idx]; + let seg_x = seg.start().x; + + // Fast path: we first do a binary search just with the horizontal bounding intervals. + // `pos` is an index where we're to the left of that segment's right-most point, and + // we're to the right of the segment at `pos - 1`'s right-most point. + // (It isn't necessarily the first such index because things aren't quite sorted; see + // below about the binary search.) + let pos = self + .segs + .partition_point(|entry| seg_x >= entry.upper_bound); + + if pos >= self.segs.len() { + return pos; + } else if pos + 1 >= self.segs.len() || self.segs[pos + 1].lower_bound >= seg_x { + // At this point, the segment before pos is definitely to our left, and the segment + // after pos is definitely to our right (or there is no such segment). That means + // we can insert either before or after `pos`. We'll choose based on what the order + // will be in the future, so as to minimize swapping. + // (We could also try checking whether the new segment is a contour neighbor of + // the segment at pos. That should be a pretty common case.) + let cmp = comparer.compare_segments(segments, seg_idx, self.segs[pos].seg); + return match cmp.order_after(y) { + Order::Right => pos + 1, + Order::Ish => pos, + Order::Left => pos, + }; + } + + // A predicate that tests whether `other` is definitely to the left of `seg` at `y`. + let lower_pred = |other: &SegmentOrderEntry| -> bool { + let cmp = comparer.compare_segments(segments, other.seg, seg_idx); + cmp.order_at(y) == Order::Left + }; + + // The rust stdlib docs say that we're not allowed to do this, because + // our array isn't sorted with respect to `lower_pred`. + // But for now at least, their implementation does a normal + // binary search and so it's guaranteed to return an index where + // `lower_pred` fails but the index before it succeeds. + // + // `search_start` is `i_- + 1` in the write-up; it's the first index + // where the predicate returns false. + let search_start = self.segs.partition_point(lower_pred); + let mut idx = search_start; + for i in search_start..self.segs.len() { + let cmp = comparer.compare_segments(segments, self.segs[i].seg, seg_idx); + match cmp.order_at(y) { + Order::Right => { + // The segment at i is definitely to the right of the new one. + break; + } + Order::Ish => { + // We could go either way, and we'll update our preference based on the + // future ordering. + if cmp.order_after(y) == Order::Left { + idx = i + 1; + } + } + Order::Left => { + // The segment at i is definitely to the left of the new one. + idx = i + 1; + } + } + } + idx + } + + // Find the position of the given segment in our array. + // + // Returns an index pointing to a `SegmentOrderEntry` for which + // `seg_idx` is either the `seg` or the `old_seg`. + fn position(&self, seg_idx: SegIdx) -> Option { + self.segs + .iter() + .position(|x| x.seg == seg_idx || x.old_seg == Some(seg_idx)) + } +} + +/// Finds a feasible `x` position for each segment. +/// +/// The point is that we've decided on a horizontal ordering of the segments, but +/// their numerical positions might not completely agree with that ordering. For +/// each segment, this function computes a range of `x` coordinates with the +/// guarantee that if you go from left to right (or right to left) and assign +/// each segment an `x` coordinate within its range then you won't paint +/// yourself into a corner: subsequent points can be positioned with the right +/// ordering *and* within the designated range. +fn feasible_horizontal_positions SegIdx>( + entries: &[SegmentOrderEntry], + entry_seg: G, + y: f64, + segments: &Segments, + eps: f64, + out: &mut Vec<(SegmentOrderEntry, f64, f64)>, +) { + out.clear(); + let mut max_so_far = f64::NEG_INFINITY; + let mut max = f64::NEG_INFINITY; + let mut min = f64::INFINITY; + + for entry in entries { + let seg = &segments[entry_seg(entry)]; + max_so_far = max_so_far.max(seg.lower(y, eps)); + // Fill out the minimum allowed positions, with a placeholder for the maximum. + out.push((*entry, max_so_far, 0.0)); + + let x = seg.at_y(y); + max = max.max(x); + min = min.min(x); + } + + let mut min_so_far = f64::INFINITY; + + for (entry, min_allowed, max_allowed) in out.iter_mut().rev() { + let x = segments[entry_seg(entry)].upper(y, eps); + min_so_far = min_so_far.min(x); + *max_allowed = min_so_far.min(max); + *min_allowed = min_allowed.max(min); + } +} + +/// A sweep-line, as output by a [`Sweeper`]. +/// +/// This contains all the information about how the input line segments interact +/// with a sweep-line, including which segments start here, which segments end here, +/// and which segments intersect here. +/// +/// A sweep-line stores a `y` coordinate along with two (potentially) different +/// orderings of the segments: the ordering just above `y` and the ordering just +/// below `y`. If two line segments intersect at the sweep-line, their orderings +/// above `y` and below `y` will be different. +#[derive(Debug)] +pub struct SweepLine<'buf, 'state, 'segs> { + pub(crate) state: &'state Sweeper<'segs>, + bufs: &'buf mut SweepLineBuffers, + // Index into state.changed_intervals + next_changed_interval: usize, +} + +impl<'segs> SweepLine<'_, '_, 'segs> { + /// The vertical position of this sweep-line. + pub fn y(&self) -> f64 { + self.state.y + } + + /// Our tolerance parameter. + pub fn eps(&self) -> f64 { + self.state.eps + } + + /// The collection of segments that we're sweeping over. + pub fn segments(&self) -> &Segments { + self.state.segments + } + + /// Get the line segment at position `idx` in the new order. + pub fn line_segment(&self, idx: usize) -> Option { + self.state.line.segs.get(idx).map(|entry| entry.seg) + } + + /// Get the line segment at position `idx` in the new order. + pub(crate) fn line_entry(&self, idx: usize) -> Option<&SegmentOrderEntry> { + self.state.line.segs.get(idx) + } + + /// Returns an iterator over the segments in `range`, according to the "old" + /// sweep-line order. + /// + /// `range` must be a full changed interval, for example the one returned by + /// `cur_interval`. + pub(crate) fn old_segment_range( + &self, + range: std::ops::Range, + ) -> impl Iterator + '_ { + let mut ret = vec![None; range.end - range.start]; + for (idx, entry) in self.state.line.segs.range(range.clone()).enumerate() { + let seg = if entry.enter { + match entry.old_seg { + Some(s) => s, + None => continue, + } + } else { + entry.seg + }; + + let idx = entry.old_idx.unwrap_or(idx); + ret[idx - range.start] = Some(seg); + } + + ret.into_iter().flatten() + } + + /// Returns an iterator over the segments in `range`, according to the "new" + /// sweep-line order. + pub fn segment_range( + &self, + range: std::ops::Range, + ) -> impl Iterator + '_ { + self.state.line.segs.range(range).filter_map(|entry| { + if entry.exit && entry.old_seg.is_none() { + None + } else { + Some(entry.seg) + } + }) + } + + /// Returns the index ranges of segments in this sweep-line that need to be + /// given explicit positions. + /// + /// Not every line segment that passes through a sweep-line needs to be + /// subdivided at that sweep-line; in order to have a fast sweep-line + /// implementation, we need to be able to ignore the segments that don't + /// need subdivision. + /// + /// This method returns a range of segments that need to be subdivided at + /// the current sweep-line. If you call [`SweepLine::next_range`] and then + /// call this again, it will give you a non-overlapping range. + pub fn cur_interval(&self) -> Option<&ChangedInterval> { + self.next_changed_interval + .checked_sub(1) + .and_then(|idx| self.state.changed_intervals.get(idx)) + } + + fn next_range_single_seg<'a, 'bufs>( + &'a mut self, + bufs: &'bufs mut SweepLineRangeBuffers, + segments: &Segments, + idx: usize, + ) -> Option> { + bufs.clear(); + self.bufs.output_events.clear(); + + let entry = &self.state.line.segs[idx]; + let x = segments[entry.seg].at_y(self.y()); + if let Some(old_seg) = entry.old_seg { + // This entry is on a contour, where one segment ends and the next begins. + // We output two events (one per segment) at the same position. + self.bufs.output_events.push(OutputEvent { + x0: x, + connected_above: true, + x1: x, + connected_below: false, + seg_idx: old_seg, + sweep_idx: None, + old_sweep_idx: entry.old_idx, + }); + + self.bufs.output_events.push(OutputEvent { + x0: x, + connected_above: false, + x1: x, + connected_below: true, + seg_idx: entry.seg, + sweep_idx: Some(idx), + old_sweep_idx: None, + }); + } else { + // It's a single segment either entering or exiting at this height. + // We can handle them both in a single case. + self.bufs.output_events.push(OutputEvent { + x0: x, + connected_above: !entry.enter, + x1: x, + connected_below: !entry.exit, + seg_idx: entry.seg, + sweep_idx: Some(idx), + old_sweep_idx: entry.old_idx, + }); + } + + let changed_interval = ChangedInterval { + segs: idx..(idx + 1), + horizontals: None, + }; + + Some(SweepLineRange::new( + self, + &self.bufs.output_events, + bufs, + changed_interval, + )) + } + + /// Returns a [`SweepLineRange`] for visiting and processing all positions within + /// a range of segments. + pub fn next_range<'a, 'bufs>( + &'a mut self, + bufs: &'bufs mut SweepLineRangeBuffers, + segments: &Segments, + ) -> Option> { + let range = self + .state + .changed_intervals + .get(self.next_changed_interval)? + .clone(); + self.next_changed_interval += 1; + + if range.segs.len() == 1 && range.horizontals.is_none() { + return self.next_range_single_seg(bufs, segments, range.segs.start); + } + + let dummy_entry = SegmentOrderEntry { + seg: SegIdx(424242), + exit: false, + enter: false, + lower_bound: 0.0, + upper_bound: 0.0, + in_changed_interval: false, + old_idx: None, + old_seg: None, + }; + + let buffers = &mut self.bufs; + buffers + .old_line + .resize(range.segs.end - range.segs.start, dummy_entry); + for entry in self.state.line.segs.range(range.segs.clone()) { + buffers.old_line[entry.old_idx.unwrap() - range.segs.start] = *entry; + } + + // Assign horizontal positions to all the points in the old sweep line. + // First, compute the feasible positions. + feasible_horizontal_positions( + &buffers.old_line, + |entry| entry.old_seg(), + self.state.y, + segments, + self.state.eps, + &mut buffers.positions, + ); + + // Now go through and assign the actual positions. + // + // The two positioning arrays should have the same segments, but possibly in a different + // order. We build them up in the old-sweep-line order. + buffers.output_events.clear(); + let events = &mut buffers.output_events; + let mut max_so_far = f64::NEG_INFINITY; + for (entry, min_x, max_x) in &buffers.positions { + let preferred_x = if entry.exit { + // The best possible position is the true segment-ending position. + // (This could change if we want to be more sophisticated at joining contours.) + segments[entry.old_seg()].end().x + } else if entry.enter { + // The best possible position is the true segment-starting position. + // (This could change if we want to be more sophisticated at joining contours.) + segments[entry.seg].start().x + } else { + segments[entry.seg].at_y(self.state.y) + }; + let x = preferred_x.max(*min_x).max(max_so_far).min(*max_x); + max_so_far = x; + events.push(OutputEvent { + x0: x, + connected_above: entry.old_seg.is_some() || !entry.enter, + // This will be filled out when we traverse new_xs. + x1: 42.42, + connected_below: !entry.exit, + seg_idx: entry.old_seg(), + sweep_idx: None, + old_sweep_idx: entry.old_idx, + }); + } + + // And now we repeat for the new sweep line: compute the feasible positions + // and then choose the actual positions. + buffers.old_line.clear(); + buffers + .old_line + .extend(self.state.line.segs.range(range.segs.clone()).cloned()); + feasible_horizontal_positions( + &buffers.old_line, + |entry| entry.seg, + self.state.y, + segments, + self.state.eps, + &mut buffers.positions, + ); + let mut max_so_far = f64::NEG_INFINITY; + for (idx, (entry, min_x, max_x)) in buffers.positions.iter().enumerate() { + let ev = &mut events[entry.old_idx.unwrap() - range.segs.start]; + ev.sweep_idx = Some(range.segs.start + idx); + debug_assert_eq!(ev.seg_idx, entry.old_seg()); + let preferred_x = if *min_x <= ev.x0 && ev.x0 <= *max_x { + // Try snapping to the previous position if possible. + ev.x0 + } else { + segments[entry.seg].at_y(self.state.y) + }; + ev.x1 = preferred_x.max(*min_x).max(max_so_far).min(*max_x); + max_so_far = ev.x1; + + let x1 = ev.x1; + if entry.old_seg.is_some() { + events.push(OutputEvent { + x0: x1, + x1, + connected_above: false, + // This will be filled out when we traverse new_xs. + connected_below: true, + seg_idx: entry.seg, + sweep_idx: Some(range.segs.start + idx), + old_sweep_idx: None, + }); + } + } + + // Modify the positions so that entering and exiting segments get their exact position. + // + // This is the easiest way to maintain the continuity of contours, but + // eventually we should change this to minimize horizontal jank. + for ev in &mut *events { + let seg = &segments[ev.seg_idx]; + if !ev.connected_above { + ev.x0 = seg.start().x; + } + if !ev.connected_below { + ev.x1 = seg.end().x; + } + } + + if let Some(range) = &range.horizontals { + for &seg_idx in &self.state.horizontals[range.clone()] { + let seg = &self.state.segments[seg_idx]; + events.push(OutputEvent { + x0: seg.start().x, + connected_above: false, + x1: seg.end().x, + connected_below: false, + seg_idx, + sweep_idx: None, + old_sweep_idx: None, + }); + } + } + events.sort(); + bufs.clear(); + + Some(SweepLineRange::new( + self, + &self.bufs.output_events, + bufs, + range, + )) + } +} + +/// Given a list of intervals, sorted by starting point, merge the overlapping ones. +/// +/// For example, [1..2, 4..5, 5..6] is turned into [1..2, 4..6]. +fn merge_adjacent(intervals: &mut Vec) { + if intervals.is_empty() { + return; + } + + let mut write_idx = 0; + for read_idx in 1..intervals.len() { + let last_end = intervals[write_idx].segs.end; + let cur = intervals[read_idx].clone(); + if last_end < cur.segs.start { + write_idx += 1; + intervals[write_idx] = cur; + } else { + intervals[write_idx].merge(&cur); + } + } + intervals.truncate(write_idx + 1); +} + +/// Choose a valid crossing height given a range of possibilities and +/// the current height of the sweep-line. +fn choose_cross_height( + interaction: CurveInteraction, + cur_y: f64, + which: WhichTouch, +) -> Option { + // TODO: This "mid" will have the effect of emitting an intersection event + // halfway (in y) between the two definite orders. This doesn't necessarily + // match the guarantees that we require: for the "stronger" crossing + // invariants mentioned in the write-up, when scanning left from index j + // and comparing to index i, we need to emit the intersection event before + // i's lower bound crosses j. + // + // We could just emit y0. That would be safe, but give less pretty results + // in practice. + let (mid, end) = match interaction { + CurveInteraction::Cross(y0, y1) | CurveInteraction::Touch(y0, y1) => ((y0 + y1) / 2.0, y1), + }; + debug_assert!(end >= cur_y); + match (interaction, which, mid <= cur_y) { + (CurveInteraction::Cross(..), WhichTouch::Late, true) => Some(end), + (CurveInteraction::Touch(..), _, true) => None, + _ => Some(mid.max(cur_y)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use kurbo::{BezPath, CubicBez}; + use proptest::prelude::*; + + use crate::{ + geom::Point, + perturbation::{ + F64Perturbation, Perturbation, PointPerturbation, f64_perturbation, perturbation, + realize_perturbation, + }, + segments::Segments, + }; + + fn mk_segs(xs: &[(f64, f64)]) -> Segments { + let mut segs = Segments::default(); + + for &(x0, x1) in xs { + segs.add_points([Point::new(x0, 0.0), Point::new(x1, 1.0)]); + } + segs + } + + #[test] + fn merge_adjacent() { + fn merge( + v: impl IntoIterator>, + ) -> Vec> { + let mut v = v + .into_iter() + .map(ChangedInterval::from_seg_interval) + .collect(); + super::merge_adjacent(&mut v); + v.into_iter().map(|ci| ci.segs).collect() + } + + assert_eq!(merge([1..2, 3..4]), vec![1..2, 3..4]); + assert_eq!(merge([1..2, 2..4]), vec![1..4]); + assert_eq!(merge([1..2, 3..4, 4..5]), vec![1..2, 3..5]); + assert_eq!(merge([1..2, 1..3, 4..5]), vec![1..3, 4..5]); + assert_eq!(merge([1..2, 1..4, 4..5]), vec![1..5]); + assert_eq!(merge([1..4, 1..2, 4..5]), vec![1..5]); + assert_eq!(merge([]), Vec::>::new()); + } + + #[test] + fn invalid_order() { + fn check_order(xs: &[(f64, f64)], at: f64, eps: f64) -> Option<(usize, usize)> { + let y = at; + let segs = mk_segs(xs); + + let line: SegmentOrder = SegmentOrder { + segs: (0..xs.len()) + .map(|i| SegmentOrderEntry::new(SegIdx(i), &segs, eps)) + .collect(), + }; + + let mut cmp = ComparisonCache::new(eps, eps / 2.0); + line.find_invalid_order(y, &segs, &mut cmp) + .map(|(a, b)| (a.0, b.0)) + } + + let crossing = &[(-1.0, 1.0), (1.0, -1.0)]; + let eps = 1.0 / 128.0; + assert!(check_order(crossing, 0.0, eps).is_none()); + assert!(check_order(crossing, 0.5, eps).is_none()); + assert_eq!(check_order(crossing, 0.99, eps), Some((0, 1))); + + let not_quite_crossing = &[(-0.5 * eps, 0.5 * eps), (0.5 * eps, -0.5 * eps)]; + assert!(check_order(not_quite_crossing, 0.0, eps).is_none()); + assert!(check_order(not_quite_crossing, 0.5, eps).is_none()); + assert!(check_order(not_quite_crossing, 1.0, eps).is_none()); + + let barely_crossing = &[(-1.5 * eps, 1.5 * eps), (1.5 * eps, -1.5 * eps)]; + assert!(check_order(barely_crossing, 0.0, eps).is_none()); + assert!(check_order(barely_crossing, 0.5, eps).is_none()); + assert_eq!(check_order(barely_crossing, 0.99, eps), Some((0, 1))); + + let non_adj_crossing = &[(-eps, eps), (0.0, 0.0), (eps, -eps)]; + assert!(check_order(non_adj_crossing, 0.0, eps).is_none()); + assert!(check_order(non_adj_crossing, 0.5, eps).is_none()); + assert_eq!(check_order(non_adj_crossing, 0.99, eps), Some((0, 2))); + + let flat_crossing = &[(-1e6, 1e6), (-10.0 * eps, -10.0 * eps)]; + assert_eq!(check_order(flat_crossing, 0.5 - eps, eps), None); + + let end_crossing_bevel = &[(2.5 * eps, 2.5 * eps), (-1e6, 0.0)]; + assert_eq!(check_order(end_crossing_bevel, 0.99, eps), Some((0, 1))); + + let start_crossing_bevel = &[(2.5 * eps, 2.5 * eps), (0.0, -1e6)]; + assert_eq!(check_order(start_crossing_bevel, 0.99, eps), Some((0, 1))); + } + + #[test] + fn insertion_idx() { + fn insert(xs: &[(f64, f64)], new: (f64, f64), at: f64, eps: f64) -> usize { + let y = at; + let mut xs: Vec<_> = xs.to_owned(); + xs.push(new); + let segs = mk_segs(&xs); + + let new_idx = SegIdx(xs.len() - 1); + + let mut line: SegmentOrder = SegmentOrder { + segs: (0..(xs.len() - 1)) + .map(|i| SegmentOrderEntry::new(SegIdx(i), &segs, eps)) + .collect(), + }; + let mut comparer = ComparisonCache::new(eps, eps / 2.0); + let idx = line.insertion_idx(y, &segs, new_idx, &mut comparer); + + dbg!(&line); + assert!(dbg!(line.find_invalid_order(y, &segs, &mut comparer)).is_none()); + line.segs.insert( + idx, + SegmentOrderEntry::new(SegIdx(xs.len() - 1), &segs, eps), + ); + assert!(line.find_invalid_order(y, &segs, &mut comparer,).is_none()); + idx + } + + let eps = 1.0 / 128.0; + assert_eq!( + insert(&[(-1.0, -1.0), (1.0, 1.0)], (-2.0, 0.0), 0.0, eps), + 0 + ); + assert_eq!(insert(&[(-1.0, -1.0), (1.0, 1.0)], (0.0, 0.0), 0.0, eps), 1); + assert_eq!(insert(&[(-1.0, -1.0), (1.0, 1.0)], (2.0, 0.0), 0.0, eps), 2); + + assert_eq!( + insert( + &[(-1e6, 1e6), (-1.0, -1.0), (1.0, 1.0)], + (0.0, 0.0), + 0.5 - 1e-6, + eps + ), + 2 + ); + // This test doesn't really work any more, now that we've tightened + // up our invalid order test. + // assert_eq!( + // insert( + // &[ + // (-1e6, 1e6), + // (-1e6, 1e6), + // (-1e6, 1e6), + // (-1.0, -1.0), + // (1.0, 1.0), + // (-1e6, 1e6), + // (-1e6, 1e6), + // (-1e6, 1e6), + // ], + // (0.0, 0.0), + // dbg!(0.5 - 1e-6 / 2.0), + // eps + // ), + // 4 + // ); + + insert(&[(2.0, 2.0), (-100.0, 100.0)], (1.0, 1.0), 0.51, 0.25); + } + + #[test] + fn test_sweep() { + let eps = 0.01; + + let segs = mk_segs(&[(0.0, 0.0), (1.0, 1.0), (-2.0, 2.0)]); + dbg!(&segs); + crate::sweep::sweep(&segs, eps, |_, ev| { + dbg!(ev); + }); + } + + fn p(x: f64, y: f64) -> Point { + Point::new(x, y) + } + + fn run_perturbation(ps: Vec>) { + let base = vec![vec![ + p(0.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + p(2.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + ]]; + + let perturbed_polylines = ps + .iter() + .map(|p| realize_perturbation(&base, p)) + .collect::>(); + let mut segs = Segments::default(); + for poly in perturbed_polylines { + segs.add_closed_polyline(poly); + } + let eps = 0.1; + crate::sweep::sweep(&segs, eps, |_, _| {}); + } + + #[derive(serde::Serialize, Debug)] + struct Output { + y: f64, + order: SegmentOrder, + changed: Vec, + } + + /// Runs a sweep-line on the given segments, collecting the sweep line orders + /// at every interesting y. + fn snapshot_outputs(segs: Segments, eps: f64) -> Vec { + let mut outputs = Vec::new(); + let mut sweeper = Sweeper::new(&segs, eps); + let mut line_bufs = SweepLineBuffers::default(); + while let Some(line) = sweeper.next_line(&mut line_bufs) { + outputs.push(Output { + y: line.y(), + order: line.state.line.clone(), + changed: line.state.changed_intervals.clone(), + }); + } + outputs + } + + #[test] + fn square() { + let segs = + Segments::from_closed_polyline([p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]); + insta::assert_ron_snapshot!(snapshot_outputs(segs, 0.01)); + } + + #[test] + fn square_and_diamond() { + let square = [p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]; + let diamond = [p(0.0, 0.0), p(1.0, 1.0), p(0.0, 2.0), p(-1.0, 1.0)]; + let eps = 0.01; + + let mut segs = Segments::default(); + segs.add_closed_polylines([square, diamond]); + insta::assert_ron_snapshot!(snapshot_outputs(segs, eps)); + } + + #[test] + fn regression_position_state() { + let ps = [Perturbation::Point { + perturbation: PointPerturbation { + x: F64Perturbation::Ulp(0), + y: F64Perturbation::Ulp(-1), + }, + idx: 14924312967467343829, + next: Box::new(Perturbation::Base { idx: 0 }), + }]; + let base = vec![vec![ + p(0.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + p(2.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + ]]; + let perturbed_polylines = ps + .iter() + .map(|p| realize_perturbation(&base, p)) + .collect::>(); + let mut segs = Segments::default(); + for poly in perturbed_polylines { + segs.add_closed_polyline(poly); + } + insta::assert_ron_snapshot!(snapshot_outputs(segs, 0.1)); + } + + #[test] + fn two_squares() { + let a = vec![p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]; + let b = vec![p(-0.5, -0.5), p(0.5, -0.5), p(0.5, 0.5), p(-0.5, 0.5)]; + let mut segs = Segments::default(); + segs.add_closed_polyline(a); + segs.add_closed_polyline(b); + insta::assert_ron_snapshot!(snapshot_outputs(segs, 0.1)); + } + + #[test] + fn conservative_line_order() { + // Here's an example where c0 gets "ish" with c1 in the middle, c0 gets "ish" with c2 + // in the middle, but c1 is always definitely left of c2. + let c0 = CubicBez::new((-1.0, 0.0), (0.6, 0.6), (0.6, 0.4), (-1.0, 1.0)); + let c1 = CubicBez::new((0.0, 0.0), (0.0, 1.0 / 3.0), (0.0, 2.0 / 3.0), (0.0, 1.0)); + let c2 = CubicBez::new((0.3, 0.0), (0.3, 1.0 / 3.0), (0.3, 2.0 / 3.0), (0.3, 1.0)); + dbg!(curve::intersect_cubics(c0, c1, 0.125, 0.25)); + dbg!(curve::intersect_cubics(c0, c2, 0.125, 0.25)); + dbg!(curve::intersect_cubics(c1, c2, 0.125, 0.25)); + + let to_path = |c: CubicBez| { + let mut p = BezPath::new(); + p.move_to(c.p0); + p.curve_to(c.p1, c.p2, c.p3); + p + }; + + let mut segs = Segments::default(); + segs.add_non_closed_bez_path( + &[to_path(c0), to_path(c1), to_path(c2)] + .into_iter() + .flatten() + .collect(), + ) + .unwrap(); + insta::assert_ron_snapshot!(snapshot_outputs(segs, 0.25)); + } + + #[test] + fn empty_sweep_line() { + let segs = Segments::default(); + let mut sweep = Sweeper::new(&segs, 1.0); + let mut bufs = SweepLineBuffers::default(); + assert!(sweep.next_line(&mut bufs).is_none()); + } + + proptest! { + #[test] + fn perturbation_test_f64(perturbations in prop::collection::vec(perturbation(f64_perturbation(0.1)), 1..5)) { + run_perturbation(perturbations); + } + + } +} diff --git a/vendor/linesweeper/src/topology.rs b/vendor/linesweeper/src/topology.rs new file mode 100644 index 00000000..8420d015 --- /dev/null +++ b/vendor/linesweeper/src/topology.rs @@ -0,0 +1,2688 @@ +//! Utilities for computing topological properties of closed paths. +//! +//! This consumes the output of the sweep-line algorithm and does things +//! like winding number computations and boolean operations. + +use std::collections::VecDeque; + +use kurbo::{BezPath, ParamCurve, Rect, Shape}; + +use crate::{ + FillRule, + curve::{Order, solve_t_for_y, y_subsegment}, + geom::Point, + order::ComparisonCache, + position::PositionedOutputSeg, + segments::{NonClosedPath, SegIdx, SegVec, Segments}, + sweep::{ + Correctness, SegmentsConnectedAtX, SweepLineBuffers, SweepLineRange, SweepLineRangeBuffers, + Sweeper, + }, +}; + +/// An abstraction over winding numbers. +/// +/// The windings numbers of a set can just be represented by integers. But if +/// you're doing boolean operations over two sets, you need both winding numbers +/// to determine whether a point is inside or outside the output set. Since we +/// also want to support more complicated situations (ternary operations, non-closed +/// paths for which winding numbers aren't defined, etc.), our topologies use generic +/// winding numbers: anything that implements this trait can be used. +pub trait WindingNumber: + Clone + std::fmt::Debug + std::ops::Add + std::ops::AddAssign + Default + Eq +{ + /// A tag for categorizing segments. + /// + /// When building a topology, you can assign to each segment a tag, which can then be + /// used to figure out that segment's winding number contribution. + #[cfg(not(test))] + type Tag: Copy + std::fmt::Debug + Eq; + + /// A tag for categorizing segments. + /// + /// When building a topology, you can assign to each segment a tag, which can then be + /// used to figure out that segment's winding number contribution. + #[cfg(test)] + type Tag: Copy + std::fmt::Debug + Eq + serde::Serialize; + + /// What is the winding number of a simple curve with tag `tag`? + fn single(tag: Self::Tag, positive: bool) -> Self; + + /// What if we restrict this winding number to only look at the set with tag `tag`? + fn of_tag(&self, tag: Self::Tag) -> Self; +} + +/// Winding numbers for binary set operations. +/// +/// This is just two integers, one for each set. Segments are tagged by +/// booleans, where `true` means that the segment is part of the first set. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)] +pub struct BinaryWindingNumber { + /// The winding number of the first shape. + pub shape_a: i32, + /// The winding number of the second shape. + pub shape_b: i32, +} + +/// A tag for binary set operations, where we call the two options +/// "set a" and "set b". +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] +pub enum OneOfTwo { + /// The tag for "set a". + #[default] + A, + /// The tag for "set b". + B, +} + +impl WindingNumber for BinaryWindingNumber { + type Tag = OneOfTwo; + + fn single(tag: Self::Tag, positive: bool) -> Self { + let sign = if positive { 1 } else { -1 }; + match tag { + OneOfTwo::A => Self { + shape_a: sign, + shape_b: 0, + }, + OneOfTwo::B => Self { + shape_a: 0, + shape_b: sign, + }, + } + } + + fn of_tag(&self, tag: OneOfTwo) -> Self { + match tag { + OneOfTwo::A => Self { + shape_a: self.shape_a, + shape_b: 0, + }, + OneOfTwo::B => Self { + shape_a: 0, + shape_b: self.shape_b, + }, + } + } +} + +impl std::ops::Add for BinaryWindingNumber { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + shape_a: self.shape_a + rhs.shape_a, + shape_b: self.shape_b + rhs.shape_b, + } + } +} + +impl std::ops::AddAssign for BinaryWindingNumber { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs + } +} + +impl std::fmt::Debug for BinaryWindingNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}a + {}b", self.shape_a, self.shape_b) + } +} + +impl WindingNumber for i32 { + type Tag = (); + + fn single((): Self::Tag, positive: bool) -> Self { + if positive { 1 } else { -1 } + } + + fn of_tag(&self, _tag: ()) -> Self { + *self + } +} + +/// For a segment, we store two winding numbers (one on each side of the segment). +/// +/// For simple segments, the winding numbers on two sides only differ by one. Once +/// we merge segments, they can differ by more. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq, Default)] +pub struct HalfSegmentWindingNumbers { + /// This half-segment is incident to a point. Imagine you're standing at + /// that point, looking out along the segment. This is the winding number of + /// the area just counter-clockwise (to the left, from your point of view) + /// of the segment. + pub counter_clockwise: W, + /// This half-segment is incident to a point. Imagine you're standing at + /// that point, looking out along the segment. This is the winding number of + /// the area just clockwise (to the right, from your point of view) of the segment. + pub clockwise: W, +} + +impl HalfSegmentWindingNumbers { + /// A half-segment's winding numbers are trivial if they're the same on both sides. + /// In this case, the segment is invisible to the topology of the sets. + fn is_trivial(&self) -> bool { + self.counter_clockwise == self.clockwise + } +} + +impl std::fmt::Debug for HalfSegmentWindingNumbers { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "cw {:?} | cc {:?}", + self.clockwise, self.counter_clockwise + ) + } +} + +/// An index into the set of output segments. +/// +/// There's no compile-time magic preventing misuse of this index, but you +/// should only use this to index into the [`Topology`] that you got it from. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct OutputSegIdx(usize); + +impl OutputSegIdx { + /// Returns an index to the first half of this output segment. + pub fn first_half(self) -> HalfOutputSegIdx { + HalfOutputSegIdx { + idx: self, + first_half: true, + } + } + + /// Returns an index to the second half of this output segment. + pub fn second_half(self) -> HalfOutputSegIdx { + HalfOutputSegIdx { + idx: self, + first_half: false, + } + } +} + +/// A vector indexed by output segments. +/// +/// See [`OutputSegIdx`] for more about output segments. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct OutputSegVec { + inner: Vec, +} + +impl_typed_vec!(OutputSegVec, OutputSegIdx, "o"); + +/// An index that refers to one end of an output segment. +/// +/// The two ends of an output segment are sweep-line ordered: the "first" half +/// has a smaller `y` coordinate (or smaller `x` coordinate if the `y`s are +/// tied) than the "second" half. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct HalfOutputSegIdx { + idx: OutputSegIdx, + first_half: bool, +} + +impl HalfOutputSegIdx { + /// Returns the index pointing to the other end of this output segment. + pub fn other_half(self) -> Self { + Self { + idx: self.idx, + first_half: !self.first_half, + } + } + + /// Do we point to the first half of the output segment? + pub fn is_first_half(self) -> bool { + self.first_half + } +} + +impl std::fmt::Debug for HalfOutputSegIdx { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.first_half { + write!(f, "{:?}->", self.idx) + } else { + write!(f, "->{:?}", self.idx) + } + } +} + +/// A vector indexed by half-output segments. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct HalfOutputSegVec { + start: Vec, + end: Vec, +} + +impl HalfOutputSegVec { + /// Creates a new vector that can store `cap` output segments without re-allocating. + pub fn with_capacity(cap: usize) -> Self { + Self { + start: Vec::with_capacity(cap), + end: Vec::with_capacity(cap), + } + } +} + +impl HalfOutputSegVec { + /// Creates a new vector with `size` output segments. + /// + /// Both halves of each output segment are initialized to their default values. + pub fn with_size(size: usize) -> Self { + Self { + start: std::iter::from_fn(|| Some(T::default())) + .take(size) + .collect(), + end: std::iter::from_fn(|| Some(T::default())) + .take(size) + .collect(), + } + } +} + +impl std::fmt::Debug for HalfOutputSegVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct Entry<'a, T> { + idx: usize, + start: &'a T, + end: &'a T, + } + + impl std::fmt::Debug for Entry<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{idx:4}: {start:?} -> {end:?}", + idx = self.idx, + start = self.start, + end = self.end + ) + } + } + + let mut list = f.debug_list(); + for (idx, (start, end)) in self.start.iter().zip(&self.end).enumerate() { + list.entry(&Entry { idx, start, end }); + } + list.finish() + } +} + +impl Default for HalfOutputSegVec { + fn default() -> Self { + Self { + start: Vec::new(), + end: Vec::new(), + } + } +} + +impl std::ops::Index for HalfOutputSegVec { + type Output = T; + + fn index(&self, index: HalfOutputSegIdx) -> &Self::Output { + if index.first_half { + &self.start[index.idx.0] + } else { + &self.end[index.idx.0] + } + } +} + +impl std::ops::IndexMut for HalfOutputSegVec { + fn index_mut(&mut self, index: HalfOutputSegIdx) -> &mut T { + if index.first_half { + &mut self.start[index.idx.0] + } else { + &mut self.end[index.idx.0] + } + } +} + +impl std::ops::Index for OutputSegVec { + type Output = T; + + fn index(&self, index: HalfOutputSegIdx) -> &Self::Output { + &self.inner[index.idx.0] + } +} + +/// An index into the set of points. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct PointIdx(usize); + +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Hash, PartialEq, Eq)] +struct PointVec { + inner: Vec, +} + +impl_typed_vec!(PointVec, PointIdx, "p"); + +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +struct PointNeighbors { + clockwise: HalfOutputSegIdx, + counter_clockwise: HalfOutputSegIdx, +} + +impl std::fmt::Debug for PointNeighbors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?} o {:?}", self.counter_clockwise, self.clockwise) + } +} + +/// Consumes sweep-line output and computes topology. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug)] +pub struct Topology { + eps: f64, + correctness: Correctness, + /// The user-provided tags for each segment. + tag: SegVec, + /// For each input segment, this is the list of output segments that we've started + /// recording but haven't finished with. There can be up to three of them, because + /// consider a segment that passes through a sweep-line like this: + /// + /// ```text + /// / + /// / + /// (*) /---/ + /// / + /// / + /// ``` + /// + /// When we come to process the sweep-line at height (*), we'll already have the + /// unfinished output segment coming from above. But before dealing with it, we'll + /// first encounter the output segment pointing down and add an unfinished segment + /// for that. Then we'll add an output segment for the horizontal line and so + /// at that point there will be three unfinished output segments. + open_segs: SegVec>, + /// Winding numbers of each segment. + /// + /// This is sort of logically indexed by `HalfOutputSegIdx`, because we can look at the + /// `HalfSegmentWindingNumbers` for each `HalfOutputSegIdx`. But since the two halves of + /// the winding numbers are determined by one another, we only store the winding numbers + /// for the start half of the output segment. + winding: OutputSegVec>, + /// The output points. + points: PointVec, + /// The segment endpoints, as indices into `points`. + point_idx: HalfOutputSegVec, + /// For each output half-segment, its neighboring segments are the ones that share a point with it. + point_neighbors: HalfOutputSegVec, + /// Marks the output segments that have been deleted due to merges of coindident segments. + deleted: OutputSegVec, + /// For each non-horizontal output segment, if we go just a tiny bit south of its starting + /// position then which output segment is to the west? + /// + /// The map from a segment to its scan-west neighbor is always strictly decreasing (in the + /// index). This ensures that a scan will always terminate, and it also means that we can + /// build the contours in increasing `OutputSegIdx` order. + scan_west: OutputSegVec>, + /// For each non-horizontal output segment that's at the east edge of its sweep-line-range, + /// this is the segment to the east... but *only* if the segment to the east continues through + /// the sweep-line where this output segment started. + /// + /// That is, in this situation (where the `->` points to our sweep-line) there will be no + /// "scan_east" relation from s1 to s2: + /// + /// ```text + /// \ / + /// \ / + /// \ / + /// -> ◦ ◦ + /// | | + /// | | + /// | | + /// (s1) (s2) + /// ``` + /// + /// However, in this situation there will be a "scan_east" relation from s1 to s2. + /// + /// ```text + /// \ | + /// \ | + /// \ | + /// -> ◦ | + /// | | + /// | | + /// | | + /// (s1) (s2) + /// ``` + /// + /// The reason behind this seemingly-arbitrary choice is that in the former situation + /// it's harder to build up `scan_east` (because the output segment `s2` hasn't bee + /// created when we process `s1`) and it's redundant with the `scan_west` relation from + /// `s2` to `s1`. + scan_east: OutputSegVec>, + /// This stores all the situations where two segments become scan-line + /// neighbors because something in between dropped out. For example: + /// + /// ```text + /// | \ | | + /// | \ | | + /// | \| | + /// | ◦ | <- y + /// | | + /// | | + /// | | + /// (s1) (s2) + /// ``` + /// + /// Here we would add an entry `(y, s1, s2)` to `scan_after`. If `s2` + /// weren't there (and there was nothing else further right), we'd add `(y, + /// s1, None)` to denote that `s1` has no scan-east neighbor after `y`. + scan_after: Vec<(f64, Option, Option)>, + /// For each output segment, the input segment that it came from. Will probably become + /// private at some point (TODO) + pub orig_seg: OutputSegVec, + // TODO: probably don't have this owned + /// The collection of segments used to build this topology. Will probably become private + /// at some point (TODO) + #[cfg_attr(test, serde(skip))] + pub segments: Segments, + /// Does the sweep-line orientation of each output segment agree with its original + /// orientation from the input? + /// + /// In principle, this is redundant with the winding numbers. But since the winding + /// numbers are generic, that information isn't available to us. + positively_oriented: OutputSegVec, +} + +impl Topology { + /// Construct a new topology from a collection of paths and tags. + /// + /// See [`WindingNumber`] about the tags are for. + pub fn from_paths<'a, Iter>(paths: Iter, eps: f64) -> Result + where + Iter: IntoIterator, + { + Self::from_paths_with_correctness(paths, eps, Correctness::GoodEnough) + } + + /// Construct a new topology from a collection of paths and tags. + /// + /// See [`WindingNumber`] about the tags are for. + pub fn from_paths_with_correctness<'a, Iter>(paths: Iter, eps: f64, correctness: Correctness) -> Result + where + Iter: IntoIterator, + { + let mut segments = Segments::default(); + let mut tag = Vec::new(); + for (p, t) in paths { + segments.add_path_without_updating_enter_exit(p, true)?; + tag.resize(segments.len(), t); + } + segments.update_enter_exit(0); + segments.check_invariants(); + Ok(Self::from_segments( + segments, + SegVec::from_vec(tag), + eps, + correctness, + )) + } + + fn from_segments( + segments: Segments, + tag: SegVec, + eps: f64, + correctness: Correctness, + ) -> Self { + let mut ret = Self { + eps, + correctness, + tag, + open_segs: SegVec::with_size(segments.len()), + + // We have at least as many output segments as input segments, so preallocate enough for them. + winding: OutputSegVec::with_capacity(segments.len()), + points: PointVec::with_capacity(segments.len()), + point_idx: HalfOutputSegVec::with_capacity(segments.len()), + point_neighbors: HalfOutputSegVec::with_capacity(segments.len()), + deleted: OutputSegVec::with_capacity(segments.len()), + scan_west: OutputSegVec::with_capacity(segments.len()), + scan_east: OutputSegVec::with_capacity(segments.len()), + scan_after: Vec::new(), + orig_seg: OutputSegVec::with_capacity(segments.len()), + positively_oriented: OutputSegVec::with_capacity(segments.len()), + segments: Segments::default(), + }; + let mut sweep_state = Sweeper::new_with_correctness(&segments, eps, correctness); + let mut range_bufs = SweepLineRangeBuffers::default(); + let mut line_bufs = SweepLineBuffers::default(); + while let Some(mut line) = sweep_state.next_line(&mut line_bufs) { + while let Some(positions) = line.next_range(&mut range_bufs, &segments) { + let range = positions.seg_range(); + let scan_west_seg = if range.segs.start == 0 { + None + } else { + let prev_seg = positions.line().line_segment(range.segs.start - 1).unwrap(); + debug_assert!(!ret.open_segs[prev_seg].is_empty()); + ret.open_segs[prev_seg].front().copied() + }; + ret.process_sweep_line_range(positions, &segments, scan_west_seg); + } + } + ret.segments = segments; + + #[cfg(feature = "slow-asserts")] + ret.check_invariants(); + + ret.merge_coincident(); + + #[cfg(feature = "slow-asserts")] + ret.check_invariants(); + + ret + } + + /// We're working on building up a list of half-segments that all meet at a point. + /// Maybe we've done a few already, but there's a region where we may add more. + /// Something like this: + /// + /// ```text + /// \ + /// \ ? + /// \ ? + /// -----o ? + /// /| ? + /// / |? + /// / | + /// ``` + /// + /// `first_seg` is the most-counter-clockwise segment we've added so far + /// (the one pointing down in the picture above, and `last_seg` is the + /// most-clockwise one (pointing up-left in the picture above). These can be + /// `None` if we haven't actually added any segments left. + /// + /// This method adds more segments to the picture, starting at `last_seg` + /// and working clockwise. It works only with segment indices, so it's the + /// caller's responsibility to ensure that the geometry is correct, and that + /// the provided segments actually go in clockwise order (relative to each + /// other, and also relative to the segments we've already placed). + fn add_segs_clockwise( + &mut self, + first_seg: &mut Option, + last_seg: &mut Option, + segs: impl Iterator, + p: PointIdx, + ) { + for seg in segs { + self.point_idx[seg] = p; + if first_seg.is_none() { + *first_seg = Some(seg); + } + if let Some(last) = last_seg { + self.point_neighbors[*last].clockwise = seg; + self.point_neighbors[seg].counter_clockwise = *last; + } + *last_seg = Some(seg); + } + if let Some((first, last)) = first_seg.zip(*last_seg) { + self.point_neighbors[last].clockwise = first; + self.point_neighbors[first].counter_clockwise = last; + } + } + + /// Like `add_segs_clockwise`, but adds them on the other side. + fn add_segs_counter_clockwise( + &mut self, + first_seg: &mut Option, + last_seg: &mut Option, + segs: impl Iterator, + p: PointIdx, + ) { + for seg in segs { + self.point_idx[seg] = p; + if last_seg.is_none() { + *last_seg = Some(seg); + } + if let Some(first) = first_seg { + self.point_neighbors[*first].counter_clockwise = seg; + self.point_neighbors[seg].clockwise = *first; + } + *first_seg = Some(seg); + } + if let Some((first, last)) = first_seg.zip(*last_seg) { + self.point_neighbors[last].clockwise = first; + self.point_neighbors[first].counter_clockwise = last; + } + } + + /// Takes some segments where we've already placed the first half, and + /// gets ready to place the second half. + /// + /// The state-tracking is subtle and should be re-considered. The basic + /// issue is that (as discussed in the documentation for `open_segs`) a + /// single segment index can have three open half-segments at any one time, + /// so how do we know which one is ready for its second half? The short + /// answer is that we use a double-ended queue, and see `new_half_seg` + /// for how we use it. + fn second_half_segs<'a, 'slf: 'a>( + &'slf mut self, + segs: impl Iterator + 'a, + ) -> impl Iterator + 'a { + segs.map(|s| { + self.open_segs[s] + .pop_front() + .expect("should be open") + .second_half() + }) + } + + /// Creates a new half-segment. + /// + /// This needs to update the open segment state to be compatible with `second_half_segs`. + /// + /// The key is that we know the order that the segments are processed: any + /// horizontal segments will be closed first, followed by segments coming + /// from an earlier sweep-line, followed by segments extending down from + /// this sweep-line (which won't be closed until we move on to the next + /// sweep-line). Therefore, we push horizontal half-segments to the front + /// of the queue so that they can be taken next. We push non-horizontal + /// half-segments to the back of the queue, so that the older ones (coming + /// from the previous sweep-line) will get taken before the new ones. + fn new_half_seg( + &mut self, + idx: SegIdx, + p: PointIdx, + winding: HalfSegmentWindingNumbers, + horizontal: bool, + positively_oriented: bool, + ) -> OutputSegIdx { + let out_idx = OutputSegIdx(self.winding.inner.len()); + if horizontal { + self.open_segs[idx].push_front(out_idx); + } else { + self.open_segs[idx].push_back(out_idx); + } + self.point_idx.start.push(p); + self.point_idx + .end + // TODO: maybe an option instead of this weird sentinel + .push(PointIdx(usize::MAX)); + + let no_nbrs = PointNeighbors { + clockwise: out_idx.first_half(), + counter_clockwise: out_idx.first_half(), + }; + self.point_neighbors.start.push(no_nbrs); + self.point_neighbors.end.push(no_nbrs); + self.winding.inner.push(winding); + self.deleted.inner.push(false); + self.scan_west.inner.push(None); + self.scan_east.inner.push(None); + self.orig_seg.inner.push(idx); + self.positively_oriented.inner.push(positively_oriented); + out_idx + } + + fn process_sweep_line_range( + &mut self, + mut pos: SweepLineRange, + segments: &Segments, + mut scan_west: Option, + ) { + let y = pos.line().y(); + let mut winding = scan_west + .map(|idx| self.winding[idx].counter_clockwise.clone()) + .unwrap_or_default(); + + // A re-usable buffer for holding temporary lists of segment indices. We + // need to collect the output of second_half_segments because it holds + // a &mut self reference. + let mut seg_buf = Vec::new(); + let mut connected_segs = SegmentsConnectedAtX::default(); + // The last segment we saw that points down from this sweep line. + let mut last_connected_down_seg: Option = None; + + while let Some(next_x) = pos.x() { + let p = PointIdx(self.points.inner.len()); + self.points.inner.push(Point::new(next_x, y)); + // The first segment at our current point, in clockwise order. + let mut first_seg = None; + // The last segment at our current point, in clockwise order. + let mut last_seg = None; + + // Close off the horizontal segments from the previous point in this sweep-line. + let hsegs = pos.active_horizontals(); + seg_buf.clear(); + seg_buf.extend(self.second_half_segs(hsegs)); + self.add_segs_clockwise(&mut first_seg, &mut last_seg, seg_buf.iter().copied(), p); + + // Find all the segments that are connected to something above this sweep-line at next_x. + pos.update_segments_at_x(&mut connected_segs); + seg_buf.clear(); + seg_buf.extend(self.second_half_segs(connected_segs.connected_up())); + self.add_segs_clockwise(&mut first_seg, &mut last_seg, seg_buf.iter().copied(), p); + + // Then: gather the output segments from half-segments starting here and moving + // to later sweep-lines. Allocate new output segments for them. + // Also, calculate their winding numbers and update `winding`. + seg_buf.clear(); + for new_seg in connected_segs.connected_down() { + let prev_winding = winding.clone(); + let orientation = segments.positively_oriented(new_seg); + winding += W::single(self.tag[new_seg], !orientation); + let windings = HalfSegmentWindingNumbers { + clockwise: prev_winding, + counter_clockwise: winding.clone(), + }; + let half_seg = self.new_half_seg(new_seg, p, windings, false, orientation); + self.scan_west[half_seg] = scan_west; + scan_west = Some(half_seg); + seg_buf.push(half_seg.first_half()); + + last_connected_down_seg = Some(half_seg); + } + self.add_segs_counter_clockwise( + &mut first_seg, + &mut last_seg, + seg_buf.iter().copied(), + p, + ); + + // Bump the current x position, which will get rid of horizontals ending here + // and add any horizontals starting here. + pos.increase_x(); + + // Gather the output segments from horizontal segments starting + // here. Allocate new output segments for them and calculate their + // winding numbers. + let hsegs = pos.active_horizontals_and_orientations(); + + // We don't want to update our "global" winding number state because that's supposed + // to keep track of the winding number below the current sweep line. + let mut w = winding.clone(); + seg_buf.clear(); + for (new_seg, same_orientation) in hsegs { + let prev_w = w.clone(); + let orientation = same_orientation == segments.positively_oriented(new_seg); + w += W::single(self.tag[new_seg], !orientation); + let windings = HalfSegmentWindingNumbers { + counter_clockwise: w.clone(), + clockwise: prev_w, + }; + let half_seg = self.new_half_seg(new_seg, p, windings, true, orientation); + self.scan_west[half_seg] = scan_west; + seg_buf.push(half_seg.first_half()); + } + self.add_segs_counter_clockwise( + &mut first_seg, + &mut last_seg, + seg_buf.iter().copied(), + p, + ); + } + + if let Some(seg) = last_connected_down_seg + && let Some(east_nbr) = pos.line().line_entry(pos.seg_range().segs.end) + && !east_nbr.is_in_changed_interval() + { + self.scan_east[seg] = self.open_segs[east_nbr.seg].front().copied() + } + + // If something was connected up but nothing was connected down, let's remember that + // the scan-line order has changed. + if last_connected_down_seg.is_none() { + let seg_range = pos.seg_range().segs; + let west_nbr = seg_range + .start + .checked_sub(1) + .and_then(|idx| pos.line().line_segment(idx)); + let east_nbr = pos.line().line_segment(pos.seg_range().segs.end); + let west = west_nbr.map(|idx| *self.open_segs[idx].front().unwrap()); + let east = east_nbr.map(|idx| *self.open_segs[idx].front().unwrap()); + if west.is_some() || east.is_some() { + self.scan_after.push((y, west, east)); + } + } + } + + fn delete_half(&mut self, half_seg: HalfOutputSegIdx) { + let nbr = self.point_neighbors[half_seg]; + self.point_neighbors[nbr.clockwise].counter_clockwise = nbr.counter_clockwise; + self.point_neighbors[nbr.counter_clockwise].clockwise = nbr.clockwise; + } + + fn delete(&mut self, seg: OutputSegIdx) { + self.deleted[seg] = true; + self.delete_half(seg.first_half()); + self.delete_half(seg.second_half()); + } + + fn is_horizontal(&self, seg: OutputSegIdx) -> bool { + self.point(seg.first_half()).y == self.point(seg.second_half()).y + } + + /// After generating the topology, there's a good chance we end up with + /// coincident output segments. This method removes coincident segments. If + /// a collection of coincident segments has a net winding number of zero, + /// this method just removes them all. Otherwise, they are replaced by a + /// single segment. + /// + /// In principle, we could do this as we build the topology. The thing that + /// makes it a little bit tricky is that (except for horizontal segments) + /// we don't know whether two segments are coincident until we've processed + /// their second endpoint. + fn merge_coincident(&mut self) { + for idx in 0..self.winding.inner.len() { + let idx = OutputSegIdx(idx); + if self.deleted[idx] { + continue; + } + let cw_nbr = self.point_neighbors[idx.first_half()].clockwise; + if self.point_idx[idx.second_half()] == self.point_idx[cw_nbr.other_half()] { + // We've found two segments with the same starting and ending points, but + // that doesn't mean they're coincident! + // TODO: we could use the comparison cache here, if we could keep it around somewhere. + let y0 = self.point(idx.first_half()).y; + let y1 = self.point(idx.second_half()).y; + + // Because the closeness comparison below isn't transitive, we need to be careful + // to only merge sweep-line neighbors. As an example for what can go wrong, suppose + // we have 4 segments with the same starting and ending points. Let's say that + // their sweep-line order is a, b, c, d, and suppose that a and d are close but + // b and c aren't. Then merging a and d would be bad -- even if they're clockwise + // neighbors by going around the way that avoids b and c -- because it would mess up + // the winding numbers of b and c. + if y0 != y1 && self.scan_west[idx] != Some(cw_nbr.idx) { + continue; + } + if y0 != y1 { + let s1 = self.orig_seg[idx]; + let s2 = self.orig_seg[cw_nbr]; + let s1 = y_subsegment(self.segments[s1].to_kurbo_cubic(), y0, y1); + let s2 = y_subsegment(self.segments[s2].to_kurbo_cubic(), y0, y1); + if (s1.p0 - s2.p0).hypot() > self.eps + || (s1.p1 - s2.p1).hypot() > self.eps + || (s1.p2 - s2.p2).hypot() > self.eps + || (s1.p3 - s2.p3).hypot() > self.eps + { + continue; + } + } + + // All output segments are in sweep line order, so if they're + // coincident then they'd better both be first halves. + debug_assert!(cw_nbr.first_half); + self.delete(idx); + self.winding[cw_nbr.idx].counter_clockwise = + self.winding[idx].counter_clockwise.clone(); + + if self.winding[cw_nbr.idx].is_trivial() { + self.delete(cw_nbr.idx); + } + } + } + } + + /// Iterates over indices of all output segments. + pub fn segment_indices(&self) -> impl Iterator + '_ { + (0..self.winding.inner.len()) + .map(OutputSegIdx) + .filter(|i| !self.deleted[*i]) + } + + /// Returns the winding numbers of the region on the clockwise side of an + /// output half-segment. + pub fn winding_clockwise(&self, idx: HalfOutputSegIdx) -> &W { + if idx.first_half { + &self.winding[idx.idx].clockwise + } else { + &self.winding[idx.idx].counter_clockwise + } + } + + /// Returns the winding numbers of the region on the counter-clockwise side of an + /// output half-segment. + pub fn winding_counter_clockwise(&self, idx: HalfOutputSegIdx) -> &W { + if idx.first_half { + &self.winding[idx.idx].counter_clockwise + } else { + &self.winding[idx.idx].clockwise + } + } + + /// Returns the endpoint of an output half-segment. + pub fn point(&self, idx: HalfOutputSegIdx) -> &Point { + &self.points[self.point_idx[idx]] + } + + /// Checks whether two of the input sets to this topology are disjoint. + /// + /// The `inside1` and `inside2` callbacks test whether a winding number + /// is inside the first set or inside the second set, respectively. + /// + /// If the two input sets intersect at a vertex (but not along an edge), + /// they are considered disjoint. Also, the disjointness is tested after + /// some topological simplification, and so it might not "see" thin spikes. + /// + /// The result of this method is not guaranteed to agree with the output + /// of [`Topology::contours`] because there are approximations involved. + /// For example, we might return `true` even when the contours are just + /// barely overlapping. + pub fn is_disjoint_tags( + &self, + inside1: impl Fn(&W) -> bool, + inside2: impl Fn(&W) -> bool, + ) -> bool { + self.winding.indices().all(|out_idx| { + let w = &self.winding[out_idx]; + + let touches1 = inside1(&w.clockwise) || inside1(&w.counter_clockwise); + let touches2 = inside2(&w.clockwise) || inside2(&w.counter_clockwise); + dbg!(touches1, touches2); + !(touches1 && touches2) + }) + } + + /// Returns an iterator over all output segments from the given set. + /// + /// Note that this includes segments that were merged because they were coincident with something else. + fn out_segments_of(&self, tag: W::Tag) -> impl Iterator + use<'_, W> { + self.orig_seg + .indices() + .filter(move |&i| self.tag[self.orig_seg[i]] == tag) + } + + /// Checks whether one of the input sets has a "normal" boundary. + /// + /// We say the boundary is "normal" if the winding number just to the right + /// of the boundary is always 1. Our convention is that the winding number is 1 + /// for a clockwise curve in y-down coordinates. + /// + /// For example, this set (traced out by two disjoint contours) has a normal + /// boundary: + /// + /// ```text + /// ╭───>───╮ + /// │xxxxxxx│ + /// │xxxxxxx│ ╭───>───╮ + /// ╰───<───╯ │xxxxxxx│ + /// │xxxxxxx│ + /// ╰───<───╯ + /// ``` + /// + /// This example does not have a normal boundary, because one of the contours + /// goes the wrong way (and so its winding numbers are -1, not 1). + /// + /// ```text + /// ╭───>───╮ + /// │xxxxxxx│ + /// │xxxxxxx│ ╭───<───╮ + /// ╰───<───╯ │xxxxxxx│ + /// │xxxxxxx│ + /// ╰───>───╯ + /// ``` + /// + /// This example does not have a normal boundary, because it has some places with + /// a winding number of 2. + /// + /// ```text + /// ╭───>───╮ + /// │xxxxxxx│ + /// │xxxxx╭───<───╮ + /// ╰───<─│─╯xxxxx│ + /// │xxxxxxx│ + /// ╰───>───╯ + /// ``` + /// + /// Up to precision issues, the output of [`Topology::contours`] will + /// have a normal boundary, so `has_normal_contours` is a reasonable proxy + /// for checking whether an input set will be passed through unchanged by + /// `Topology::contours`. For example, suppose you're trying to compute the + /// difference between set `a` and set `b`. If [`Topology::is_disjoint`] + /// says that the two sets are disjoint and `has_normal_contours` returns + /// `true`, you can decide that the intersection is just `a`. This isn't + /// a hard guarantee that `Topology::contours` will return `a` exactly: + /// it probably won't, because of perturbations and ordering changes. + pub fn has_normal_contours(&self, tag: W::Tag) -> bool { + self.out_segments_of(tag).all(|out_idx| { + let w = &self.winding[out_idx]; + + // Find the winding number to the right, as we walk in the original + // orientation. + let w = if self.positively_oriented[out_idx] { + &w.clockwise + } else { + &w.counter_clockwise + }; + w.of_tag(tag) == W::single(tag, true) + }) + } + + /// The segments in `segs` form a closed path, and each one is the ending half + /// of its segment. + /// + /// This basically does two things: + /// - puts the segments in the correct orientation + /// - merges (if possible) segments that were divided because of the + /// monotonicity constraints. + fn segs_to_path( + &self, + segs: &[HalfOutputSegIdx], + positions: &OutputSegVec, + ) -> BezPath { + let mut ret = BezPath::default(); + let mut unfinished: Option = None; + + // Find a segment that can't be merged into the previous one. + let start_idx = segs + .iter() + .position(|s| self.can_merge(s.other_half(), positions).is_none()) + .unwrap(); + ret.move_to(self.point(segs[start_idx].other_half()).to_kurbo()); + for seg in segs[start_idx..].iter().chain(&segs[..start_idx]) { + let path = &positions[seg.idx]; + + // TODO: avoid the allocation in reverse_subpaths + let rev_path; + let oriented_path = if seg.is_first_half() { + rev_path = path.path.reverse_subpaths(); + &rev_path + } else { + &path.path + }; + // skip(1) leaves off the initial MoveTo, which is unnecessary + // because this path starts where the last one ended. + let mut elems = oriented_path.iter().skip(1); + if let (Some(our_t), Some(unfinished_t)) = + (self.can_merge(seg.other_half(), positions), unfinished) + { + // Reconstruct part of the input curve + let input = self.segments.input_segs[self.orig_seg[seg.idx]]; + let sub_input = if our_t > unfinished_t { + input.subsegment(unfinished_t..our_t) + } else { + input + .reverse() + .subsegment((1.0 - unfinished_t)..(1.0 - our_t)) + }; + // Maybe we should "fix up" the endpoints of `sub_input` to agree with + // what was in `positions`? It shouldn't matter too much, though: they + // should be close anyway, and any errors won't cause gaps in the path + // because we skip the MoveTo. + // + // unwrap: if unfinished had something, the path must have been non-empty. + *ret.elements_mut().last_mut().unwrap() = sub_input.as_path_el(); + // Skip the first element in this output segment, because we merged it + // to the previous output segment. + elems.next(); + + // If we could merge the beginning of this output segment *and* + // the end, it could be part of a longer merge. Therefore, we + // don't update `unfinished`. + if self.can_merge(*seg, positions).is_none() { + unfinished = None; + } + } else if let Some(t) = self.can_merge(*seg, positions) { + unfinished = Some(t); + } else { + unfinished = None; + } + ret.extend(elems); + } + + ret.close_path(); + ret + } + + /// Is this intersection point a simple one, where at most two output segments meet? + fn is_simple_point(&self, idx: HalfOutputSegIdx) -> bool { + self.point_neighbors[idx].clockwise == self.point_neighbors[idx].counter_clockwise + } + + /// Checks whether this output segment can merge with its neighbor. + /// + /// Imagine you have a single input segment that looks like this: + /// + /// ```text + /// / + /// /-x-\ / + /// / \ / + /// / \-x-/ + /// / + /// ``` + /// + /// `Segments` will turn it into three monotonic pieces (by splitting it at + /// the marked `x`s), but when generating output we try to put the pieces + /// back together into a single segment. We can't always do this, for example + /// if there is an intersection with something else: + /// + /// ```text + /// | + /// /-x-\ + /// / | \ + /// / | \ + /// ``` + /// + /// or a near-intersection that causes some segment to get perturbed. + /// + /// This function returns `Some` for a half-output-segment that can be + /// merged to its neighbor. The value in the `Some` is in the parameter + /// space of the original input segment, and it says how far that merge + /// can go. In pictures, suppose that the curved segment goes from `t=0` + /// at the left to `t=1` at the right. + /// + /// ```text + /// 1 -> <- 2 + /// /-x-\ + /// / \ + /// / \ + /// ``` + /// + /// The `1 -> <- 2` in the diagram assigns names to the two + /// half-output-segments that meet at the `x`. If you call this function + /// with for half-output-segment `1`, it will return `Some(0.0)`, because + /// the output segment ending at `1` can be merged from input parameter + /// `0.0` all the way to the `x`. If you call this function with + /// half-output-segment `1`, it will return `Some(1.0)`. + /// + /// ```text + /// 1 -> <- 2 + /// /-x-\ / + /// / \/ + /// / /\ + /// ``` + /// + /// Now imagine that there is some other intersection preventing + /// the entire half-output-segment `2` from merging. In this case, + /// this function will return something like `Some(0.8)`, because the + /// output segment ending at `2` can be merged with its neighbor + /// ending at `1`, but not all the way to `1.0`. + fn can_merge( + &self, + seg: HalfOutputSegIdx, + positions: &OutputSegVec, + ) -> Option { + let pos = &positions[seg.idx]; + let orig_idx = self.orig_seg[seg.idx]; + + // We're currently using y positions on the output segment to figure out + if self.segments[orig_idx].is_horizontal() { + return None; + } + + let extremal_seg_is_copied = if seg.is_first_half() { + pos.copied_idx == Some(0) + } else { + // "- 2" because copied_idx counts segments, not elements + pos.copied_idx == Some(pos.path.elements().len() - 2) + }; + + // Is the curve from `seg` to `seg.other_half()` oriented the same way + // as the input segment? There are two possible changes of orientation: + // the segment might have had its orientation switched from the input + // segment, and our current contour direction might not agree with the + // output segment's orientation. + let has_orig_orientation = + seg.is_first_half() == self.segments.positively_oriented(orig_idx); + + let start_y = self.point(seg).y; + let end_y = self.point(seg.other_half()).y; + let split_from_pred = self.segments.split_from_predecessor[orig_idx]; + let can_merge = if has_orig_orientation { + let seg_in_input_t = split_from_pred.0; + if (seg_in_input_t > 0.0) + && extremal_seg_is_copied + // Is this out seg the "first" one of the input seg? + && start_y == self.segments.oriented_start(orig_idx).y + { + let t = solve_t_for_y(self.segments[orig_idx].to_kurbo_cubic(), end_y); + Some(self.input_seg_t(seg.idx, t)) + } else { + None + } + } else { + let seg_in_input_t = split_from_pred.1; + if seg_in_input_t < 1.0 + && extremal_seg_is_copied + && start_y == self.segments.oriented_end(orig_idx).y + { + let t = solve_t_for_y(self.segments[orig_idx].to_kurbo_cubic(), end_y); + Some(self.input_seg_t(seg.idx, t)) + } else { + None + } + }; + if self.is_simple_point(seg) { + can_merge + } else { + None + } + } + + fn input_seg_t(&self, out_seg: OutputSegIdx, t: f64) -> f64 { + let orig_idx = self.orig_seg[out_seg]; + let split_from_pred = self.segments.split_from_predecessor[orig_idx]; + let ret = if self.segments.positively_oriented(orig_idx) { + split_from_pred.0 + t * (split_from_pred.1 - split_from_pred.0) + } else { + split_from_pred.1 - t * (split_from_pred.1 - split_from_pred.0) + }; + ret.clamp(0.0, 1.0) + } + + /// Returns the contours of some set defined by this topology. + /// + /// The callback function `inside` takes a winding number and returns `true` + /// if a point with that winding number should be in the resulting set. For example, + /// to compute a boolean "and" using the non-zero winding rule, `inside` should be + /// `|w| w.shape_a != 0 && w.shape_b != 0`. + pub fn contours(&self, inside: impl Fn(&W) -> bool) -> Contours { + let positions = self.compute_positions(); + self.contours_for_positions(inside, positions) + } + + fn contours_for_positions( + &self, + inside: impl Fn(&W) -> bool, + positions: OutputSegVec, + ) -> Contours { + // We walk contours in sweep-line order of their smallest point. This mostly ensures + // that we visit outer contours before we visit their children. However, when the inner + // and outer contours share a point, we run into a problem. For example: + // + // /---------o--------\ + // | / \ | + // | / \ | + // \ \ / / + // \ \_/ / + // \ / + // ------------- + // (where the top-middle point is supposed to have 4 segments coming out of it; it's + // a hard to draw it in ASCII). In this case, we'll "notice" the inner contour when + // we realize that we've visited a point twice. At that point, we extract the inner part + // into a separate contour and mark it as a child of the outer one. This requires some + // slightly sketch indexing, because we need to refer to the outer contour even though + // we haven't finished generating it. We solve this by reserving a slot for the unfinished + // outer contour as soon as we start walking it. + // + // You might worry, as I did, that this construction can be nested. In fact, it doesn't + // matter if it is because we always walk the contours with the set to our right. So if + // our inner loop above had a further inner loop (say, attached to its parent at the + // bottom of the parent) then our initial contour walk won't visit that innermost loop. It + // will get picked up on a subsequent walk, by which point its parent will already be + // constructed and have an index. + let mut ret = Contours::default(); + let mut seg_contour: Vec> = vec![None; self.winding.inner.len()]; + + let bdy = |idx: OutputSegIdx| -> bool { + inside(&self.winding[idx].clockwise) != inside(&self.winding[idx].counter_clockwise) + }; + + let mut visited = vec![false; self.winding.inner.len()]; + // Keep track of the points that were visited on this walk, so that if we re-visit a + // point we can split out an additional contour. + let mut last_visit = PointVec::with_capacity(self.points.inner.len()); + last_visit.inner.resize(self.points.inner.len(), None); + for idx in self.segment_indices() { + if visited[idx.0] { + continue; + } + + if !bdy(idx) { + continue; + } + + // We found a boundary segment. Let's start by scanning west to figure out where we + // are relative to existing contours. + let contour_idx = ContourIdx(ret.contours.len()); + let mut contour = Contour::default(); + let mut west_seg = self.scan_west[idx]; + while let Some(left) = west_seg { + if self.deleted[left] || !bdy(left) { + west_seg = self.scan_west[left]; + } else { + break; + } + } + if let Some(west) = west_seg { + if let Some(west_contour) = seg_contour[west.0] { + // Is the thing just to our west inside or outside the output set? + let outside = !inside(self.winding_counter_clockwise(west.first_half())); + if outside == ret.contours[west_contour.0].outer { + // They're an outer contour, and there's exterior between us and them, + // or they're an inner contour and there's interior between us. + // That means they're our sibling. + contour.parent = ret.contours[west_contour.0].parent; + contour.outer = outside; + debug_assert!(outside || contour.parent.is_some()); + } else { + // They're an outer contour, and there's interior between us and them, + // or they're an inner contour and there's exterior between us. + // That means they're our parent. + contour.parent = Some(west_contour); + contour.outer = !ret.contours[west_contour.0].outer; + } + } else { + panic!("I'm {idx:?}, west is {west:?}. Y u no have contour?"); + } + }; + // Reserve a space for the unfinished outer contour, as described above. + ret.contours.push(contour); + + // First, arrange the orientation so that the interior is on our + // right as we walk. + let (start, mut next) = if inside(self.winding_clockwise(idx.first_half())) { + (idx.first_half(), idx.second_half()) + } else { + (idx.second_half(), idx.first_half()) + }; + // `segs` collects the endpoint of each segment as we walk along it. + // "endpoint" here means "as we walk the contour;" it could be either a first_half + // or a second_half as far as `HalfOutputSegIdx` is concerned. + let mut segs = Vec::new(); + last_visit[self.point_idx[start]] = Some(0); + + debug_assert!(inside(self.winding_clockwise(start))); + loop { + visited[next.idx.0] = true; + + debug_assert!(inside(self.winding_counter_clockwise(next))); + debug_assert!(!inside(self.winding_clockwise(next))); + + // Walk counter-clockwise around the point until we find the next segment + // that's on the boundary. + let mut nbr = self.point_neighbors[next].counter_clockwise; + debug_assert!(inside(self.winding_clockwise(nbr))); + while inside(self.winding_counter_clockwise(nbr)) { + nbr = self.point_neighbors[nbr].counter_clockwise; + } + + segs.push(next); + if nbr == start { + break; + } + + let p = self.point_idx[nbr]; + let last_visit_idx = last_visit[p] + // We don't clean up `last_visit` when we finish walking a + // contour because it could be expensive if there are many + // small contours. This means that `last_visit[p]` could be + // a false positive from an earlier contours. We handle this + // by double-checking that it was actually a time when this + // contour visited `p`. + .filter(|&idx| idx < segs.len() && self.point_idx[segs[idx].other_half()] == p); + if let Some(seg_idx) = last_visit_idx { + // We repeated a point, meaning that we've found an inner contour. Extract + // it and remove it from the current contour. + + // seg_idx should point to the end of a segment whose start is at p. + debug_assert_eq!(self.point_idx[segs[seg_idx].other_half()], p); + + let loop_contour_idx = ContourIdx(ret.contours.len()); + for &seg in &segs[seg_idx..] { + seg_contour[seg.idx.0] = Some(loop_contour_idx); + } + ret.contours.push(Contour { + path: self.segs_to_path(&segs[seg_idx..], &positions), + parent: Some(contour_idx), + outer: !ret.contours[contour_idx.0].outer, + }); + segs.truncate(seg_idx); + // In principle, we should also be unsetting `last_visit` + // for all points in the contour we just removed. I *think* + // we don't need to, because it's impossible for the outer + // contour to visit any of them anyway. Should check this + // more carefully. + } else { + last_visit[p] = Some(segs.len()); + } + + next = nbr.other_half(); + } + for &seg in &segs { + seg_contour[seg.idx.0] = Some(contour_idx); + } + ret.contours[contour_idx.0].path = self.segs_to_path(&segs, &positions); + } + + ret + } + + /// Returns a rectangle bounding all of our segments. + pub fn bounding_box(&self) -> kurbo::Rect { + let mut rect = Rect::new( + f64::INFINITY, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + ); + for seg in self.segments.segments() { + rect = rect.union(seg.to_kurbo_cubic().bounding_box()); + } + rect + } + + // Doesn't account for deleted segments. I mean, they're still present in the output. + fn build_scan_line_orders(&self) -> ScanLineOrder { + let mut west_map: OutputSegVec)>> = + OutputSegVec::with_size(self.winding.len()); + let mut east_map: OutputSegVec)>> = + OutputSegVec::with_size(self.winding.len()); + + // This slightly funny iteration order ensures that we push everything + // in increasing y. `scan_west` (resp. east) contains the *first* west + // (resp east) neighbor of an output segment, so that's the one we + // should push into the west_map (resp east_map) first. + + // Filter out horizontal segments, because we don't care about them when + // doing scan line orders (they're present in scan_west because we do + // care about them when doing topology). + for idx in self.scan_west.indices().filter(|i| !self.is_horizontal(*i)) { + let y = self.point(idx.first_half()).y; + if let Some(west) = self.scan_west[idx] { + west_map[idx].push((y, Some(west))); + } + } + + for idx in self.scan_east.indices() { + let y = self.point(idx.first_half()).y; + if let Some(east) = self.scan_east[idx] { + east_map[idx].push((y, Some(east))); + // Double-check that we're inserting in order. + if let Some((last_y, _)) = west_map[east].last() { + debug_assert!(y > *last_y); + } + + west_map[east].push((y, Some(idx))); + } + } + + for idx in self.scan_west.indices().filter(|i| !self.is_horizontal(*i)) { + let y = self.point(idx.first_half()).y; + if let Some(west) = self.scan_west[idx] { + // Double-check that we're inserting in order. + if let Some((last_y, _)) = east_map[west].last() { + debug_assert!(y > *last_y); + } + east_map[west].push((y, Some(idx))); + } + } + + // Account for the scan-line changes that were triggered by segments + // ending and revealing other things behind them. It would be nice if + // we could process these in sweep-line order, to avoid the need to sort + // and dedup... + for &(y, west, east) in &self.scan_after { + if let Some(east) = east { + west_map[east].push((y, west)); + } + if let Some(west) = west { + east_map[west].push((y, east)); + } + } + for vec in &mut west_map.inner { + vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + vec.dedup(); + } + for vec in &mut east_map.inner { + vec.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + vec.dedup(); + } + + let ret = ScanLineOrder::new(west_map, east_map); + #[cfg(feature = "slow-asserts")] + ret.check_invariants(&self.orig_seg, &self.segments); + ret + } + + /// Computes paths for all the output segments. + pub fn compute_positions(&self) -> OutputSegVec { + match self.correctness { + Correctness::GoodEnough => self.compute_positions_good_enough(), + Correctness::ReallyCorrect => self.compute_positions_correct(), + } + } + + /// Computes paths for all the output segments. + /// + /// TODO: We should allow passing in an "inside" callback and then only do + /// positioning for the segments that are on the boundary. + fn compute_positions_good_enough(&self) -> OutputSegVec { + let mut output = OutputSegVec::with_size(self.orig_seg.len()); + for idx in self.orig_seg.indices() { + let start = self.points[self.point_idx[idx.first_half()]].to_kurbo(); + let end = self.points[self.point_idx[idx.second_half()]].to_kurbo(); + + let orig_idx = self.orig_seg[idx]; + let seg = &self.segments[orig_idx]; + + output[idx] = PositionedOutputSeg::from_seg(seg.to_kurbo_seg(), start, end); + } + output + } + + /// Computes paths for all the output segments, with strong correctness guarantees + /// for the orderings. + /// + /// The sweep-line decides on orderings between segments, which are approximately + /// correct but subject to numerical error. This method perturbs the segments so + /// that the sweep-line's ordering decisions are exactly correct. + /// + /// The main downside of this method (as compared with [`Topology::compute_positions_good_enough`]) + /// is that it may cause many more subdivisions. It's also usually slower. + fn compute_positions_correct(&self) -> OutputSegVec { + // TODO: reuse the cache from the sweep-line + let mut cmp = ComparisonCache::new(self.eps, self.eps / 2.0); + let mut endpoints = HalfOutputSegVec::with_size(self.orig_seg.len()); + for idx in self.orig_seg.indices() { + endpoints[idx.first_half()] = self.points[self.point_idx[idx.first_half()]].to_kurbo(); + endpoints[idx.second_half()] = + self.points[self.point_idx[idx.second_half()]].to_kurbo(); + } + + crate::position::compute_positions( + &self.segments, + &self.orig_seg, + &mut cmp, + &endpoints, + &self.build_scan_line_orders(), + // The sweep-line guarantees that any two segments coming within + // eps / 2 will get noticed by the sweep-line. That means we + // can perturb things by eps / 4 without causing any unexpected + // collisions. + self.eps / 4.0, + ) + } + + #[cfg(feature = "slow-asserts")] + fn check_invariants(&self) { + // Check that the winding numbers are locally consistent around every point. + for out_idx in self.segment_indices() { + if !self.deleted[out_idx] { + for half in [out_idx.first_half(), out_idx.second_half()] { + let cw_nbr = self.point_neighbors[half].clockwise; + if self.winding_clockwise(half) != self.winding_counter_clockwise(cw_nbr) { + #[cfg(feature = "debug-svg")] + { + dbg!(self); + let svg = self.dump_svg(|_| "black".to_owned()); + svg::save("out.svg", &svg).unwrap(); + } + dbg!(half, cw_nbr); + panic!(); + } + } + } + } + + // Check the continuity of contours. + let mut out_segs = SegVec::>::with_size(self.segments.len()); + for out_seg in self.winding.indices() { + out_segs[self.orig_seg[out_seg]].push(out_seg); + } + // For each segment, figure out the first and last points of its output-segment-polyline, + // in that segment's natural orientation. + let mut realized_endpoints = Vec::new(); + for (in_seg, out_segs) in out_segs.iter_mut() { + // Sort the out segments so that they're in the same order that the segment will + // visit them if it's traversed in sweep-line order. This is almost the same as + // sorting the out segments by sweep-line order, but there's one little wrinkle + // for horizontal segments: in a situation like this: + // + // / + // / + // o--o--o + // / + // / + // + // where there are multiple horizontal out segments on the same sweep line, we + // need to sort those segments relative to one another depending on the direction + // in which the input segment traverses that sweep line. + out_segs.sort_by(|&o1, &o2| { + let p11 = self.point(o1.first_half()); + let p12 = self.point(o1.second_half()); + let p21 = self.point(o2.first_half()); + let p22 = self.point(o2.second_half()); + let horiz1 = p11.y == p12.y; + let horiz2 = p21.y == p22.y; + + // Compare the y positions first, so that horizontal segments will get sorted + // after segments coming from above and before segments going down, no matter the x positions. + let cmp = (p11.y, p12.y).partial_cmp(&(p21.y, p22.y)).unwrap(); + let cmp = cmp.then((p11.x, p12.x).partial_cmp(&(p21.x, p22.x)).unwrap()); + if horiz1 && horiz2 { + // We can create multiple horizontal segments for a segment in the same + // sweep line, but currently we don't create any backtracking: all the + // horizontal segments we create have the same orientation. + debug_assert_eq!(self.positively_oriented[o1], self.positively_oriented[o2]); + if self.positively_oriented[o1] == self.segments.positively_oriented(in_seg) { + cmp + } else { + cmp.reverse() + } + } else { + cmp + } + }); + + let mut first = None; + let mut last = None; + + for &out_seg in &*out_segs { + let same_orientation = + self.positively_oriented[out_seg] == self.segments.positively_oriented(in_seg); + let (first_endpoint, second_endpoint) = if same_orientation { + (out_seg.first_half(), out_seg.second_half()) + } else { + (out_seg.second_half(), out_seg.first_half()) + }; + if first.is_none() { + first = Some(first_endpoint); + } + + // When walking the output segments in sweep-line order, the last endpoint of + // the previous one should be the first endpoint of this one. + if let Some(last) = last { + assert_eq!(self.point_idx[first_endpoint], self.point_idx[last]); + } + last = Some(second_endpoint); + } + + let first = first.unwrap(); + let last = last.unwrap(); + let (first, last) = if self.segments.positively_oriented(in_seg) { + (first, last) + } else { + (last, first) + }; + realized_endpoints.push((self.point_idx[first], self.point_idx[last])); + } + + for seg in self.segments.indices() { + if let Some(prev) = self.segments.contour_prev(seg) { + assert_eq!(realized_endpoints[prev.0].1, realized_endpoints[seg.0].0); + } + } + } + + /// Renders out our state as an svg. + #[cfg(feature = "debug-svg")] + pub fn dump_svg(&self, tag_color: impl Fn(W::Tag) -> String) -> svg::Document { + let mut bbox = Rect::new( + f64::INFINITY, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + ); + let mut document = svg::Document::new(); + let p = |point: Point| (point.x, point.y); + + for seg in self.segment_indices() { + let p0 = p(*self.point(seg.first_half())); + let p1 = p(*self.point(seg.second_half())); + bbox = bbox.union_pt(p0); + bbox = bbox.union_pt(p1); + } + + bbox = bbox.inset(10.0); + let bbox_size = bbox.width().max(bbox.height()); + let stroke_width = 1.0 * bbox_size / 1024.0; + let point_radius = 1.5 * bbox_size / 1024.0; + let font_size = 8.0 * bbox_size / 1024.0; + + for seg_idx in self.segments.indices() { + let mut data = svg::node::element::path::Data::new(); + let seg = self.segments[seg_idx].to_kurbo_cubic(); + let p0 = (seg.p0.x, seg.p0.y); + data = data.move_to(p0); + data = data.cubic_curve_to(( + (seg.p1.x, seg.p1.y), + (seg.p2.x, seg.p2.y), + (seg.p3.x, seg.p3.y), + )); + let color = tag_color(self.tag[seg_idx]); + let path = svg::node::element::Path::new() + .set("id", format!("{seg:?}")) + .set("class", format!("{:?}", seg_idx)) + .set("stroke", color) + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("opacity", 0.2) + .set("fill", "none") + .set("d", data); + document = document.add(path); + } + + for seg in self.segment_indices() { + let mut data = svg::node::element::path::Data::new(); + let p0 = p(*self.point(seg.first_half())); + let p1 = p(*self.point(seg.second_half())); + data = data.move_to(p0); + data = data.line_to(p1); + let color = tag_color(self.tag[self.orig_seg[seg]]); + let path = svg::node::element::Path::new() + .set("id", format!("{seg:?}")) + .set("class", format!("{:?}", self.orig_seg[seg])) + .set("stroke", color) + .set("stroke-width", stroke_width) + .set("stroke-linecap", "round") + .set("stroke-linejoin", "round") + .set("opacity", 0.2) + .set("fill", "none") + .set("d", data); + document = document.add(path); + + let text = svg::node::element::Text::new(format!("{seg:?}",)) + .set("font-size", font_size) + .set("text-anchor", "middle") + .set("x", (p0.0 + p1.0) / 2.0) + .set("y", (p0.1 + p1.1) / 2.0); + document = document.add(text); + } + + for p_idx in self.points.indices() { + let p = self.points[p_idx]; + let c = svg::node::element::Circle::new() + .set("id", format!("{p_idx:?}")) + .set("cx", p.x) + .set("cy", p.y) + .set("r", point_radius) + .set("stroke", "none") + .set("fill", "black"); + document = document.add(c); + + let text = svg::node::element::Text::new(format!("{p_idx:?}",)) + .set("font-size", font_size) + .set("text-anchor", "left") + .set("x", p.x) + .set("y", p.y); + document = document.add(text); + } + + document = document.set( + "viewBox", + (bbox.min_x(), bbox.min_y(), bbox.width(), bbox.height()), + ); + document + } +} + +impl Topology { + /// Construct a new topology from a single path. + pub fn from_path(path: &BezPath, eps: f64) -> Result { + Self::from_paths(std::iter::once((path, ())), eps) + } +} + +impl Topology { + /// Creates a new `Topology` for two collections of polylines and a given tolerance. + /// + /// Each "set" is a collection of sequences of points; each sequence of + /// points is interpreted as a closed polyline (i.e. the last point will be + /// connected back to the first one). + pub fn from_polylines_binary( + set_a: impl IntoIterator>, + set_b: impl IntoIterator>, + eps: f64, + ) -> Self { + let mut segments = Segments::default(); + let mut shape_a = Vec::new(); + segments.add_closed_polylines(set_a); + shape_a.resize(segments.len(), OneOfTwo::A); + segments.add_closed_polylines(set_b); + shape_a.resize(segments.len(), OneOfTwo::B); + Self::from_segments( + segments, + SegVec::from_vec(shape_a), + eps, + Correctness::GoodEnough, + ) + } + + /// Creates a new `Topology` from two Bézier paths. + /// + /// The two Bézier paths represent two different sets for the purpose of boolean set operations. + pub fn from_paths_binary( + set_a: &BezPath, + set_b: &BezPath, + eps: f64, + ) -> Result { + Self::from_paths([(set_a, OneOfTwo::A), (set_b, OneOfTwo::B)], eps) + } + + /// Checks whether the two input sets to this topology are disjoint. + /// + /// If the two input sets intersect at a vertex (but not along an edge), + /// they are considered disjoint. Also, the disjointness is tested after + /// some topological simplification, and so it might not "see" thin spikes. + /// + /// The result of this method is not guaranteed to agree with the output + /// of [`Topology::contours`] because there are approximations involved. + /// For example, we might return `true` even when the contours are just + /// barely overlapping. + pub fn is_disjoint(&self, fill_rule: FillRule) -> bool { + self.is_disjoint_tags( + |w| fill_rule.is_inside(w.shape_a), + |w| fill_rule.is_inside(w.shape_b), + ) + } +} + +/// One direction of a `ScanLineOrder`. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug)] +struct HalfScanLineOrder { + inner: OutputSegVec)>>, +} + +impl HalfScanLineOrder { + fn neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option { + // TODO: maybe binary search, if this might get big? + self.iter(seg) + .take_while(|(y0, _, _)| *y0 <= y) + .find(|(_, y1, _)| *y1 > y) + .and_then(|(_, _, idx)| idx) + } + + /// Returns an iterator over `(y0, y1, maybe_seg)`. + /// + /// Each item in the iterator tells us that `maybe_seg` is `seg`'s neighbor + /// between heights `y0` and `y1`. If `maybe_seg` is `None`, it means `seg` + /// has no neighbor between heights `y0` and `y1`. The y intervals in this + /// iterator are guaranteed to be in increasing order, which each `y0` equal to + /// the previous `y1`. The last `y1` will be `f64::INFINITY`. + fn iter( + &self, + seg: OutputSegIdx, + ) -> impl Iterator)> + '_ { + let ends = self.inner[seg] + .iter() + .map(|(y0, _)| *y0) + .skip(1) + .chain(std::iter::once(f64::INFINITY)); + self.inner[seg] + .iter() + .zip(ends) + .map(|((y0, maybe_seg), y1)| (*y0, y1, *maybe_seg)) + } + + fn close_neighbor_height_after( + &self, + seg: OutputSegIdx, + y: f64, + orig_seg: &OutputSegVec, + segs: &Segments, + cmp: &mut ComparisonCache, + ) -> Option { + for (_, y1, other_seg) in self.iter(seg) { + if y1 <= y { + continue; + } + + if let Some(other_seg) = other_seg { + let order = cmp.compare_segments(segs, orig_seg[seg], orig_seg[other_seg]); + let next_ish = order + .iter() + .take_while(|(order_y0, _, _)| *order_y0 < y1) + .filter(|(_, _, order)| *order == Order::Ish) + .find(|(order_y0, _, _)| *order_y0 >= y); + if let Some((order_y0, _, _)) = next_ish { + return Some(order_y0); + } + } + } + None + } +} + +/// A summary of all the east-west ordering relations between non-horizontal +/// output segments. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug)] +pub struct ScanLineOrder { + /// Each entry is a list of `(y, west_neighbor)`: starting at `y`, `west_neighbor` + /// is the segment to my west. The `y`s are in increasing order. + west_map: HalfScanLineOrder, + /// Each entry is a list of `(y, east_neighbor)`: starting at `y`, `east_neighbor` + /// is the segment to my east. The `y`s are in increasing order. + east_map: HalfScanLineOrder, +} + +impl ScanLineOrder { + fn new( + west: OutputSegVec)>>, + east: OutputSegVec)>>, + ) -> Self { + Self { + west_map: HalfScanLineOrder { inner: west }, + east_map: HalfScanLineOrder { inner: east }, + } + } + + /// Returns the neighbor to the west of `seg` just after height `y`. + pub fn west_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option { + self.west_map.neighbor_after(seg, y) + } + + /// Returns the neighbor to the east of `seg` just after height `y`. + pub fn east_neighbor_after(&self, seg: OutputSegIdx, y: f64) -> Option { + self.east_map.neighbor_after(seg, y) + } + + /// Returns the next height (greater than or equal to `y`) at which `seg` has a close + /// neighbor to the east. + pub fn close_east_neighbor_height_after( + &self, + seg: OutputSegIdx, + y: f64, + orig_seg: &OutputSegVec, + segs: &Segments, + cmp: &mut ComparisonCache, + ) -> Option { + self.east_map + .close_neighbor_height_after(seg, y, orig_seg, segs, cmp) + } + + /// Returns the next height (greater than or equal to `y`) at which `seg` has a close + /// neighbor to the west. + pub fn close_west_neighbor_height_after( + &self, + seg: OutputSegIdx, + y: f64, + orig_seg: &OutputSegVec, + segs: &Segments, + cmp: &mut ComparisonCache, + ) -> Option { + self.west_map + .close_neighbor_height_after(seg, y, orig_seg, segs, cmp) + } + + #[cfg(feature = "slow-asserts")] + fn check_invariants(&self, orig_seg: &OutputSegVec, segs: &Segments) { + for idx in self.east_map.inner.indices() { + for &(y, east_idx) in &self.east_map.inner[idx] { + if let Some(east_idx) = east_idx { + let seg = &segs[orig_seg[idx]]; + let east_seg = &segs[orig_seg[east_idx]]; + assert!(y >= seg.start().y && y >= east_seg.start().y); + assert!(y < seg.end().y && y < east_seg.end().y); + } + } + for &(y, west_idx) in &self.west_map.inner[idx] { + if let Some(west_idx) = west_idx { + let seg = &segs[orig_seg[idx]]; + let west_seg = &segs[orig_seg[west_idx]]; + assert!(y >= seg.start().y && y >= west_seg.start().y); + assert!(y < seg.end().y && y < west_seg.end().y); + } + } + } + } +} + +/// An index for a [`Contour`] within [`Contours`]. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct ContourIdx(pub usize); + +/// A simple, closed path. +/// +/// A contour has no repeated points, and its segments do not intersect. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug, PartialEq)] +pub struct Contour { + /// The contour's path, which is simple and closed. + pub path: BezPath, + + /// A contour can have a parent, so that sets with holes can be represented as nested contours. + /// For example, the shaded set below: + /// + /// ```text + /// ---------------------- + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxx/\xxxxxxxxx| + /// |xxxxxxxx/ \xxxxxxxx| + /// |xxxxxxx/ \xxxxxxx| + /// |xxxxxxx\ /xxxxxxx| + /// |xxxxxxxx\ /xxxxxxxx| + /// |xxxxxxxxx\/xxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// ---------------------- + /// ``` + /// + /// is represented as a square contour with no parent, and a diamond contour with the square + /// as its parent. + /// + /// A contour can share at most one point with its parent. For example, if you translate the + /// diamond above upwards until it touches the top of the square, it will share that top point + /// with its parent. You can't make them share two points, though: if you try to translate the + /// diamond to a corner... + /// + /// ```text + /// ---------------------- + /// |xx/\xxxxxxxxxxxxxxxx| + /// |x/ \xxxxxxxxxxxxxxx| + /// |/ \xxxxxxxxxxxxxx| + /// |\ /xxxxxxxxxxxxxx| + /// |x\ /xxxxxxxxxxxxxxx| + /// |xx\/xxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// ---------------------- + /// ``` + /// + /// ...then it will be interpreted as two contours without a parent/child relationship: + /// + /// ```text + /// ---- + /// |xx/ + /// |x/ + /// |/ + /// ``` + /// + /// and + /// + /// ```text + /// ------------------ + /// \xxxxxxxxxxxxxxxx| + /// \xxxxxxxxxxxxxxx| + /// \xxxxxxxxxxxxxx| + /// |\ /xxxxxxxxxxxxxx| + /// |x\ /xxxxxxxxxxxxxxx| + /// |xx\/xxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// |xxxxxxxxxxxxxxxxxxxx| + /// ---------------------- + /// ``` + /// + pub parent: Option, + + /// Whether this contour is "outer" or not. A contour with no parent is "outer", and + /// then they alternate: a contour is "outer" if and only if its parent isn't. + /// + /// As you walk along a contour, the "occupied" part of the set it represents is + /// on your right. This means that outer contours wind clockwise and inner + /// contours wind counter-clockwise. + pub outer: bool, +} + +impl Default for Contour { + fn default() -> Self { + Self { + path: BezPath::default(), + outer: true, + parent: None, + } + } +} + +/// A collection of [`Contour`]s, representing a set. +/// +/// Can be indexed with a [`ContourIdx`]. +/// +/// A `Contour` represents a set as a hierarchical collection of closed paths, where +/// each path has the set on its right (in a y-down coordinate system). A very simple +/// set is represented as just a single closed path: +/// +/// ```text +/// ╭───>───╮ +/// │xxxxxxx│ +/// │xxxxxxx│ +/// ╰───<───╯ +/// ``` +/// +/// (The `x`s represent the interior of the set, and the arrows show the orientation +/// of the curve.) +/// +/// Two disjoint sets are represented as two unrelated closed paths: +/// +/// ```text +/// ╭───>───╮ +/// │xxxxxxx│ +/// │xxxxxxx│ ╭───>───╮ +/// ╰───<───╯ │xxxxxxx│ +/// │xxxxxxx│ +/// ╰───<───╯ +/// ``` +/// +/// The hierarchical structure appears when you have sets with holes: a set with a single +/// hole is represented as a contour (the outer boundary) with a child contour (the inner +/// boundary). Notice how the curves are oriented so that the set is always on the right. +/// +/// ```text +/// ╭───>──────╮ +/// │xxxxxxxxxx│ +/// │xxx╭<─╮xxx│ +/// │xxx│ │xxx│ +/// │xxx╰─>╯xxx│ +/// │xxxxxxxxxx│ +/// ╰──────<───╯ +/// ``` +/// +/// A set can have multiple holes (and so a contour can have multiple children), +/// and those holes can contain more parts of the set. So in general, the +/// collection of contours forms a forest. +/// +/// Because of the way we organize the contour directions, the set described by +/// our contours has a winding number of 1 and its complement has a winding +/// number of 0. In particular, if you gather up all the contours and put them +/// in an SVG, it won't make a difference whether you fill them with a non-zero +/// or an even-odd fill rule. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Contours { + contours: Vec, +} + +impl Contours { + /// Returns all of the contour indices, grouped by containment. + /// + /// For each of the inner vecs, the first element is an outer contour with + /// no parent. All of the other contours in that inner vec lie inside that + /// outer contour. + pub fn grouped(&self) -> Vec> { + let mut children = vec![Vec::new(); self.contours.len()]; + let mut top_level = Vec::new(); + for i in 0..self.contours.len() { + if let Some(parent) = self.contours[i].parent { + children[parent.0].push(ContourIdx(i)); + } else { + top_level.push(ContourIdx(i)); + } + } + + let mut ret = Vec::with_capacity(top_level.len()); + for top in top_level { + let mut tree = Vec::new(); + fn visit(idx: ContourIdx, children: &[Vec], acc: &mut Vec) { + acc.push(idx); + for &child in &children[idx.0] { + visit(child, children, acc); + } + } + visit(top, &children, &mut tree); + ret.push(tree); + } + + ret + } + + /// Returns the number of contours in the collection. + pub const fn len(&self) -> usize { + self.contours.len() + } + + /// Returns `true` if the collection contains no contours. + pub const fn is_empty(&self) -> bool { + self.contours.is_empty() + } + + /// Iterates over all of the contours, ignoring the hierarchical structure. + /// + /// For example, if you're creating an SVG path out of all these contours then + /// you don't need the hierarchical structure: the SVG renderer can figure that + /// out by itself. + pub fn contours(&self) -> impl Iterator + '_ { + self.contours.iter() + } + + /// Creates an owned iterator over all of the contours, ignoring the hierarchical + /// structure. + /// + /// For example, if you're creating an SVG path out of all these contours then + /// you don't need the hierarchical structure: the SVG renderer can figure that + /// out by itself. + pub fn into_contours(self) -> impl Iterator { + self.contours.into_iter() + } +} + +impl std::ops::Index for Contours { + type Output = Contour; + + fn index(&self, index: ContourIdx) -> &Self::Output { + &self.contours[index.0] + } +} + +#[cfg(test)] +mod tests { + use kurbo::BezPath; + use proptest::prelude::*; + + use crate::{ + FillRule, SegIdx, + geom::Point, + order::ComparisonCache, + perturbation::{ + F64Perturbation, Perturbation, f64_perturbation, perturbation, realize_perturbation, + }, + }; + + use super::{OneOfTwo, Topology}; + + fn p(x: f64, y: f64) -> Point { + Point::new(x, y) + } + + const EMPTY: [[Point; 0]; 0] = []; + + #[test] + fn square() { + let segs = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]]; + let eps = 0.01; + let top = Topology::from_polylines_binary(segs, EMPTY, eps); + //check_intersections(&top); + + insta::assert_ron_snapshot!(top); + } + + #[test] + fn diamond() { + let segs = [[p(0.0, 0.0), p(1.0, 1.0), p(0.0, 2.0), p(-1.0, 1.0)]]; + let eps = 0.01; + let top = Topology::from_polylines_binary(segs, EMPTY, eps); + //check_intersections(&top); + + insta::assert_ron_snapshot!(top); + } + + #[test] + fn square_and_diamond() { + let square = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]]; + let diamond = [[p(0.0, 0.0), p(1.0, 1.0), p(0.0, 2.0), p(-1.0, 1.0)]]; + let eps = 0.01; + let top = Topology::from_polylines_binary(square, diamond, eps); + //check_intersections(&top); + + insta::assert_ron_snapshot!(top); + } + + #[test] + fn square_with_double_back() { + let segs = [[ + p(0.0, 0.0), + p(0.5, 0.0), + p(0.5, 0.5), + p(0.5, 0.0), + p(1.0, 0.0), + p(1.0, 1.0), + p(0.0, 1.0), + ]]; + let eps = 0.01; + let top = Topology::from_polylines_binary(segs, EMPTY, eps); + //check_intersections(&top); + + insta::assert_ron_snapshot!(top); + } + + #[test] + fn nested_squares() { + let outer = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]]; + let inner = [[p(-1.0, -1.0), p(1.0, -1.0), p(1.0, 1.0), p(-1.0, 1.0)]]; + let eps = 0.01; + let top = Topology::from_polylines_binary(outer, inner, eps); + let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0); + + insta::assert_ron_snapshot!((&top, contours, top.build_scan_line_orders())); + + let out_idx = top.orig_seg.indices().collect::>(); + let orders = top.build_scan_line_orders(); + assert_eq!(orders.west_neighbor_after(out_idx[0], -2.0), None); + assert_eq!(orders.east_neighbor_after(out_idx[0], -2.2), None); + assert_eq!( + orders.east_neighbor_after(out_idx[0], -2.0), + Some(out_idx[2]) + ); + assert_eq!( + orders.east_neighbor_after(out_idx[0], -1.5), + Some(out_idx[2]) + ); + assert_eq!( + orders.east_neighbor_after(out_idx[0], -1.0), + Some(out_idx[3]) + ); + assert_eq!( + orders.east_neighbor_after(out_idx[0], 1.0), + Some(out_idx[2]) + ); + // Maybe this is a little surprising, but it's what the current implementation does. + assert_eq!( + orders.east_neighbor_after(out_idx[0], 2.0), + Some(out_idx[2]) + ); + } + + #[test] + fn squares_with_gaps() { + let mid = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]]; + let left_right = [ + [p(-4.0, -1.0), p(-3.0, -1.0), p(-3.0, 1.0), p(-4.0, 1.0)], + [p(4.0, -1.0), p(3.0, -1.0), p(3.0, -0.5), p(4.0, -0.5)], + [p(4.0, 1.0), p(3.0, 1.0), p(3.0, 0.5), p(4.0, 0.5)], + ]; + let eps = 0.01; + let top = Topology::from_polylines_binary(mid, left_right, eps); + let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0); + + insta::assert_ron_snapshot!((&top, contours, top.build_scan_line_orders())); + } + + #[test] + fn close_neighbor_height() { + let big = [[p(0.0, 0.0), p(0.0, 10.0), p(1.0, 10.0), p(1.0, 0.0)]]; + let left = [ + [p(-10.0, 0.5), p(-0.25, 2.0), p(-10.0, 10.0)], + [p(-10.0, 0.5), p(-0.25, 10.0), p(-10.0, 10.0)], + ]; + let eps = 0.5; + let top = Topology::from_polylines_binary(big, left, eps); + + // The output segs coming from the left side of the big square. + let indices: Vec<_> = top + .orig_seg + .indices() + .filter(|i| top.orig_seg[*i] == SegIdx(0)) + .collect(); + + assert_eq!(indices.len(), 2); + let orders = top.build_scan_line_orders(); + let mut cmp = ComparisonCache::new(eps, eps / 2.0); + + let h = orders + .close_west_neighbor_height_after( + indices[0], + 0.0, + &top.orig_seg, + &top.segments, + &mut cmp, + ) + .unwrap(); + assert!((0.5..=2.0).contains(&h)); + + let h = orders + .close_west_neighbor_height_after( + indices[0], + 0.6, + &top.orig_seg, + &top.segments, + &mut cmp, + ) + .unwrap(); + assert!((0.5..=2.0).contains(&h)); + + let h = orders + .close_west_neighbor_height_after( + indices[1], + 5.0, + &top.orig_seg, + &top.segments, + &mut cmp, + ) + .unwrap(); + assert!((8.0..=10.0).contains(&h)); + } + + #[test] + fn inner_loop() { + let outer = [[p(-2.0, -2.0), p(2.0, -2.0), p(2.0, 2.0), p(-2.0, 2.0)]]; + let inners = [ + [p(-1.5, -1.0), p(0.0, 2.0), p(1.5, -1.0)], + [p(-0.1, 0.0), p(0.0, 2.0), p(0.1, 0.0)], + ]; + let eps = 0.01; + let top = Topology::from_polylines_binary(outer, inners, eps); + let contours = top.contours(|w| (w.shape_a + w.shape_b) % 2 != 0); + + insta::assert_ron_snapshot!((top, contours)); + } + + #[test] + fn merge_monotonic_segs() { + let mut bez = BezPath::default(); + bez.move_to((-1., 0.)); + // An "N-shaped" curve, so it divides into 3 monotonic pieces. + bez.curve_to((-1., -1.), (1., 1.), (1., 0.)); + // Finish off the shape in a way that doesn't cause any intersections. + bez.line_to((2., 4.)); + bez.line_to((-2., 4.)); + bez.close_path(); + + let top = Topology::from_path(&bez, 1e-6).unwrap(); + let contours = top.contours(|w| *w != 0); + + insta::assert_ron_snapshot!((top, contours)); + } + + #[test] + fn merge_monotonic_segs_with_cut() { + let mut bez = BezPath::default(); + bez.move_to((-1., 0.)); + // An "N-shaped" curve, so it divides into 3 monotonic pieces. + bez.curve_to((-1., -1.), (1., 1.), (1., 0.)); + // Finish off the shape in a way that cuts through the middle. + bez.close_path(); + + let top = Topology::from_path(&bez, 1e-6).unwrap(); + let contours = top.contours(|w| *w != 0); + + dbg!(&top.segments.split_from_predecessor); + insta::assert_ron_snapshot!((top, contours)); + } + + #[test] + fn disjoint() { + let s1 = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]]; + let s2 = [[p(2.0, 0.0), p(3.0, 0.0), p(3.0, 1.0), p(2.0, 1.0)]]; + assert!(Topology::from_polylines_binary(s1, s2, 1e-5).is_disjoint(FillRule::EvenOdd)); + + // These touch at a corner: still disjoint. + let s2 = [[p(1.0, 0.0), p(3.0, 0.0), p(3.0, 1.0), p(2.0, 1.0)]]; + assert!(Topology::from_polylines_binary(s1, s2, 1e-5).is_disjoint(FillRule::EvenOdd)); + + // These touch on an edge: not disjoint. + let s2 = [[p(1.0, 0.0), p(3.0, 0.0), p(3.0, 1.0), p(1.0, 1.0)]]; + assert!(!Topology::from_polylines_binary(s1, s2, 1e-5).is_disjoint(FillRule::EvenOdd)); + + // These overlap fully. + let s2 = [[p(0.5, 0.5), p(1.5, 0.5), p(1.5, 1.5), p(0.5, 1.5)]]; + assert!(!Topology::from_polylines_binary(s1, s2, 1e-5).is_disjoint(FillRule::EvenOdd)); + + // The second set is repeated, so according to the EvenOdd fill rule it's empty. + // Note that we make the second set completely contain the first for this test, + // because we don't really specify what happens exactly at the doubled-up lines. + let s2 = [ + [p(-0.5, -0.5), p(1.5, -0.5), p(1.5, 1.5), p(-0.5, 1.5)], + [p(-0.5, -0.5), p(1.5, -0.5), p(1.5, 1.5), p(-0.5, 1.5)], + ]; + assert!(Topology::from_polylines_binary(s1, s2, 1e-5).is_disjoint(FillRule::EvenOdd)); + } + + #[test] + fn normal_contours() { + let s = [[p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)]]; + assert!(Topology::from_polylines_binary(s, EMPTY, 1e-5).has_normal_contours(OneOfTwo::A)); + + // This one goes the wrong way around. + let s = [[p(0.0, 0.0), p(0.0, 1.0), p(1.0, 1.0), p(1.0, 0.0)]]; + assert!(!Topology::from_polylines_binary(s, EMPTY, 1e-5).has_normal_contours(OneOfTwo::A)); + + let s = [ + [p(0.0, 0.0), p(4.0, 0.0), p(4.0, 4.0), p(0.0, 4.0)], + [p(1.0, 1.0), p(1.0, 2.0), p(2.0, 2.0), p(2.0, 1.0)], + ]; + assert!(Topology::from_polylines_binary(s, EMPTY, 1e-5).has_normal_contours(OneOfTwo::A)); + + // The inner contour goes the wrong way + let s = [ + [p(0.0, 0.0), p(0.0, 4.0), p(4.0, 4.0), p(4.0, 0.0)], + [p(1.0, 1.0), p(2.0, 1.0), p(2.0, 2.0), p(1.0, 2.0)], + ]; + assert!(!Topology::from_polylines_binary(s, EMPTY, 1e-5).has_normal_contours(OneOfTwo::A)); + + // Overlapping squares + let s = [ + [p(0.0, 0.0), p(1.0, 0.0), p(1.0, 1.0), p(0.0, 1.0)], + [p(0.5, 0.5), p(1.5, 0.5), p(1.5, 1.5), p(0.5, 1.5)], + ]; + assert!(!Topology::from_polylines_binary(s, EMPTY, 1e-5).has_normal_contours(OneOfTwo::A)); + } + + #[test] + fn invalid_sweep_order_repro() { + let polys = [ + vec![p(1.0, 0.33681665819045015), p(1.0, -1.0)], + vec![p(2.0, 0.0), p(0.9109561480029991, 1.000000000000005)], + vec![ + p(1.0, 1.0), + p(1.0, -1.0), + p(1.0877400346065769, 1.0000000000000102), + p(1.0, -1.0), + ], + ]; + let _top = Topology::from_polylines_binary(polys, EMPTY, 0.1); + } + + #[test] + fn nested_loop_heirarchy() { + // For a doubly-nested loop like this: + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / ________ \ + // / \ /\ / \ + // / \ └┘ / \ + // / \ / \ + // / \/ \ + // ------------------------- + // Our contour-walking (which always produces simple curves) is supposed + // to create three nested contours. + let mut outer_triangle = BezPath::new(); + outer_triangle.move_to((0.0, -100.0)); + outer_triangle.line_to((100.0, 0.0)); + outer_triangle.line_to((-100.0, 0.0)); + outer_triangle.close_path(); + + let mut mid_triangle = outer_triangle; + mid_triangle.move_to((0.0, 0.0)); + mid_triangle.line_to((10.0, -10.0)); + mid_triangle.line_to((-10.0, -10.0)); + mid_triangle.close_path(); + + let mut inner_triangle = mid_triangle.clone(); + inner_triangle.move_to((0.0, -10.0)); + inner_triangle.line_to((1.0, -9.0)); + inner_triangle.line_to((-1.0, -9.0)); + inner_triangle.close_path(); + + let top = Topology::from_path(&inner_triangle, 1e-6).unwrap(); + let contours = top.contours(|&w| w % 2 != 0); + let contours = contours.contours().collect::>(); + assert_eq!(contours.len(), 3); + assert_eq!(contours[1].parent.unwrap().0, 0); + assert!(!contours[1].outer); + assert_eq!(contours[2].parent.unwrap().0, 1); + assert!(contours[2].outer); + + // If we flip the innermost loop, it gets parented to the outermost. + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / ________ \ + // / \ /\ / \ + // / \ └┘ / \ + // / \ / \ + // / \/ \ + // ------------------------- + // Our contour-walking (which always produces simple curves) is supposed + // to create three nested contours. + let mut flipped_inner_triangle = mid_triangle; + flipped_inner_triangle.move_to((0.0, -10.0)); + flipped_inner_triangle.line_to((1.0, -11.0)); + flipped_inner_triangle.line_to((-1.0, -11.0)); + flipped_inner_triangle.close_path(); + let top = Topology::from_path(&flipped_inner_triangle, 1e-6).unwrap(); + let contours = top.contours(|&w| w % 2 != 0); + let contours = contours.contours().collect::>(); + assert_eq!(contours.len(), 3); + assert_eq!(contours[1].parent.unwrap().0, 0); + assert!(!contours[1].outer); + assert_eq!(contours[2].parent.unwrap().0, 0); + assert!(!contours[2].outer); + } + + // Checks that all output segments intersect one another only at endpoints. + // fn check_intersections(top: &Topology) { + // for i in 0..top.winding.inner.len() { + // for j in (i + 1)..top.winding.inner.len() { + // let i = OutputSegIdx(i); + // let j = OutputSegIdx(j); + + // let p0 = top.point(i.first_half()).clone(); + // let p1 = top.point(i.second_half()).clone(); + // let q0 = top.point(j.first_half()).clone(); + // let q1 = top.point(j.second_half()).clone(); + + // let s = Segment::new(p0.clone().min(p1.clone()), p1.max(p0)).to_exact(); + // let t = Segment::new(q0.clone().min(q1.clone()), q1.max(q0)).to_exact(); + + // if s.end.y >= t.start.y && t.end.y >= s.start.y { + // // FIXME + // // if let Some(y) = s.exact_intersection_y(&t) { + // // dbg!(y); + // // assert!( + // // s.start == t.start + // // || s.start == t.end + // // || s.end == t.start + // // || s.end == t.end + // // ); + // // } + // } + // } + // } + // } + + fn run_perturbation(ps: Vec>) { + let base = vec![vec![ + p(0.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + p(2.0, 0.0), + p(1.0, 1.0), + p(1.0, -1.0), + ]]; + let perturbed_polylines = ps + .iter() + .map(|p| realize_perturbation(&base, p)) + .collect::>(); + let eps = 0.1; + let _top = Topology::from_polylines_binary(perturbed_polylines, EMPTY, eps); + //check_intersections(&top); + } + + proptest! { + #[test] + fn perturbation_test_f64(perturbations in prop::collection::vec(perturbation(f64_perturbation(0.1)), 1..5)) { + run_perturbation(perturbations); + } + } +} diff --git a/vendor/linesweeper/src/treevec.rs b/vendor/linesweeper/src/treevec.rs new file mode 100644 index 00000000..ff17270b --- /dev/null +++ b/vendor/linesweeper/src/treevec.rs @@ -0,0 +1,955 @@ +use arrayvec::ArrayVec; + +#[cfg(test)] +use serde::ser::SerializeSeq; + +/// A container with fast random access and random insertion/deletion. +/// +/// The implementation is based on a short, wide, more-or-less-balanced tree. +/// You get to choose how wide it is: `B` is the maximum size of each node, +/// and we ensure that every node but the root has at least `B/2` elements. +/// The depth of the tree is therefore of order `log_B (n)`, where `n` +/// is the number of elements. +#[derive(Clone, Debug)] +pub struct TreeVec { + root: Box>, +} + +#[cfg(test)] +impl serde::Serialize for TreeVec { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for x in self.iter() { + seq.serialize_element(x)?; + } + seq.end() + } +} + +/// A single node in the tree. +#[cfg_attr(test, derive(serde::Serialize))] +#[derive(Clone, Debug)] +enum Node { + Leaf { + /// Guaranteed to have at least `B/2` elements (and in particular, + /// to be non-empty) *except* if this node happens to be the root + /// of the tree. + data: ArrayVec, + }, + /// An internal node. The two arrays, `size` and `children`, are guaranteed + /// to be non-empty. And unless this node is the root of the tree, + /// they will have at least `B/2` elements. + Internal { + /// For each child, the number of elements in its subtree. + /// + /// (And so the number of elements in my subtree is the sum of these.) + size: ArrayVec, + /// The children. Each subtree is guaranteed to have the same height. + children: ArrayVec>, B>, + }, +} + +/// The result of inserting an element into a subtree. +enum InsertResult { + /// It fits! + Done, + /// It didn't fit, and the subtree had to be split into two subtrees. + /// The node in here is the second of the two subtrees. + Split(Box>), +} + +/// The result of removing an element from a subtree. +enum RemoveResult { + /// It's gone! + Done(T), + /// It's gone, but the root of the subtree now has too few children. + Undersize(T), +} + +impl RemoveResult { + fn into_inner(self) -> T { + match self { + RemoveResult::Done(ret) | RemoveResult::Undersize(ret) => ret, + } + } +} + +/// The result of fixing up the sizes of two sibling subtrees. +enum MergeResult { + /// The two subtrees both fit in a single node: one has been drained and added + /// to the other. + Absorbed, + /// Some elements were moved from one subtree to another, and now they both + /// have valid sizes. + Rebalanced, +} + +/// Which subtree does the given offset belong to? +/// +/// Returns the index of the subtree containing that offset, and the offset +/// relative to the subtree. +fn child_idx(sizes: &[usize], mut offset: usize) -> Option<(usize, usize)> { + for (idx, &size) in sizes.iter().enumerate() { + if size > offset { + return Some((idx, offset)); + } + offset -= size; + } + None +} + +impl Node { + /// Returns the size of the subtree rooted at this node. + fn subtree_size(&self) -> usize { + match self { + Node::Leaf { data } => data.len(), + Node::Internal { size, .. } => size.iter().copied().sum(), + } + } + + /// Gets the item at offset `offset`, relative to the subtree rooted at this node. + fn get(&self, offset: usize) -> Option<&T> { + match self { + Node::Leaf { data } => data.get(offset), + Node::Internal { size, children } => { + let (idx, offset) = child_idx(size, offset)?; + children[idx].get(offset) + } + } + } + + /// Gets (mutably) the item at offset `offset`, relative to the subtree rooted at this node. + fn get_mut(&mut self, offset: usize) -> Option<&mut T> { + match self { + Node::Leaf { data } => data.get_mut(offset), + Node::Internal { size, children } => { + let (idx, offset) = child_idx(size, offset)?; + children[idx].get_mut(offset) + } + } + } + + /// Returns the first item in the subtree. + /// + /// `tree.first()` is equivalent to `tree.get(0)`, but faster. + fn first(&self) -> Option<&T> { + match self { + Node::Leaf { data } => data.first(), + Node::Internal { children, .. } => children.first().and_then(|c| c.first()), + } + } + + /// Inserts an element into the subtree rooted at this node. + /// + /// The offset is relative to this subtree. + /// + /// If this insertion causes the subtree to overflow, the root node will be split. + /// In this case, this node will be modified, and the new sibling will be returned. + fn insert(&mut self, offset: usize, element: T) -> InsertResult { + match self { + Node::Leaf { data } => { + if data.is_full() { + let mut second_half: ArrayVec = data.drain(B / 2..).collect(); + if offset <= B / 2 { + data.insert(offset, element) + } else { + second_half.insert(offset - B / 2, element) + } + InsertResult::Split(Box::new(Node::Leaf { data: second_half })) + } else { + data.insert(offset, element); + InsertResult::Done + } + } + Node::Internal { size, children } => { + let (idx, offset) = if offset > 0 { + // unwrap: if this fails, it's out-of-bounds + let (idx, offset) = child_idx(size, offset - 1).unwrap(); + (idx, offset + 1) + } else { + (0, 0) + }; + match children[idx].insert(offset, element) { + InsertResult::Done => { + size[idx] += 1; + InsertResult::Done + } + InsertResult::Split(node) => { + size[idx] = children[idx].subtree_size(); + + if children.is_full() { + let mut second_half_children: ArrayVec<_, B> = + children.drain(B / 2..).collect(); + let mut second_half_size: ArrayVec<_, B> = + size.drain(B / 2..).collect(); + if idx < B / 2 { + size.insert(idx + 1, node.subtree_size()); + children.insert(idx + 1, node); + } else { + second_half_size.insert(idx + 1 - B / 2, node.subtree_size()); + second_half_children.insert(idx + 1 - B / 2, node); + } + InsertResult::Split(Box::new(Node::Internal { + size: second_half_size, + children: second_half_children, + })) + } else { + size.insert(idx + 1, node.subtree_size()); + children.insert(idx + 1, node); + InsertResult::Done + } + } + } + } + } + } + + /// We're undersized (by one; we only support removing a single element at a time) + /// and maybe our right_sibling can help by giving us some more elements. + fn merge_from_right(&mut self, right_sibling: &mut Node) -> MergeResult { + match (self, right_sibling) { + (Node::Leaf { data: left_data }, Node::Leaf { data: right_data }) => { + debug_assert!(right_data.len() >= left_data.len()); + if left_data.len() + right_data.len() <= B { + left_data.extend(right_data.drain(..)); + MergeResult::Absorbed + } else { + // Since we're only undersized by one, we could just move a single + // element over. It's probably more efficient to rebalance more + // aggressively, so we can avoid rebalancing in the future. So + // here we try to equalize the sizes. + let count = (right_data.len() - left_data.len()) / 2; + // We're undersized, and since the two of us don't fit in a single + // node, right_data must have at least B/2 + 1 elements. Therefore + // the difference in sizes is at least 2. + debug_assert!(count > 0); + left_data.extend(right_data.drain(..count)); + MergeResult::Rebalanced + } + } + ( + Node::Internal { + size: left_size, + children: left_children, + }, + Node::Internal { + size: right_size, + children: right_children, + }, + ) => { + if left_children.len() + right_children.len() <= B { + left_size.extend(right_size.drain(..)); + left_children.extend(right_children.drain(..)); + MergeResult::Absorbed + } else { + let count = (right_children.len() - left_children.len()) / 2; + debug_assert!(count > 0); + left_children.extend(right_children.drain(..count)); + left_size.extend(right_size.drain(..count)); + MergeResult::Rebalanced + } + } + _ => unreachable!(), + } + } + + /// We're undersized (by one; we only support removing a single element at a time) + /// and maybe our right_sibling can help by giving us some more elements. + fn merge_from_left(&mut self, left_sibling: &mut Node) -> MergeResult { + match (left_sibling, self) { + (Node::Leaf { data: left_data }, Node::Leaf { data: right_data }) => { + debug_assert!(right_data.len() <= left_data.len()); + if left_data.len() + right_data.len() <= B { + // There's no efficient and safe way to insert a bunch of + // data at the beginning of right, so instead we append to + // left and then swap them. + left_data.extend(right_data.drain(..)); + std::mem::swap(left_data, right_data); + MergeResult::Absorbed + } else { + // Unlike merge_from_right, here we only move a single element + // from the left to the right. This is just because safe rust + // makes it tricky to efficiently move more; ideally we'd also + // be rebalancing here. + right_data.insert(0, left_data.pop().unwrap()); + MergeResult::Rebalanced + } + } + ( + Node::Internal { + size: left_size, + children: left_children, + }, + Node::Internal { + size: right_size, + children: right_children, + }, + ) => { + if left_children.len() + right_children.len() <= B { + left_size.extend(right_size.drain(..)); + left_children.extend(right_children.drain(..)); + std::mem::swap(left_children, right_children); + std::mem::swap(left_size, right_size); + MergeResult::Absorbed + } else { + right_children.insert(0, left_children.pop().unwrap()); + right_size.insert(0, left_size.pop().unwrap()); + MergeResult::Rebalanced + } + } + _ => unreachable!(), + } + } + + /// Remove an element from a given offset in this subtree. + /// + /// This may leave the root of the subtree undersized, in which case the + /// return value will let you know. + fn remove(&mut self, offset: usize) -> RemoveResult { + match self { + Node::Leaf { data } => { + let ret = data.remove(offset); + if data.len() < B / 2 { + RemoveResult::Undersize(ret) + } else { + RemoveResult::Done(ret) + } + } + Node::Internal { size, children } => { + let (idx, offset) = child_idx(size, offset).unwrap(); + size[idx] -= 1; + match children[idx].remove(offset) { + RemoveResult::Done(ret) => RemoveResult::Done(ret), + RemoveResult::Undersize(ret) => { + // We now have an undersized node at index i. We try + // to merge in more from the right neighbor (because + // that's the more efficient merge direction in our + // implementation). But if `i` is the last element, we + // fall back to merging from the left. + if idx + 1 < children.len() { + // This little incantation seems to be the easiest + // (stable, safe) way to get two &muts to different + // elements. + let (a, b) = children.split_at_mut(idx + 1); + let cur = a.last_mut().unwrap(); + let next = b.first_mut().unwrap(); + + match cur.merge_from_right(next) { + MergeResult::Absorbed => { + size[idx] = cur.subtree_size(); + + children.remove(idx + 1); + size.remove(idx + 1); + + if children.len() < B / 2 { + RemoveResult::Undersize(ret) + } else { + RemoveResult::Done(ret) + } + } + MergeResult::Rebalanced => { + size[idx] = cur.subtree_size(); + size[idx + 1] = next.subtree_size(); + RemoveResult::Done(ret) + } + } + } else { + // We couldn't merge from the right, since `idx` + // is at the end. Since internal nodes can't be + // undersized, as long as B >= 4 we must have at + // least 2 elements and so there is something to + // our left. (Maybe we should have something to + // ensure that B >= 4?) + debug_assert!(idx > 0); + + let (a, b) = children.split_at_mut(idx); + let prev = a.last_mut().unwrap(); + let cur = b.first_mut().unwrap(); + + match cur.merge_from_left(prev) { + MergeResult::Absorbed => { + size[idx] = cur.subtree_size(); + + children.remove(idx - 1); + size.remove(idx - 1); + if children.len() < B / 2 { + RemoveResult::Undersize(ret) + } else { + RemoveResult::Done(ret) + } + } + MergeResult::Rebalanced => { + size[idx - 1] = prev.subtree_size(); + size[idx] = cur.subtree_size(); + RemoveResult::Done(ret) + } + } + } + } + } + } + } + } + + /// Returns the offset (in this subtree) of the first element for which `pred` returns false. + fn partition_point

(&self, mut pred: P) -> usize + where + P: FnMut(&T) -> bool, + { + match self { + Node::Leaf { data } => data.partition_point(pred), + Node::Internal { children, size } => { + // It seems to be faster if we first do a binary search over our subtrees, + // and then we descend into the interesting subtree. + let child_idx = children.partition_point(|child| pred(child.first().unwrap())); + if child_idx > 0 { + children[child_idx - 1].partition_point(pred) + + size[..(child_idx - 1)].iter().copied().sum::() + } else { + 0 + } + } + } + } + + /// Assert that this subtree satisfies our invariants, panicking if not. + fn check_invariants(&self, is_root: bool) { + match self { + Node::Leaf { data } => { + if !is_root { + assert!(data.len() >= B / 2); + } + } + Node::Internal { size, children } => { + assert_eq!(size.len(), children.len()); + if !is_root { + assert!(size.len() >= B / 2); + } + + for (child, size) in children.iter().zip(size) { + assert_eq!(child.subtree_size(), *size); + + child.check_invariants(false); + } + } + } + } +} + +impl Default for TreeVec { + fn default() -> Self { + Self { + root: Box::new(Node::Leaf { + data: ArrayVec::new(), + }), + } + } +} + +impl TreeVec { + /// Constructs a new, empty, `TreeVec`. + /// + /// This allocates a little; we don't have a special-case non-allocating + /// `TreeVec` for empty containers. + pub fn new() -> Self { + Self::default() + } + + /// Returns true if we're empty. + pub fn is_empty(&self) -> bool { + match &*self.root { + Node::Leaf { data } => data.is_empty(), + Node::Internal { .. } => false, + } + } + + /// Returns the number of elements in this container. + pub fn len(&self) -> usize { + self.root.subtree_size() + } + + /// Gets a reference to the element at a specified index, or `None` if the + /// index is out-of-bounds. + /// + /// Runtime is linear in the height of the tree (logarithmic in the length). + pub fn get(&self, index: usize) -> Option<&T> { + self.root.get(index) + } + + /// Gets a mutable reference to the element at a specified index, or `None` + /// if the index is out-of-bounds. + /// + /// Runtime is linear in the height of the tree (logarithmic in the length). + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.root.get_mut(index) + } + + /// Inserts an element at a specified index, or panics if the index is + /// out-of-bounds. + /// + /// Runtime is linear in the height of the tree (logarithmic in the length), + /// and also linear in `B`. + pub fn insert(&mut self, index: usize, element: T) { + match self.root.insert(index, element) { + InsertResult::Done => {} + InsertResult::Split(node) => { + let mut root = Box::new(Node::Internal { + size: ArrayVec::new(), + children: ArrayVec::new(), + }); + std::mem::swap(&mut root, &mut self.root); + + let Node::Internal { size, children } = &mut *self.root else { + unreachable!(); + }; + size.push(root.subtree_size()); + size.push(node.subtree_size()); + children.push(root); + children.push(node); + } + } + } + + /// Inserts an element at a specified index, shifting all later elements + /// back by 1. + /// + /// Runtime is linear in the height of the tree (logarithmic in the length), + /// and also linear in `B`. + pub fn remove(&mut self, index: usize) -> T { + let ret = self.root.remove(index).into_inner(); + + if let Node::Internal { children, .. } = &mut *self.root + && children.len() == 1 + { + // unwrap: an internal node always has children + self.root = children.pop().unwrap() + } + ret + } + + /// Asserts that this subtree satisfies our invariants, panicking if not. + pub fn check_invariants(&self) { + self.root.check_invariants(true); + } + + /// Returns an iterator over all the elements in this container. + pub fn iter(&self) -> Iter<'_, T, B> { + let inner = match &*self.root { + Node::Leaf { data } => IterInner::Simple(data.iter()), + Node::Internal { .. } => { + let mut ret = TreeIter { + stack: Vec::new(), + leaf: [].iter(), + remaining: self.len(), + }; + ret.descend(&*self.root); + IterInner::Tree(ret) + } + }; + Iter { inner } + } + + /// Returns an iterator over mutable references to all the elements in this + /// container. + pub fn iter_mut(&mut self) -> IterMut<'_, T, B> { + let len = self.len(); + let inner = match &mut *self.root { + Node::Leaf { data } => IterMutInner::Simple(data.iter_mut()), + root @ Node::Internal { .. } => { + let mut ret = TreeIterMut { + stack: Vec::new(), + leaf: [].iter_mut(), + remaining: len, + }; + ret.descend(root); + IterMutInner::Tree(ret) + } + }; + IterMut { inner } + } + + /// Returns the index of the first element for which `pred` returns false. + /// + /// The above assumes that we're sorted in the sense that `pred` returns `true` for the first + /// bunch of elements and then `false` for the rest. If not, we don't necessarily return + /// the first offset for which `pred` if `false`, but we do return some offset where + /// `pred` is `false` but `pred` is `true` at `offset - 1`. (Here, corner cases are specified + /// by declaring that `pred` is `true` at offset `-1` and `false` at offset `len`. + pub fn partition_point

(&self, pred: P) -> usize + where + P: FnMut(&T) -> bool, + { + self.root.partition_point(pred) + } + + /// Turns a generic range bounds into an inclusive start and an exclusive end. + /// + /// This won't work if the range end is inclusive of `usize::MAX`, so don't do that. + fn normalize_bounds(&self, range: impl std::ops::RangeBounds) -> (usize, usize) { + let start = match range.start_bound() { + std::ops::Bound::Included(x) => *x, + std::ops::Bound::Excluded(x) => *x + 1, + std::ops::Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + std::ops::Bound::Included(x) => *x + 1, + std::ops::Bound::Excluded(x) => *x, + std::ops::Bound::Unbounded => self.len(), + }; + (start, end) + } + + /// Returns an iterator over a sub-range of this container. + pub fn range(&self, range: impl std::ops::RangeBounds) -> Iter<'_, T, B> { + let (start, end) = self.normalize_bounds(range); + let inner = match &*self.root { + Node::Leaf { data } => IterInner::Simple(data[start..end].iter()), + Node::Internal { .. } => { + if end > self.len() { + panic!("out of bounds"); + } + let mut ret = TreeIter { + stack: Vec::new(), + leaf: [].iter(), + remaining: end - start, + }; + ret.descend_to(&*self.root, start); + IterInner::Tree(ret) + } + }; + Iter { inner } + } + + /// Returns an iterator over mutable references to a sub-range of this container. + pub fn range_mut(&mut self, range: impl std::ops::RangeBounds) -> IterMut<'_, T, B> { + let (start, end) = self.normalize_bounds(range); + let len = self.len(); + + let inner = match &mut *self.root { + Node::Leaf { data } => IterMutInner::Simple(data[start..end].iter_mut()), + root @ Node::Internal { .. } => { + if end > len { + panic!("out of bounds"); + } + let mut ret = TreeIterMut { + stack: Vec::new(), + leaf: [].iter_mut(), + remaining: end - start, + }; + ret.descend_to(root, start); + IterMutInner::Tree(ret) + } + }; + IterMut { inner } + } +} + +impl FromIterator for TreeVec { + fn from_iter>(iter: I) -> Self { + // TODO: a faster implementation, maybe? This is only + // used in tests right now. + let mut ret = TreeVec::new(); + for (idx, x) in iter.into_iter().enumerate() { + ret.insert(idx, x); + } + ret + } +} + +impl std::ops::Index for TreeVec { + type Output = T; + + fn index(&self, index: usize) -> &T { + self.get(index).unwrap() + } +} + +impl std::ops::IndexMut for TreeVec { + fn index_mut(&mut self, index: usize) -> &mut T { + self.get_mut(index).unwrap() + } +} + +/// An iterator over elements of a [`TreeVec`]. +pub struct Iter<'a, T, const B: usize> { + inner: IterInner<'a, T, B>, +} + +// We special-case a fast-path for a tree with height 1. +// +// This, along with some inline annotations, makes a measurable performance +// difference. +enum IterInner<'a, T, const B: usize> { + Simple(std::slice::Iter<'a, T>), + Tree(TreeIter<'a, T, B>), +} + +struct TreeIter<'a, T, const B: usize> { + // If you imagine how the recursive implementation of a tree iteration + // works, the local state at each intermediate call consists of a + // partially-finished iteration over the children of that internal node. + // This stores the stack of that imaginary recursive implementation. + stack: Vec>>>, + leaf: std::slice::Iter<'a, T>, + // The number of remaining elements; used for iteration over ranges. The + // full-iteration implementation doesn't need it, but it isn't worth having + // two different iterator implementations. + remaining: usize, +} + +impl<'a, T, const B: usize> TreeIter<'a, T, B> { + // Fill out the rest of the stack and the leaf, by descending + // to the first descendent of `node`. + fn descend(&mut self, mut node: &'a Node) { + loop { + match node { + Node::Leaf { data } => { + self.leaf = data.iter(); + return; + } + Node::Internal { children, .. } => { + let mut children = children.iter(); + // unwrap: internal nodes are always non-empty + node = children.next().unwrap(); + self.stack.push(children); + } + } + } + } + + // Fill out the rest of the stack and the leaf, by descending + // to the node at offset `offset` in the subtree rooted at `node`. + fn descend_to(&mut self, mut node: &'a Node, mut offset: usize) { + loop { + match node { + Node::Leaf { data } => { + self.leaf = data[offset..].iter(); + return; + } + Node::Internal { children, size } => { + let Some((idx, child_offset)) = child_idx(size, offset) else { + return; + }; + offset = child_offset; + let mut children = children[idx..].iter(); + // unwrap: child_idx always returns a valid index into children + node = children.next().unwrap(); + self.stack.push(children); + } + } + } + } + + fn next(&mut self) -> Option<&'a T> { + if self.remaining == 0 { + None + } else { + self.remaining -= 1; + if let Some(ret) = self.leaf.next() { + Some(ret) + } else { + // The current leaf is exhausted. Unwind the stack until we find + // a non-exhausted internal node, then descend again. + loop { + let stack_top = self.stack.last_mut()?; + + let Some(next_node) = stack_top.next() else { + self.stack.pop(); + continue; + }; + + self.descend(next_node); + return self.leaf.next(); + } + } + } + } +} + +impl<'a, T, const B: usize> Iterator for Iter<'a, T, B> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option { + match &mut self.inner { + IterInner::Simple(it) => it.next(), + IterInner::Tree(it) => it.next(), + } + } +} + +/// An iterator over mutable references elements of a [`TreeVec`]. +pub struct IterMut<'a, T, const B: usize> { + inner: IterMutInner<'a, T, B>, +} + +enum IterMutInner<'a, T, const B: usize> { + Simple(std::slice::IterMut<'a, T>), + Tree(TreeIterMut<'a, T, B>), +} + +// This is basically a copy-pasted of `TreeIter`, but with non-mut things +// turned into mut things. Right now there's only the one duplication so +// is isn't worth trying to build an abstraction. +struct TreeIterMut<'a, T, const B: usize> { + stack: Vec>>>, + leaf: std::slice::IterMut<'a, T>, + remaining: usize, +} + +impl<'a, T, const B: usize> TreeIterMut<'a, T, B> { + fn descend(&mut self, mut node: &'a mut Node) { + loop { + match node { + Node::Leaf { data } => { + self.leaf = data.iter_mut(); + return; + } + Node::Internal { children, .. } => { + let mut children = children.iter_mut(); + // unwrap: internal nodes are always non-empty + node = children.next().unwrap(); + self.stack.push(children); + } + } + } + } + + fn descend_to(&mut self, mut node: &'a mut Node, mut offset: usize) { + loop { + match node { + Node::Leaf { data } => { + self.leaf = data[offset..].iter_mut(); + return; + } + Node::Internal { children, size } => { + let Some((idx, child_offset)) = child_idx(size, offset) else { + return; + }; + offset = child_offset; + let mut children = children[idx..].iter_mut(); + // unwrap: child_idx always returns a valid index into children + node = children.next().unwrap(); + self.stack.push(children); + } + } + } + } + + fn next(&mut self) -> Option<&'a mut T> { + if self.remaining == 0 { + None + } else { + self.remaining -= 1; + if let Some(ret) = self.leaf.next() { + Some(ret) + } else { + loop { + let stack_top = self.stack.last_mut()?; + + let Some(next_node) = stack_top.next() else { + self.stack.pop(); + continue; + }; + + self.descend(next_node); + return self.leaf.next(); + } + } + } + } +} + +impl<'a, T, const B: usize> Iterator for IterMut<'a, T, B> { + type Item = &'a mut T; + + #[inline(always)] + fn next(&mut self) -> Option { + match &mut self.inner { + IterMutInner::Simple(iter) => iter.next(), + IterMutInner::Tree(tree_iter) => tree_iter.next(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn insert_get() { + let mut vec = TreeVec::::default(); + vec.insert(0, 1); + vec.insert(0, 2); + vec.insert(0, 3); + vec.insert(0, 4); + assert_eq!(*vec.get(0).unwrap(), 4); + assert_eq!(*vec.get(1).unwrap(), 3); + assert_eq!(*vec.get(2).unwrap(), 2); + assert_eq!(*vec.get(3).unwrap(), 1); + vec.check_invariants(); + + vec.insert(0, 1); + vec.insert(0, 2); + vec.insert(0, 3); + vec.insert(0, 4); + vec.check_invariants(); + assert_eq!(*vec.get(0).unwrap(), 4); + assert_eq!(*vec.get(1).unwrap(), 3); + assert_eq!(*vec.get(2).unwrap(), 2); + assert_eq!(*vec.get(3).unwrap(), 1); + assert_eq!(*vec.get(4).unwrap(), 4); + assert_eq!(*vec.get(5).unwrap(), 3); + assert_eq!(*vec.get(6).unwrap(), 2); + assert_eq!(*vec.get(7).unwrap(), 1); + } + + #[test] + fn insert_remove() { + let mut vec = TreeVec::::default(); + vec.insert(0, 1); + vec.insert(0, 2); + vec.insert(0, 3); + vec.insert(0, 4); + vec.remove(1); + vec.check_invariants(); + assert_eq!(*vec.get(0).unwrap(), 4); + assert_eq!(*vec.get(1).unwrap(), 2); + assert_eq!(*vec.get(2).unwrap(), 1); + + vec.insert(0, 1); + vec.insert(0, 2); + vec.insert(0, 3); + vec.insert(0, 4); + vec.remove(5); + vec.check_invariants(); + assert_eq!(*vec.get(0).unwrap(), 4); + assert_eq!(*vec.get(1).unwrap(), 3); + assert_eq!(*vec.get(2).unwrap(), 2); + assert_eq!(*vec.get(3).unwrap(), 1); + assert_eq!(*vec.get(4).unwrap(), 4); + assert_eq!(*vec.get(5).unwrap(), 1); + } + + #[test] + fn iter() { + let mut vec = TreeVec::::default(); + assert_eq!(vec.iter().cloned().collect::>(), Vec::::new()); + vec.insert(0, 1); + assert_eq!(vec.iter().cloned().collect::>(), vec![1]); + vec.insert(1, 2); + assert_eq!(vec.iter().cloned().collect::>(), vec![1, 2]); + vec.insert(0, 1); + vec.insert(0, 1); + vec.insert(0, 1); + vec.insert(0, 1); + vec.insert(0, 1); + assert_eq!( + vec.iter().cloned().collect::>(), + vec![1, 1, 1, 1, 1, 1, 2] + ); + } +} diff --git a/vendor/linesweeper/src/typed_vec.rs b/vendor/linesweeper/src/typed_vec.rs new file mode 100644 index 00000000..cf0fe491 --- /dev/null +++ b/vendor/linesweeper/src/typed_vec.rs @@ -0,0 +1,129 @@ +/// Implement methods for vectors indexed with typed indices. +/// +/// We use a lot of indices into arrays: we have arrays indexed by input +/// segments, arrays indexed by output segments, arrays indexed by output +/// endpoints, and more. To help keep these straight, we wrap indices +/// in new types, like `SegIdx`, `OutputSegIdx`, etc., and we wrap `Vec` +/// in new types, like `SegVec`, `OutputSegVec`, etc. Then you can only +/// use the right index type with the right vector type. +/// +/// This macro implements a bunch of useful methods on the new-type +/// wrappers. It assumes that the index new-type is just a wrapper +/// around `usize`. +macro_rules! impl_typed_vec { + ($vec_name:ident, $idx_name:ident, $dbg_prefix:expr_2021) => { + impl std::fmt::Debug for $idx_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}_{}", $dbg_prefix, self.0) + } + } + + #[allow(dead_code)] + impl $vec_name { + /// Wraps a plain vector. + pub fn from_vec(vec: Vec) -> Self { + Self { inner: vec } + } + + /// Creates a new vector with capacity for at least `cap` output segments before reallocating. + pub fn with_capacity(cap: usize) -> Self { + Self { + inner: Vec::with_capacity(cap), + } + } + + /// Returns an iterator over all indices into this vector. + pub fn indices(&self) -> impl Iterator + use { + (0..self.inner.len()).map($idx_name) + } + + /// The length of this vector. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Are we empty? + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Adds a new element, returning its index. + pub fn push(&mut self, elt: T) -> $idx_name { + self.inner.push(elt); + $idx_name(self.len() - 1) + } + + /// Returns an iterator over indices and elements. + pub fn iter(&self) -> impl Iterator + '_ { + self.inner + .iter() + .enumerate() + .map(|(idx, t)| ($idx_name(idx), t)) + } + + /// Returns an iterator over indices and mutable elements. + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + self.inner + .iter_mut() + .enumerate() + .map(|(idx, t)| ($idx_name(idx), t)) + } + } + + #[allow(dead_code)] + impl $vec_name { + /// Creates a new vector with `size` elements, each initialized with the default value. + pub fn with_size(size: usize) -> Self { + Self { + inner: std::iter::from_fn(|| Some(T::default())) + .take(size) + .collect(), + } + } + } + + impl Default for $vec_name { + fn default() -> Self { + Self { inner: Vec::new() } + } + } + + impl std::ops::Index<$idx_name> for $vec_name { + type Output = T; + + fn index(&self, index: $idx_name) -> &Self::Output { + &self.inner[index.0] + } + } + + impl std::ops::IndexMut<$idx_name> for $vec_name { + fn index_mut(&mut self, index: $idx_name) -> &mut T { + &mut self.inner[index.0] + } + } + + impl std::fmt::Debug for $vec_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct Entry<'a, T> { + idx: $idx_name, + inner: &'a T, + } + + impl std::fmt::Debug for Entry<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{idx:?}: {inner:?}", idx = self.idx, inner = self.inner,) + } + } + + let mut list = f.debug_list(); + for idx in self.indices() { + list.entry(&Entry { + idx, + inner: &self[idx], + }); + } + list.finish() + } + } + }; +} diff --git a/vendor/linesweeper/tests/fonts.rs b/vendor/linesweeper/tests/fonts.rs new file mode 100644 index 00000000..d377bc33 --- /dev/null +++ b/vendor/linesweeper/tests/fonts.rs @@ -0,0 +1,56 @@ +use std::path::PathBuf; + +use kurbo::BezPath; +use libtest_mimic::{Arguments, Failed, Trial}; +use linesweeper::topology::Topology; +use linesweeper_util::outline_to_bezpath; +use skrifa::{FontRef, MetadataProvider}; + +fn main() { + let args = Arguments::from_args(); + let tests = glyph_tests(); + + libtest_mimic::run(&args, tests).exit(); +} + +fn glyph_tests() -> Vec { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let paths = glob::glob(&format!("{ws}/tests/fonts/**/*.ttf")).unwrap(); + paths + .into_iter() + .flat_map(|p| { + let p = p.unwrap(); + let name = p.strip_prefix(&ws).unwrap().display().to_string(); + font_glyph_tests(name, p) + }) + .collect() +} + +fn font_glyph_tests(name: String, path: PathBuf) -> Vec { + let data = std::fs::read(&path).unwrap(); + let font_ref = FontRef::new(&data).unwrap(); + let names = font_ref.glyph_names(); + font_ref + .outline_glyphs() + .iter() + .zip(font_ref.outline_glyphs().iter().skip(1)) + .map(|((id1, outline1), (_id2, outline2))| { + let glyph_name = names.get(id1).unwrap(); + let path1 = outline_to_bezpath(outline1); + let path2 = outline_to_bezpath(outline2); + Trial::test(format!("{name}-{glyph_name}"), || { + test_outline(path1, path2) + }) + }) + .collect() +} + +fn test_outline(path1: BezPath, path2: BezPath) -> Result<(), Failed> { + if path1.is_empty() || path2.is_empty() { + return Ok(()); + } + + let top = Topology::from_paths_binary(&path1, &path2, 1e-3).unwrap(); + let _out_paths = top.compute_positions(); + Ok(()) +} diff --git a/vendor/linesweeper/tests/paperjs.rs b/vendor/linesweeper/tests/paperjs.rs new file mode 100644 index 00000000..a7ad9c5a --- /dev/null +++ b/vendor/linesweeper/tests/paperjs.rs @@ -0,0 +1,222 @@ +use kurbo::{BezPath, PathEl, Shape}; +use libtest_mimic::{Arguments, Failed, Trial}; +use linesweeper::{BinaryOp, FillRule, binary_op}; +use serde::Deserialize; +use std::fs; + +fn main() { + let args = Arguments::from_args(); + let tests = paperjs_tests(); + libtest_mimic::run(&args, tests).exit(); +} + +fn paperjs_tests() -> Vec { + let mut tests = Vec::new(); + + // Load all test files + let test_files = vec![ + ("boolean-test/data/intersection.json", "intersection"), + ("boolean-test/data/union.json", "union"), + ("boolean-test/data/subtraction.json", "subtraction"), + ]; + + for (file_path, op_name) in test_files { + if let Ok(file_tests) = load_test_file(file_path, op_name) { + tests.extend(file_tests); + } + } + + tests +} + +fn load_test_file( + file_path: &str, + op_name: &str, +) -> Result, Box> { + let contents = fs::read_to_string(file_path)?; + let test_file: PaperJsTestFile = serde_json::from_str(&contents)?; + let operation = get_binary_op(&test_file.operation) + .ok_or_else(|| format!("Unknown operation: {}", test_file.operation))?; + + let mut trials = Vec::new(); + + for test_set in test_file.tests { + let set_a = test_set.op1.clone(); + for case in test_set.cases { + let test_name = format!("{}/{}:{}", op_name, test_set.name, case.name); + let test_name_path = format!("{}__{}__{}", op_name, test_set.name, case.name); + let set_a = set_a.clone(); + let set_b = case.op2.clone(); + let expected = case.res.clone(); + + trials.push(Trial::test(test_name, move || { + run_test_case(&test_name_path, &set_a, &set_b, &expected, operation) + })); + } + } + + Ok(trials) +} + +#[derive(Debug, Deserialize)] +struct PaperJsTestFile { + #[serde(rename = "fn")] + operation: String, + tests: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestSet { + name: String, + op1: String, // set_a in SVG format + cases: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestCase { + name: String, + op2: String, // set_b in SVG format + res: String, // expected result in SVG format +} + +/// Convert SVG string to BezPath +fn svg_to_path(svg: &str) -> Result> { + BezPath::from_svg(svg).map_err(|e| format!("Failed to parse SVG path: {}", e).into()) +} + +/// Convert Contours to a single BezPath by concatenating all contour paths +fn contours_to_bezpath(contours: &linesweeper::topology::Contours) -> BezPath { + let mut combined = BezPath::new(); + for contour in contours.contours() { + combined.extend(contour.path.clone()); + } + combined +} + +/// Map paper.js operation names to BinaryOp enum +fn get_binary_op(operation: &str) -> Option { + match operation { + "intersect" => Some(BinaryOp::Intersection), + "unite" | "union" => Some(BinaryOp::Union), + "subtract" | "difference" => Some(BinaryOp::Difference), + "exclude" | "xor" => Some(BinaryOp::Xor), + _ => None, + } +} + +fn to_tiny_skia(p: &BezPath) -> Option { + let mut b = tiny_skia::PathBuilder::new(); + for el in p.elements() { + match el { + PathEl::MoveTo(p) => b.move_to(p.x as f32, p.y as f32), + PathEl::LineTo(p) => b.line_to(p.x as f32, p.y as f32), + PathEl::QuadTo(p0, p1) => b.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32), + PathEl::CurveTo(p0, p1, p2) => b.cubic_to( + p0.x as f32, + p0.y as f32, + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + ), + PathEl::ClosePath => b.close(), + } + } + b.finish() +} + +#[cfg(feature = "debug-svg")] +fn dump_svg(file_path: &str, svg: &BezPath) { + let bbox = svg.bounding_box().inset(2.0); + let doc = svg::Document::new().set( + "viewBox", + (bbox.min_x(), bbox.min_y(), bbox.width(), bbox.height()), + ); + let path = svg::node::element::Path::new() + .set("d", svg.to_svg()) + .set("stroke-width", "0") + .set("fill", "black"); + let doc = doc.add(path); + svg::save(file_path, &doc).unwrap(); +} + +fn pixmap_distance(a: &tiny_skia::Pixmap, b: &tiny_skia::Pixmap) -> f32 { + let w = a.width().min(b.width()); + let h = a.height().min(b.height()); + let mut diff = 0.0; + for x in 0..w { + for y in 0..h { + let ac = a.pixel(x, y).unwrap(); + let bc = b.pixel(x, y).unwrap(); + let rdiff = ac.red().abs_diff(bc.red()); + let gdiff = ac.green().abs_diff(bc.green()); + let bdiff = ac.blue().abs_diff(bc.blue()); + diff += rdiff.max(gdiff).max(bdiff) as f32 / 255.0; + } + } + + diff / (w * h) as f32 +} + +fn path_to_pixmap(bbox: kurbo::Rect, path: &BezPath) -> tiny_skia::Pixmap { + let size = 256; + let scale = size as f64 / bbox.width().max(bbox.height()); + let transform = + tiny_skia::Transform::from_translate(-bbox.min_x() as f32, -bbox.min_y() as f32) + .post_scale(scale as f32, scale as f32); + + let mut pixmap = tiny_skia::Pixmap::new(256, 256).unwrap(); + + let mut paint = tiny_skia::Paint::default(); + paint.set_color(tiny_skia::Color::BLACK); + if let Some(p) = to_tiny_skia(path) { + pixmap.fill_path(&p, &paint, tiny_skia::FillRule::EvenOdd, transform, None); + } + pixmap +} + +/// Run a single test case +fn run_test_case( + test_name_path: &str, + set_a_svg: &str, + set_b_svg: &str, + expected_svg: &str, + operation: BinaryOp, +) -> Result<(), Failed> { + let set_a = svg_to_path(set_a_svg).map_err(|e| format!("Failed to parse set_a: {}", e))?; + let set_b = svg_to_path(set_b_svg).map_err(|e| format!("Failed to parse set_b: {}", e))?; + let expected = + svg_to_path(expected_svg).map_err(|e| format!("Failed to parse expected result: {}", e))?; + + // Run binary operation + // If it's subtraction, we need to swap the order of the operands because paper.js does A - B but our library does A - B as well, so we need to do B - A to match paper.js results + let (set_a, set_b) = if operation == BinaryOp::Difference { + (set_b, set_a) + } else { + (set_a, set_b) + }; + let result = binary_op(&set_a, &set_b, FillRule::NonZero, operation) + .map_err(|e| format!("Binary operation failed: {:?}", e))?; + let result_path = contours_to_bezpath(&result).reverse_subpaths(); + + let bbox = set_a.bounding_box().union(set_b.bounding_box()); + + let our_pixmap = path_to_pixmap(bbox, &result_path); + let expected_pixmap = path_to_pixmap(bbox, &expected); + let diff = pixmap_distance(&our_pixmap, &expected_pixmap); + if diff > 0.0 { + #[cfg(feature = "debug-svg")] + { + dump_svg(&format!("ignore/{test_name_path}_set_a.svg"), &set_a); + dump_svg(&format!("ignore/{test_name_path}_set_b.svg"), &set_b); + dump_svg(&format!("ignore/{test_name_path}_expected.svg"), &expected); + dump_svg(&format!("ignore/{test_name_path}_result.svg"), &result_path); + } + // Silence the unused variable warning + #[cfg(not(feature = "debug-svg"))] + let _ = test_name_path; + + return Err(format!("pixmap distance {diff}, above threshold").into()); + } + Ok(()) +} diff --git a/vendor/linesweeper/tests/regression.rs b/vendor/linesweeper/tests/regression.rs new file mode 100644 index 00000000..4c55b8ed --- /dev/null +++ b/vendor/linesweeper/tests/regression.rs @@ -0,0 +1,46 @@ +use kurbo::BezPath; +use libtest_mimic::{Arguments, Failed, Trial}; +use linesweeper::binary_op; +use std::path::{Path, PathBuf}; + +fn main() { + let args = Arguments::from_args(); + let tests = regression_tests(); + + libtest_mimic::run(&args, tests).exit(); +} + +fn regression_tests() -> Vec { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let paths = glob::glob(&format!("{ws}/tests/regression/**/*.txt")).unwrap(); + paths + .into_iter() + .map(|p| { + let p = p.unwrap(); + let name = input_path_base(&p).display().to_string(); + Trial::test(name, || generate_regression_test(p)) + }) + .collect() +} + +fn input_path_base(input_path: &Path) -> &Path { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let base = format!("{ws}/tests/regression"); + input_path.strip_prefix(base).unwrap() +} + +fn generate_regression_test(path: PathBuf) -> Result<(), Failed> { + let input = std::fs::read_to_string(&path).unwrap(); + let lines: Vec<_> = input.lines().collect(); + assert_eq!(lines.len(), 2); + let p0 = BezPath::from_svg(lines[0]).unwrap(); + let p1 = BezPath::from_svg(lines[1]).unwrap(); + binary_op( + &p0, + &p1, + linesweeper::FillRule::EvenOdd, + linesweeper::BinaryOp::Xor, + ) + .unwrap(); + Ok(()) +} diff --git a/vendor/linesweeper/tests/regression/awesome_0.txt b/vendor/linesweeper/tests/regression/awesome_0.txt new file mode 100644 index 00000000..cabc96c9 --- /dev/null +++ b/vendor/linesweeper/tests/regression/awesome_0.txt @@ -0,0 +1,2 @@ +M160.2,-48 C208.7810588057864,-47.99999999999999 253.30168848946408,-20.527885941061772 275.03250516799596,22.933747416002024 L298.93250516799594,70.73374741600202 L213.06749483200403,70.73374741600205 L236.967494832004,22.933747416002035 C248.82297877016197,-0.7772204603138491 277.65528470768203,-10.387989106153874 301.36625258399795,1.467494832004057 C325.0772204603138,13.322978770161992 334.68798910615385,42.15528470768207 322.8325051679959,65.86625258399796 L298.93250516799594,113.66625258399797 C290.8016884894641,129.9278859410618 274.1810588057864,140.2 256,140.2 C237.81894119421364,140.2 221.19831151053594,129.9278859410618 213.06749483200403,113.66625258399799 L189.16749483200402,65.86625258399798 C183.69831151053594,54.92788594106179 172.4189411942136,47.99999999999999 160.2,48 C133.6903320081219,48 112.19999999999999,26.509667991878086 112.19999999999999,0.0000000000000029391523179536475 C112.19999999999999,-26.509667991878075 133.6903320081219,-47.99999999999999 160.2,-48 +M232.1,44.4 L256,92.2 L279.9,44.4 Z diff --git a/vendor/linesweeper/tests/regression/graphite_0.txt b/vendor/linesweeper/tests/regression/graphite_0.txt new file mode 100644 index 00000000..77dcf5e4 --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_0.txt @@ -0,0 +1,2 @@ +M27.30779709,-257.85907878 C27.30779665,-257.85907835 27.307796200000002,-257.85907792 27.30779576,-257.8590775 C27.292172620000002,-257.8439677 -109.98458240000001,-125.07781586 -233.88593633,-5.24757973 C-233.88593684,-5.24757923 -233.88593736,-5.24757873 -233.88593787,-5.24757824 C-233.88593736,-5.2475777400000005 -233.88593684,-5.24757724 -233.88593632,-5.24757675 C-174.52579263,51.894883570000005 -115.13821436,109.22164426 -77.72402204000001,145.36173051 C-77.72402157,145.36173091 -77.72402116,145.3617313 -77.7240208,145.3617317 C-77.7240208,145.3617317 -77.72402077,145.3617317 -77.72402077,145.3617317 C-77.72401908,145.3617313 -77.72401740000001,145.36173091 -77.72401572,145.36173051 C-1.60745976,127.42335802000001 72.89114167,108.36966443 101.12619937000001,101.0791851 C101.12620127,101.07918461 101.12620318,101.07918412000001 101.12620509,101.07918362000001 C101.1262059,101.07918313 101.12620674,101.07918264 101.1262076,101.07918215000001 C105.56968257,98.47134695 110.06808693,95.83127419 114.61338829,93.16367801 C114.61338927,93.16367744 114.61339025000001,93.16367686 114.61339123,93.16367628 C114.61339123,93.16367628 114.61339123,93.16367628 114.61339123,93.16367628 C114.61339142,93.16367571 114.61339161000001,93.16367513 114.6133918,93.16367456 C123.52785808,66.12754849 166.90764887,-69.59356743000001 182.19132273,-195.37502561 C182.19132278,-195.37502607 182.19132284,-195.37502653 182.19132290000002,-195.37502699 C182.19132175000001,-195.37502746 182.19132061,-195.37502792 182.19131946000002,-195.37502838 C99.03601942,-228.9220441 27.32536748,-257.85199043 27.307800320000002,-257.8590775 C27.30779923,-257.85907792 27.30779816,-257.85907835 27.30779709,-257.85907878 L27.30779709,-257.85907878 Z M-408.62463824,-172.46325538 C-408.62463827,-172.46325472 -408.62463831,-172.46325406 -408.62463834,-172.4632534 C-414.02520486000003,-63.40231834 -377.28160484,34.20037327 -342.02221595000003,99.33558422 C-342.02221565,99.33558477 -342.02221536,99.33558531 -342.02221506,99.33558586000001 C-342.02221506,99.33558586000001 -342.02221506,99.33558586000001 -342.02221506,99.33558586000001 C-342.0222145,99.33558531 -342.02221393,99.33558477 -342.02221337000003,99.33558422 C-310.40978596,68.76186957 -272.80781456,32.395413760000004 -233.88593941,-5.24757675 C-233.8859389,-5.24757724 -233.88593838,-5.2475777400000005 -233.88593787,-5.24757824 C-233.88593787,-5.24757824 -233.88593787,-5.24757824 -233.88593787,-5.24757824 C-233.88593839,-5.24757873 -233.88593891,-5.24757923 -233.88593942,-5.24757973 C-304.53464918000003,-73.25686748 -375.14449713,-141.00509201 -408.62463617000003,-172.4632534 C-408.62463687,-172.46325406 -408.62463756,-172.46325472 -408.62463824,-172.46325538 L-408.62463824,-172.46325538 Z +M27.3077958,-257.85907753 C27.2923816,-257.84416980000003 -109.98448809,-125.07790706 -233.88593634,-5.24757971 C-233.88593634,-5.24757971 -233.88593825,-5.24757971 -233.88593825,-5.24757971 C-233.88593941,-5.24757971 -233.88593942,-5.24757972 -233.88593942,-5.24757972 C-304.53459173,-73.25681217 -375.1443823,-141.00498183 -408.62463995,-172.46317667 C-408.62464066,-172.46317733 -408.62464135,-172.46317798 -408.62464203,-172.46317863000002 C-408.62464208,-172.46317798 -408.62464211,-172.46317733 -408.62464214,-172.46317667 C-414.02518763,-63.40227258 -377.28159657000003,34.20038853 -342.02221596,99.3355842 C-342.02221566000003,99.33558476 -342.02221536,99.33558531 -342.02221506,99.33558586000001 C-342.02221449,99.33558531 -342.02221392,99.33558476 -342.02221335,99.3355842 C-310.40978594,68.76186955 -272.80781455,32.39541375 -233.88593941,-5.24757675 C-233.88593941,-5.24757675 -233.88593941,-5.24757675 -233.88593941,-5.24757675 C-233.88593889,-5.24757724 -233.88593838,-5.2475777400000005 -233.88593787,-5.24757823 C-233.88593735,-5.2475777400000005 -233.88593684,-5.24757724 -233.88593632,-5.24757675 C-233.88593632,-5.24757675 -233.88593632,-5.24757675 -233.88593632,-5.24757675 C-174.52579259,51.89488361 -115.13821427,109.22164435 -77.72402196,145.36173056 C-77.72402157,145.36173094 -77.72402117,145.36173132 -77.72402078,145.3617317 C-77.72401917,145.36173132 -77.72401755,145.36173094 -77.72401594,145.36173056 C-1.60745982,127.42335803 72.89114182,108.36966439 101.12619946,101.07918507000001 C101.12620136,101.07918458 101.12620325,101.07918409 101.12620514,101.07918360000001 C101.12620595,101.07918311 101.12620679,101.07918263 101.12620764,101.07918214 C105.56968257,98.47134695 110.0680869,95.83127422 114.61338822,93.16367806 C114.61338923,93.16367747 114.61339023000001,93.16367689 114.61339123,93.1636763 C114.61339142,93.16367571 114.61339161000001,93.16367512000001 114.61339181,93.16367453000001 C123.52785811,66.12754843 166.90764887,-69.59356747 182.19132273,-195.37502563 C182.19132279000002,-195.37502608 182.19132285,-195.37502654 182.19132289,-195.37502699 C182.19132177,-195.37502745 182.19132064000001,-195.3750279 182.1913195,-195.37502836000002 C99.03588674,-228.92209763 27.32513858,-257.85208278 27.30780024,-257.85907753 C27.30780024,-257.85907753 27.30779791,-257.85907753 27.30779791,-257.85907753 C27.30779791,-257.85907753 27.3077958,-257.85907753 27.3077958,-257.85907753 L27.3077958,-257.85907753 Z diff --git a/vendor/linesweeper/tests/regression/graphite_1.txt b/vendor/linesweeper/tests/regression/graphite_1.txt new file mode 100644 index 00000000..30b4f2f2 --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_1.txt @@ -0,0 +1,2 @@ +M-51,-390 C-51.00000037,-389.99999955 -51.00000075,-389.99999909 -51.00000112,-389.99999864 C-51.0133208,-389.98380542 -169.63665568,-245.76914195 -276.70205531,-115.60587692 C-276.70205486000003,-115.60587746 -276.70205486000003,-115.60587746 -276.70205531,-115.60587692 C-276.70205486000003,-115.60587638 -276.70205442,-115.60587585 -276.70205397,-115.60587531 C-192.38931516,-13.58098944 -108.0125259,88.98477391 -108.00000139000001,88.99999842 C-108.00000091,88.99999895 -108.00000044000001,88.99999947 -108,89 C-107.99999928,88.99999947 -107.99999924000001,88.99999947 -107.9999999,89 C-107.99266176,88.99458625 300,-212 300,-212 C300,-212 -50.96433872,-389.98191536 -50.99999728,-389.99999864 C-50.9999982,-389.99999909 -50.999999100000004,-389.99999955 -51,-390 L-51,-390 Z M-445,-317 C-445.000001,-316.99999994 -445.00000199,-316.99999988 -445.00000299,-316.99999981 C-445.00000199,-316.99999976 -445.00000193,-316.99999971 -445.0000028,-316.99999965 C-445.00000193,-316.9999996 -445.00000188,-316.99999956 -445.00000264,-316.99999951 C-445.00000188,-316.99999943 -445.00000216,-316.99999934 -445.0000035,-316.99999926 C-445.00000216,-316.9999992 -445.000002,-316.99999914 -445.000003,-316.99999907 C-445.000002,-316.99999903 -445.00000187,-316.99999898 -445.00000262000003,-316.99999893 C-445.00000187,-316.99999886 -445.00000206,-316.99999879 -445.00000319000003,-316.99999872 C-445.00000206,-316.99999865 -445.00000206,-316.99999858 -445.00000319000003,-316.99999851 C-445.07164839,-316.99554979 -606,-307 -606,-307 C-606,-307 -445.00289573000003,88.99287759 -445,89 C-445.00000023,88.99999940000001 -445.00000023,88.99999940000001 -445,89 C-444.99999951,88.99999940000001 -444.99999901,88.9999988 -444.99999852,88.99999820000001 C-444.98741208,88.98469641 -364.75335505,-8.5587384 -276.70205664,-115.60587531 C-276.70205619,-115.60587585 -276.70205575,-115.60587638 -276.70205531,-115.60587692 C-276.70205531,-115.60587692 -276.70205531,-115.60587692 -276.70205531,-115.60587692 C-276.70205576,-115.60587746 -276.7020562,-115.605878 -276.70205665000003,-115.60587854 C-360.13048219,-216.56067745000001 -443.49619297,-316.98588429 -444.99987267,-316.99999851 C-444.99987444,-316.99999858 -444.99987444,-316.99999865 -444.99987267,-316.99999872 C-444.99987444,-316.99999879 -444.99987601000004,-316.99999886 -444.99987738,-316.99999893 C-444.99988877,-316.99999898 -444.99988744,-316.99999903 -444.99987339,-316.99999907 C-444.9998795,-316.99999914 -444.99987949,-316.9999992 -444.99987336,-316.99999926 C-444.99986538,-316.99999934 -444.99986663,-316.99999943 -444.99987712,-316.99999951 C-444.99988835,-316.99999956 -444.99988766,-316.9999996 -444.99987503,-316.99999965 C-444.99988443,-316.99999971 -444.9998839,-316.99999976 -444.99987344,-316.99999981 C-444.99987969,-316.99999988 -444.99992188,-316.99999994 -445,-317 L-445,-317 Z +M-147.77035068,-523.63725234 C-147.77897268,-523.63725228 -147.78328911,-523.63725221 -147.78329996,-523.63725215 C-147.78278455,-523.6372520900001 -147.78276705,-523.63725204 -147.78324747,-523.63725198 C-147.78238075000002,-523.63725194 -147.78233785,-523.63725189 -147.78311876,-523.63725184 C-147.78494057,-523.63725176 -147.78487849,-523.63725167 -147.78293254,-523.63725159 C-147.78318771,-523.6372515200001 -147.78331018,-523.63725146 -147.78329996,-523.6372514 C-147.78235912,-523.63725135 -147.78229246,-523.6372513 -147.78309999,-523.63725125 C-147.78382096,-523.63725118 -147.78386999,-523.63725111 -147.78324709,-523.63725104 C-147.78386999,-523.63725097 -147.78386999,-523.6372509 -147.78324709,-523.63725083 C-290.97059178,-523.62540865 -459.36114997,-454.02730629 -459.36114997,-454.02730629 C-503.78067701000003,-360.84914474 -515.91201353,-267.22250217 -511.28673862,-182.54906502 C-501.51553210000003,-3.67069389 -416.96053353,135.25003698 -404.05763535,145.57150696 C-402.79147715,146.58435025 -400.17221075000003,147.05752789000002 -396.38569434,147.05843347 C-396.38546768000003,147.05843352 -396.3852332,147.05843358 -396.3849909,147.05843363 C-396.38523229000003,147.05843368 -396.3852474,147.05843373 -396.38503624000003,147.05843377 C-396.38525358,147.05843386 -396.38517355,147.05843394000001 -396.38479617,147.05843402 C-396.38516954,147.05843409 -396.38521642,147.05843415 -396.38493682,147.05843421 C-396.38521417,147.05843426 -396.38524896,147.05843430000002 -396.38504119,147.05843435 C-396.38525258,147.05843442 -396.38520028,147.05843449 -396.38488428,147.05843456 C-396.38520028,147.05843463 -396.38183866,147.0584347 -396.37479941,147.05843477 C-396.36776496,147.0584347 -396.36439327,147.05843463 -396.36468434,147.05843456 C-396.36439327,147.05843449 -396.36434448,147.05843442 -396.36453799000003,147.05843435 C-396.36434177,147.05843430000002 -396.36437435,147.05843426 -396.36463574,147.05843421 C-396.36437604,147.05843415 -396.36441911000003,147.05843409 -396.36476495,147.05843402 C-396.36442211,147.05843394000001 -396.36434802,147.05843386 -396.36454266,147.05843377 C-396.36434339,147.05843373 -396.36435761,147.05843368 -396.36458529000004,147.05843363 C-396.36435829,147.05843358 -396.36412543,147.05843352 -396.36388671000003,147.05843347 C-343.24271064,147.0457326 -61.00302123,54.328209290000004 -61.00302123,54.328209290000004 C-61.00302123,54.328209290000004 54.36842292,-476.62945279 -57.54655157,-511.5974822 C-84.89366723,-520.14213629 -115.68055714,-523.63459936 -147.75748146,-523.63725083 C-147.75684889000001,-523.6372509 -147.75684889000001,-523.63725097 -147.75748146,-523.63725104 C-147.75684889000001,-523.63725111 -147.75689632,-523.63725118 -147.75762373,-523.63725125 C-147.75842483,-523.6372513 -147.75835924,-523.63725135 -147.75742698,-523.6372514 C-147.75740817,-523.63725146 -147.75753208,-523.6372515200001 -147.7577987,-523.63725159 C-147.75584128,-523.63725167 -147.75577675,-523.63725176 -147.75760512,-523.63725184 C-147.75837949,-523.63725189 -147.75833706,-523.63725194 -147.75747781,-523.63725198 C-147.75795075,-523.63725204 -147.75793379,-523.6372520900001 -147.75742692,-523.63725215 C-147.75742922,-523.63725221 -147.76173714,-523.63725228 -147.77035068,-523.63725234 L-147.77035068,-523.63725234 Z diff --git a/vendor/linesweeper/tests/regression/graphite_2.txt b/vendor/linesweeper/tests/regression/graphite_2.txt new file mode 100644 index 00000000..556e896d --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_2.txt @@ -0,0 +1,2 @@ +M-4.04445106,-227.53699448 C-4.04445141,-227.53699414000002 -4.04445176,-227.5369938 -4.0444521,-227.53699347 C-51.7088457,-181.43874387 -146.15069456,-90.10004021 -233.88593633,-5.24757973 C-233.88593684,-5.24757923 -233.88593736,-5.24757873 -233.88593787,-5.24757824 C-233.88593736,-5.2475777400000005 -233.88593684,-5.24757724 -233.88593632,-5.24757675 C-136.31505417,88.6780763 -38.67004972,183.1016691 -38.655462480000004,183.1157752 C-38.65546192,183.11577569 -38.65546137,183.11577618 -38.655460850000004,183.11577667 C-38.65546,183.11577618 -38.655459130000004,183.11577569 -38.65545825,183.1157752 C-38.636636960000004,183.10472910000001 63.43909693,123.19740587 172.66309485,59.09483126 C172.66309582,59.0948307 172.66309679,59.09483013 172.66309776,59.09482957 C172.66309789000002,59.094829000000004 172.66309804,59.09482844 172.66309821000002,59.09482787 C195.93027539,-28.361478 218.13974889,-146.00472757 185.99061181,-193.84229367 C185.99061146,-193.84229419000002 185.99061111,-193.84229471 185.99061077000002,-193.84229523 C185.99061077000002,-193.84229523 185.99061076,-193.84229523 185.99061076,-193.84229523 C185.99060948000002,-193.84229575 185.99060819000002,-193.84229627 185.99060690000002,-193.84229679 C163.21411887,-203.03092579 141.22123575,-211.90342815 121.23520424,-219.96631425 C121.23520325,-219.96631465000002 121.23520226000001,-219.96631505 121.23520127,-219.96631545 C121.23519826,-219.96631585 121.23519525,-219.96631625 121.23519224,-219.96631665 C81.94489447000001,-225.17543523 38.58618673,-227.27591237000001 -4.04428678,-227.53699347 C-4.0443415400000005,-227.5369938 -4.0443963,-227.53699414000002 -4.04445106,-227.53699448 L-4.04445106,-227.53699448 Z M-298.96972663,-67.82445804 C-298.96972679,-67.82445746 -298.96972695,-67.82445687 -298.96972711,-67.82445628 C-308.0266757,-34.79351542 -313.47490909,-4.88751908 -311.92312795,13.14190599 C-310.47587425,29.956875240000002 -307.39575194,46.21813067 -303.15410566,61.74459228 C-303.1541055,61.74459287 -303.15410534,61.74459345 -303.15410518,61.74459404 C-303.15410518,61.74459404 -303.15410509000003,61.74459404 -303.15410509000003,61.74459404 C-303.1541046,61.74459345 -303.15410399,61.74459287 -303.15410327,61.74459228 C-281.29005965,40.5989515 -257.83792341000003,17.91739932 -233.88593941,-5.24757675 C-233.8859389,-5.24757724 -233.88593838,-5.2475777400000005 -233.88593787,-5.24757824 C-233.88593787,-5.24757824 -233.88593787,-5.24757824 -233.88593787,-5.24757824 C-233.88593839,-5.24757873 -233.88593891,-5.24757923 -233.88593942,-5.24757973 C-255.96152711,-26.49842777 -278.03332044,-47.72378629 -298.96972479,-67.82445628 C-298.96972541,-67.82445687 -298.96972602,-67.82445746 -298.96972663,-67.82445804 L-298.96972663,-67.82445804 Z +M-4.04445105,-227.53699448 C-4.0444448,-227.53699444 -4.04443853,-227.5369944 -4.04443224,-227.53699436 C-4.04443855,-227.53699429 -4.04443619,-227.53699422 -4.04442515,-227.53699416 C-4.04443617,-227.53699411 -4.0444375500000005,-227.53699405 -4.04442929,-227.536994 C-4.04443757,-227.53699393 -4.0444355100000005,-227.53699385000002 -4.0444230800000005,-227.53699378000002 C-4.0444355100000005,-227.5369937 -4.04444445,-227.53699362 -4.0444499,-227.53699355 C-4.04444445,-227.53699362 -4.0444355100000005,-227.5369937 -4.0444230800000005,-227.53699378000002 C-4.0444355100000005,-227.53699385000002 -4.04443757,-227.53699393 -4.04442929,-227.536994 C-4.0444375500000005,-227.53699405 -4.04443617,-227.53699411 -4.04442515,-227.53699416 C-4.04443619,-227.53699422 -4.04443855,-227.53699429 -4.04443224,-227.53699436 C-4.04443853,-227.5369944 -4.0444448,-227.53699444 -4.04445105,-227.53699448 L-4.04445105,-227.53699448 Z M-4.04445201,-227.53699355 C-51.70884559,-181.43874397 -146.15069452,-90.10004025 -233.88593634,-5.24757971 C-233.88593634,-5.24757971 -233.88593825,-5.24757971 -233.88593825,-5.24757971 C-233.88593941,-5.24757971 -233.88593942,-5.24757972 -233.88593942,-5.24757972 C-255.96152637,-26.49842706 -278.03331896000003,-47.72378487 -298.9697253,-67.82445426 C-298.96972593,-67.82445486 -298.96972655,-67.82445546 -298.96972717,-67.82445606 C-298.96972733,-67.82445546 -298.96972750000003,-67.82445486 -298.96972767,-67.82445426 C-308.02667592,-34.793514200000004 -313.47490906,-4.88751871 -311.92312795,13.14190599 C-310.47587426,29.95687522 -307.39575195000003,46.21813063 -303.15410568,61.74459222 C-303.15410552000003,61.74459282 -303.15410535,61.74459342 -303.15410519,61.74459402 C-303.15410457,61.74459342 -303.15410394,61.74459282 -303.15410332,61.74459222 C-281.29005969,40.59895153 -257.83792342,17.917399330000002 -233.88593941,-5.24757675 C-233.88593941,-5.24757675 -233.88593941,-5.24757675 -233.88593941,-5.24757675 C-233.88593889,-5.24757724 -233.88593838,-5.2475777400000005 -233.88593787,-5.24757823 C-233.88593735,-5.2475777400000005 -233.88593684,-5.24757724 -233.88593632,-5.24757675 C-233.88593632,-5.24757675 -233.88593632,-5.24757675 -233.88593632,-5.24757675 C-136.31500758,88.67812116 -38.66995645,183.1017593 -38.65546246,183.11577522000002 C-38.6554624,183.11577526 -38.65546235,183.11577531 -38.6554623,183.11577536000001 C-38.65546195,183.11577567 -38.65546121,183.11577599 -38.6554601,183.1157763 C-38.65545957,183.11577599 -38.65545901,183.11577567 -38.6554584,183.11577536000001 C-38.63718132,183.10504862 63.43880567,123.19757684 172.6630948,59.094831310000004 C172.66309578,59.09483074 172.66309677,59.09483016 172.66309775,59.09482959 C172.66309789000002,59.09482901 172.66309804,59.09482844 172.66309821000002,59.09482786 C195.93027539,-28.36147801 218.13974889,-146.00472758 185.99061181,-193.84229367 C185.99061146,-193.84229419000002 185.9906111,-193.84229471 185.99061076,-193.84229523 C185.99060946,-193.84229576 185.99060816,-193.84229627 185.99060688,-193.8422968 C163.21411883,-203.03092581 141.22123568,-211.90342818 121.23520415,-219.96631429 C121.2352032,-219.96631467 121.23520226000001,-219.96631506 121.23520131000001,-219.96631544000002 C121.23519842,-219.96631582 121.23519553,-219.96631621 121.23519264000001,-219.96631659 C81.94489083,-225.17543574 38.5861782,-227.27591278 -4.04429958,-227.53699355 C-4.04429958,-227.53699355 -4.0444499,-227.53699355 -4.0444499,-227.53699355 C-4.0444499,-227.53699355 -4.04445201,-227.53699355 -4.04445201,-227.53699355 L-4.04445201,-227.53699355 Z diff --git a/vendor/linesweeper/tests/regression/graphite_3.txt b/vendor/linesweeper/tests/regression/graphite_3.txt new file mode 100644 index 00000000..7430df9a --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_3.txt @@ -0,0 +1,2 @@ +M989.66666667,768 C1058.74723866,687.80584194 1094.1623535200001,594.2965698200001 1094.1623535200001,594.2965698200001 C1173.36305304,625.10359457 1293.66666667,681 1318,768 L1299.30645729,786.39937432 L1011.11108398,786.39935303 L989.66666667,768 L989.66666667,768 +M997.50296389,540.72139231 C1000.26188778,540.72139231 1002.50296389,542.96246842 1002.50296389,545.72139231 C1002.50296389,548.4803162000001 1000.26188778,550.72139231 997.50296389,550.72139231 C994.74404,550.72139231 992.50296389,548.4803162000001 992.50296389,545.72139231 C992.50296389,542.96246842 994.74404,540.72139231 997.50296389,540.72139231 L997.50296389,540.72139231 M1024.77569116,545.26684685 C1027.53461505,545.26684685 1029.77569116,547.50792297 1029.77569116,550.26684685 C1029.77569116,553.02577074 1027.53461505,555.26684685 1024.77569116,555.26684685 C1022.0167672700001,555.26684685 1019.7756911600001,553.02577074 1019.7756911600001,550.26684685 C1019.7756911600001,547.50792297 1022.0167672700001,545.26684685 1024.77569116,545.26684685 L1024.77569116,545.26684685 M1052.04841843,549.8123014 C1054.80734232,549.8123014 1057.04841843,552.05337751 1057.04841843,554.8123014 C1057.04841843,557.57122529 1054.80734232,559.8123014 1052.04841843,559.8123014 C1049.28949454,559.8123014 1047.04841843,557.57122529 1047.04841843,554.8123014 C1047.04841843,552.05337751 1049.28949454,549.8123014 1052.04841843,549.8123014 L1052.04841843,549.8123014 M1079.32114571,554.35775595 C1082.08006959,554.35775595 1084.32114571,556.5988320600001 1084.32114571,559.35775595 C1084.32114571,562.1166798300001 1082.08006959,564.35775595 1079.32114571,564.35775595 C1076.56222182,564.35775595 1074.32114571,562.1166798300001 1074.32114571,559.35775595 C1074.32114571,556.5988320600001 1076.56222182,554.35775595 1079.32114571,554.35775595 L1079.32114571,554.35775595 M1106.59387298,558.90321049 C1109.35279687,558.90321049 1111.59387298,561.1442866 1111.59387298,563.90321049 C1111.59387298,566.66213438 1109.35279687,568.90321049 1106.59387298,568.90321049 C1103.83494909,568.90321049 1101.59387298,566.66213438 1101.59387298,563.90321049 C1101.59387298,561.1442866 1103.83494909,558.90321049 1106.59387298,558.90321049 L1106.59387298,558.90321049 M1133.86660025,563.44866504 C1136.62552414,563.44866504 1138.86660025,565.68974115 1138.86660025,568.44866504 C1138.86660025,571.20758893 1136.62552414,573.44866504 1133.86660025,573.44866504 C1131.10767636,573.44866504 1128.86660025,571.20758893 1128.86660025,568.44866504 C1128.86660025,565.68974115 1131.10767636,563.44866504 1133.86660025,563.44866504 L1133.86660025,563.44866504 M1161.13932752,567.99411958 C1163.89825141,567.99411958 1166.13932752,570.2351956900001 1166.13932752,572.99411958 C1166.13932752,575.75304347 1163.89825141,577.99411958 1161.13932752,577.99411958 C1158.38040363,577.99411958 1156.13932752,575.75304347 1156.13932752,572.99411958 C1156.13932752,570.2351956900001 1158.38040363,567.99411958 1161.13932752,567.99411958 L1161.13932752,567.99411958 M1188.4120548,572.53957413 C1191.17097868,572.53957413 1193.4120548,574.78065024 1193.4120548,577.53957413 C1193.4120548,580.29849802 1191.17097868,582.53957413 1188.4120548,582.53957413 C1185.65313091,582.53957413 1183.4120548,580.29849802 1183.4120548,577.53957413 C1183.4120548,574.78065024 1185.65313091,572.53957413 1188.4120548,572.53957413 L1188.4120548,572.53957413 M1215.68478207,577.08502867 C1218.44370596,577.08502867 1220.68478207,579.32610478 1220.68478207,582.08502867 C1220.68478207,584.84395256 1218.44370596,587.08502867 1215.68478207,587.08502867 C1212.92585818,587.08502867 1210.68478207,584.84395256 1210.68478207,582.08502867 C1210.68478207,579.32610478 1212.92585818,577.08502867 1215.68478207,577.08502867 L1215.68478207,577.08502867 M1242.9575093400001,581.63048322 C1245.7164332300001,581.63048322 1247.9575093400001,583.87155933 1247.9575093400001,586.63048322 C1247.9575093400001,589.38940711 1245.7164332300001,591.63048322 1242.9575093400001,591.63048322 C1240.19858545,591.63048322 1237.9575093400001,589.38940711 1237.9575093400001,586.63048322 C1237.9575093400001,583.87155933 1240.19858545,581.63048322 1242.9575093400001,581.63048322 L1242.9575093400001,581.63048322 M1270.23023661,586.17593776 C1272.9891605,586.17593776 1275.23023661,588.41701387 1275.23023661,591.17593776 C1275.23023661,593.93486165 1272.9891605,596.17593776 1270.23023661,596.17593776 C1267.4713127300001,596.17593776 1265.23023661,593.93486165 1265.23023661,591.17593776 C1265.23023661,588.41701387 1267.4713127300001,586.17593776 1270.23023661,586.17593776 L1270.23023661,586.17593776 M1297.50296389,590.72139231 C1300.26188778,590.72139231 1302.50296389,592.96246842 1302.50296389,595.72139231 C1302.50296389,598.4803162000001 1300.26188778,600.72139231 1297.50296389,600.72139231 C1294.74404,600.72139231 1292.50296389,598.4803162000001 1292.50296389,595.72139231 C1292.50296389,592.96246842 1294.74404,590.72139231 1297.50296389,590.72139231 L1297.50296389,590.72139231 M997.50296389,569.29282088 C1000.26188778,569.29282088 1002.50296389,571.53389699 1002.50296389,574.29282088 C1002.50296389,577.05174477 1000.26188778,579.29282088 997.50296389,579.29282088 C994.74404,579.29282088 992.50296389,577.05174477 992.50296389,574.29282088 C992.50296389,571.53389699 994.74404,569.29282088 997.50296389,569.29282088 L997.50296389,569.29282088 M1024.77569116,573.8382754300001 C1027.53461505,573.8382754300001 1029.77569116,576.0793515400001 1029.77569116,578.8382754300001 C1029.77569116,581.5971993100001 1027.53461505,583.8382754300001 1024.77569116,583.8382754300001 C1022.0167672700001,583.8382754300001 1019.7756911600001,581.5971993100001 1019.7756911600001,578.8382754300001 C1019.7756911600001,576.0793515400001 1022.0167672700001,573.8382754300001 1024.77569116,573.8382754300001 L1024.77569116,573.8382754300001 M1052.04841843,578.38372997 C1054.80734232,578.38372997 1057.04841843,580.62480608 1057.04841843,583.38372997 C1057.04841843,586.14265386 1054.80734232,588.38372997 1052.04841843,588.38372997 C1049.28949454,588.38372997 1047.04841843,586.14265386 1047.04841843,583.38372997 C1047.04841843,580.62480608 1049.28949454,578.38372997 1052.04841843,578.38372997 L1052.04841843,578.38372997 M1079.32114571,582.92918452 C1082.08006959,582.92918452 1084.32114571,585.17026063 1084.32114571,587.92918452 C1084.32114571,590.68810841 1082.08006959,592.92918452 1079.32114571,592.92918452 C1076.56222182,592.92918452 1074.32114571,590.68810841 1074.32114571,587.92918452 C1074.32114571,585.17026063 1076.56222182,582.92918452 1079.32114571,582.92918452 L1079.32114571,582.92918452 M1106.59387298,587.47463906 C1109.35279687,587.47463906 1111.59387298,589.7157151700001 1111.59387298,592.47463906 C1111.59387298,595.23356295 1109.35279687,597.47463906 1106.59387298,597.47463906 C1103.83494909,597.47463906 1101.59387298,595.23356295 1101.59387298,592.47463906 C1101.59387298,589.7157151700001 1103.83494909,587.47463906 1106.59387298,587.47463906 L1106.59387298,587.47463906 M1133.86660025,592.02009361 C1136.62552414,592.02009361 1138.86660025,594.26116972 1138.86660025,597.02009361 C1138.86660025,599.7790175 1136.62552414,602.02009361 1133.86660025,602.02009361 C1131.10767636,602.02009361 1128.86660025,599.7790175 1128.86660025,597.02009361 C1128.86660025,594.26116972 1131.10767636,592.02009361 1133.86660025,592.02009361 L1133.86660025,592.02009361 M1161.13932752,596.56554815 C1163.89825141,596.56554815 1166.13932752,598.80662426 1166.13932752,601.56554815 C1166.13932752,604.32447204 1163.89825141,606.56554815 1161.13932752,606.56554815 C1158.38040363,606.56554815 1156.13932752,604.32447204 1156.13932752,601.56554815 C1156.13932752,598.80662426 1158.38040363,596.56554815 1161.13932752,596.56554815 L1161.13932752,596.56554815 M1188.4120548,601.1110027 C1191.17097868,601.1110027 1193.4120548,603.35207881 1193.4120548,606.1110027 C1193.4120548,608.86992659 1191.17097868,611.1110027 1188.4120548,611.1110027 C1185.65313091,611.1110027 1183.4120548,608.86992659 1183.4120548,606.1110027 C1183.4120548,603.35207881 1185.65313091,601.1110027 1188.4120548,601.1110027 L1188.4120548,601.1110027 M1215.68478207,605.65645724 C1218.44370596,605.65645724 1220.68478207,607.89753336 1220.68478207,610.65645724 C1220.68478207,613.41538113 1218.44370596,615.65645724 1215.68478207,615.65645724 C1212.92585818,615.65645724 1210.68478207,613.41538113 1210.68478207,610.65645724 C1210.68478207,607.89753336 1212.92585818,605.65645724 1215.68478207,605.65645724 L1215.68478207,605.65645724 M1242.9575093400001,610.20191179 C1245.7164332300001,610.20191179 1247.9575093400001,612.4429879 1247.9575093400001,615.20191179 C1247.9575093400001,617.9608356800001 1245.7164332300001,620.20191179 1242.9575093400001,620.20191179 C1240.19858545,620.20191179 1237.9575093400001,617.9608356800001 1237.9575093400001,615.20191179 C1237.9575093400001,612.4429879 1240.19858545,610.20191179 1242.9575093400001,610.20191179 L1242.9575093400001,610.20191179 M1270.23023661,614.74736634 C1272.9891605,614.74736634 1275.23023661,616.98844245 1275.23023661,619.74736634 C1275.23023661,622.50629022 1272.9891605,624.74736634 1270.23023661,624.74736634 C1267.4713127300001,624.74736634 1265.23023661,622.50629022 1265.23023661,619.74736634 C1265.23023661,616.98844245 1267.4713127300001,614.74736634 1270.23023661,614.74736634 L1270.23023661,614.74736634 M1297.50296389,619.29282088 C1300.26188778,619.29282088 1302.50296389,621.53389699 1302.50296389,624.29282088 C1302.50296389,627.05174477 1300.26188778,629.29282088 1297.50296389,629.29282088 C1294.74404,629.29282088 1292.50296389,627.05174477 1292.50296389,624.29282088 C1292.50296389,621.53389699 1294.74404,619.29282088 1297.50296389,619.29282088 L1297.50296389,619.29282088 M997.50296389,597.86424945 C1000.26188778,597.86424945 1002.50296389,600.10532556 1002.50296389,602.86424945 C1002.50296389,605.62317334 1000.26188778,607.86424945 997.50296389,607.86424945 C994.74404,607.86424945 992.50296389,605.62317334 992.50296389,602.86424945 C992.50296389,600.10532556 994.74404,597.86424945 997.50296389,597.86424945 L997.50296389,597.86424945 M1024.77569116,602.409704 C1027.53461505,602.409704 1029.77569116,604.65078011 1029.77569116,607.409704 C1029.77569116,610.16862789 1027.53461505,612.409704 1024.77569116,612.409704 C1022.0167672700001,612.409704 1019.7756911600001,610.16862789 1019.7756911600001,607.409704 C1019.7756911600001,604.65078011 1022.0167672700001,602.409704 1024.77569116,602.409704 L1024.77569116,602.409704 M1052.04841843,606.95515854 C1054.80734232,606.95515854 1057.04841843,609.1962346500001 1057.04841843,611.95515854 C1057.04841843,614.71408243 1054.80734232,616.95515854 1052.04841843,616.95515854 C1049.28949454,616.95515854 1047.04841843,614.71408243 1047.04841843,611.95515854 C1047.04841843,609.1962346500001 1049.28949454,606.95515854 1052.04841843,606.95515854 L1052.04841843,606.95515854 M1079.32114571,611.50061309 C1082.08006959,611.50061309 1084.32114571,613.7416892 1084.32114571,616.50061309 C1084.32114571,619.25953698 1082.08006959,621.50061309 1079.32114571,621.50061309 C1076.56222182,621.50061309 1074.32114571,619.25953698 1074.32114571,616.50061309 C1074.32114571,613.7416892 1076.56222182,611.50061309 1079.32114571,611.50061309 L1079.32114571,611.50061309 M1106.59387298,616.04606763 C1109.35279687,616.04606763 1111.59387298,618.28714374 1111.59387298,621.04606763 C1111.59387298,623.80499152 1109.35279687,626.04606763 1106.59387298,626.04606763 C1103.83494909,626.04606763 1101.59387298,623.80499152 1101.59387298,621.04606763 C1101.59387298,618.28714374 1103.83494909,616.04606763 1106.59387298,616.04606763 L1106.59387298,616.04606763 M1133.86660025,620.59152218 C1136.62552414,620.59152218 1138.86660025,622.83259829 1138.86660025,625.59152218 C1138.86660025,628.35044607 1136.62552414,630.59152218 1133.86660025,630.59152218 C1131.10767636,630.59152218 1128.86660025,628.35044607 1128.86660025,625.59152218 C1128.86660025,622.83259829 1131.10767636,620.59152218 1133.86660025,620.59152218 L1133.86660025,620.59152218 M1161.13932752,625.13697672 C1163.89825141,625.13697672 1166.13932752,627.37805284 1166.13932752,630.13697672 C1166.13932752,632.89590061 1163.89825141,635.13697672 1161.13932752,635.13697672 C1158.38040363,635.13697672 1156.13932752,632.89590061 1156.13932752,630.13697672 C1156.13932752,627.37805284 1158.38040363,625.13697672 1161.13932752,625.13697672 L1161.13932752,625.13697672 M1188.4120548,629.68243127 C1191.17097868,629.68243127 1193.4120548,631.92350738 1193.4120548,634.68243127 C1193.4120548,637.4413551600001 1191.17097868,639.68243127 1188.4120548,639.68243127 C1185.65313091,639.68243127 1183.4120548,637.4413551600001 1183.4120548,634.68243127 C1183.4120548,631.92350738 1185.65313091,629.68243127 1188.4120548,629.68243127 L1188.4120548,629.68243127 M1215.68478207,634.22788582 C1218.44370596,634.22788582 1220.68478207,636.46896193 1220.68478207,639.22788582 C1220.68478207,641.9868097 1218.44370596,644.22788582 1215.68478207,644.22788582 C1212.92585818,644.22788582 1210.68478207,641.9868097 1210.68478207,639.22788582 C1210.68478207,636.46896193 1212.92585818,634.22788582 1215.68478207,634.22788582 L1215.68478207,634.22788582 M1242.9575093400001,638.77334036 C1245.7164332300001,638.77334036 1247.9575093400001,641.01441647 1247.9575093400001,643.77334036 C1247.9575093400001,646.53226425 1245.7164332300001,648.77334036 1242.9575093400001,648.77334036 C1240.19858545,648.77334036 1237.9575093400001,646.53226425 1237.9575093400001,643.77334036 C1237.9575093400001,641.01441647 1240.19858545,638.77334036 1242.9575093400001,638.77334036 L1242.9575093400001,638.77334036 M1270.23023661,643.3187949100001 C1272.9891605,643.3187949100001 1275.23023661,645.5598710200001 1275.23023661,648.3187949100001 C1275.23023661,651.0777188 1272.9891605,653.3187949100001 1270.23023661,653.3187949100001 C1267.4713127300001,653.3187949100001 1265.23023661,651.0777188 1265.23023661,648.3187949100001 C1265.23023661,645.5598710200001 1267.4713127300001,643.3187949100001 1270.23023661,643.3187949100001 L1270.23023661,643.3187949100001 M1297.50296389,647.86424945 C1300.26188778,647.86424945 1302.50296389,650.10532556 1302.50296389,652.86424945 C1302.50296389,655.62317334 1300.26188778,657.86424945 1297.50296389,657.86424945 C1294.74404,657.86424945 1292.50296389,655.62317334 1292.50296389,652.86424945 C1292.50296389,650.10532556 1294.74404,647.86424945 1297.50296389,647.86424945 L1297.50296389,647.86424945 M997.50296389,626.4356780200001 C1000.26188778,626.4356780200001 1002.50296389,628.6767541300001 1002.50296389,631.4356780200001 C1002.50296389,634.19460191 1000.26188778,636.4356780200001 997.50296389,636.4356780200001 C994.74404,636.4356780200001 992.50296389,634.19460191 992.50296389,631.4356780200001 C992.50296389,628.6767541300001 994.74404,626.4356780200001 997.50296389,626.4356780200001 L997.50296389,626.4356780200001 M1024.77569116,630.98113257 C1027.53461505,630.98113257 1029.77569116,633.22220868 1029.77569116,635.98113257 C1029.77569116,638.74005646 1027.53461505,640.98113257 1024.77569116,640.98113257 C1022.0167672700001,640.98113257 1019.7756911600001,638.74005646 1019.7756911600001,635.98113257 C1019.7756911600001,633.22220868 1022.0167672700001,630.98113257 1024.77569116,630.98113257 L1024.77569116,630.98113257 M1052.04841843,635.52658711 C1054.80734232,635.52658711 1057.04841843,637.76766323 1057.04841843,640.52658711 C1057.04841843,643.285511 1054.80734232,645.52658711 1052.04841843,645.52658711 C1049.28949454,645.52658711 1047.04841843,643.285511 1047.04841843,640.52658711 C1047.04841843,637.76766323 1049.28949454,635.52658711 1052.04841843,635.52658711 L1052.04841843,635.52658711 M1079.32114571,640.07204166 C1082.08006959,640.07204166 1084.32114571,642.31311777 1084.32114571,645.07204166 C1084.32114571,647.83096555 1082.08006959,650.07204166 1079.32114571,650.07204166 C1076.56222182,650.07204166 1074.32114571,647.83096555 1074.32114571,645.07204166 C1074.32114571,642.31311777 1076.56222182,640.07204166 1079.32114571,640.07204166 L1079.32114571,640.07204166 M1106.59387298,644.61749621 C1109.35279687,644.61749621 1111.59387298,646.85857232 1111.59387298,649.61749621 C1111.59387298,652.37642009 1109.35279687,654.61749621 1106.59387298,654.61749621 C1103.83494909,654.61749621 1101.59387298,652.37642009 1101.59387298,649.61749621 C1101.59387298,646.85857232 1103.83494909,644.61749621 1106.59387298,644.61749621 L1106.59387298,644.61749621 M1133.86660025,649.16295075 C1136.62552414,649.16295075 1138.86660025,651.40402686 1138.86660025,654.16295075 C1138.86660025,656.92187464 1136.62552414,659.16295075 1133.86660025,659.16295075 C1131.10767636,659.16295075 1128.86660025,656.92187464 1128.86660025,654.16295075 C1128.86660025,651.40402686 1131.10767636,649.16295075 1133.86660025,649.16295075 L1133.86660025,649.16295075 M1161.13932752,653.7084053 C1163.89825141,653.7084053 1166.13932752,655.94948141 1166.13932752,658.7084053 C1166.13932752,661.46732918 1163.89825141,663.7084053 1161.13932752,663.7084053 C1158.38040363,663.7084053 1156.13932752,661.46732918 1156.13932752,658.7084053 C1156.13932752,655.94948141 1158.38040363,653.7084053 1161.13932752,653.7084053 L1161.13932752,653.7084053 M1188.4120548,658.25385984 C1191.17097868,658.25385984 1193.4120548,660.49493595 1193.4120548,663.25385984 C1193.4120548,666.01278373 1191.17097868,668.25385984 1188.4120548,668.25385984 C1185.65313091,668.25385984 1183.4120548,666.01278373 1183.4120548,663.25385984 C1183.4120548,660.49493595 1185.65313091,658.25385984 1188.4120548,658.25385984 L1188.4120548,658.25385984 M1215.68478207,662.7993143900001 C1218.44370596,662.7993143900001 1220.68478207,665.0403905000001 1220.68478207,667.7993143900001 C1220.68478207,670.5582382800001 1218.44370596,672.7993143900001 1215.68478207,672.7993143900001 C1212.92585818,672.7993143900001 1210.68478207,670.5582382800001 1210.68478207,667.7993143900001 C1210.68478207,665.0403905000001 1212.92585818,662.7993143900001 1215.68478207,662.7993143900001 L1215.68478207,662.7993143900001 M1242.9575093400001,667.34476893 C1245.7164332300001,667.34476893 1247.9575093400001,669.58584504 1247.9575093400001,672.34476893 C1247.9575093400001,675.10369282 1245.7164332300001,677.34476893 1242.9575093400001,677.34476893 C1240.19858545,677.34476893 1237.9575093400001,675.10369282 1237.9575093400001,672.34476893 C1237.9575093400001,669.58584504 1240.19858545,667.34476893 1242.9575093400001,667.34476893 L1242.9575093400001,667.34476893 M1270.23023661,671.89022348 C1272.9891605,671.89022348 1275.23023661,674.13129959 1275.23023661,676.89022348 C1275.23023661,679.64914737 1272.9891605,681.89022348 1270.23023661,681.89022348 C1267.4713127300001,681.89022348 1265.23023661,679.64914737 1265.23023661,676.89022348 C1265.23023661,674.13129959 1267.4713127300001,671.89022348 1270.23023661,671.89022348 L1270.23023661,671.89022348 M1297.50296389,676.4356780200001 C1300.26188778,676.4356780200001 1302.50296389,678.6767541300001 1302.50296389,681.4356780200001 C1302.50296389,684.19460191 1300.26188778,686.4356780200001 1297.50296389,686.4356780200001 C1294.74404,686.4356780200001 1292.50296389,684.19460191 1292.50296389,681.4356780200001 C1292.50296389,678.6767541300001 1294.74404,676.4356780200001 1297.50296389,676.4356780200001 L1297.50296389,676.4356780200001 M997.50296389,655.00710659 C1000.26188778,655.00710659 1002.50296389,657.24818271 1002.50296389,660.00710659 C1002.50296389,662.76603048 1000.26188778,665.00710659 997.50296389,665.00710659 C994.74404,665.00710659 992.50296389,662.76603048 992.50296389,660.00710659 C992.50296389,657.24818271 994.74404,655.00710659 997.50296389,655.00710659 L997.50296389,655.00710659 M1024.77569116,659.55256114 C1027.53461505,659.55256114 1029.77569116,661.79363725 1029.77569116,664.55256114 C1029.77569116,667.31148503 1027.53461505,669.55256114 1024.77569116,669.55256114 C1022.0167672700001,669.55256114 1019.7756911600001,667.31148503 1019.7756911600001,664.55256114 C1019.7756911600001,661.79363725 1022.0167672700001,659.55256114 1024.77569116,659.55256114 L1024.77569116,659.55256114 M1052.04841843,664.09801569 C1054.80734232,664.09801569 1057.04841843,666.3390918 1057.04841843,669.09801569 C1057.04841843,671.85693957 1054.80734232,674.09801569 1052.04841843,674.09801569 C1049.28949454,674.09801569 1047.04841843,671.85693957 1047.04841843,669.09801569 C1047.04841843,666.3390918 1049.28949454,664.09801569 1052.04841843,664.09801569 L1052.04841843,664.09801569 M1079.32114571,668.64347023 C1082.08006959,668.64347023 1084.32114571,670.88454634 1084.32114571,673.64347023 C1084.32114571,676.40239412 1082.08006959,678.64347023 1079.32114571,678.64347023 C1076.56222182,678.64347023 1074.32114571,676.40239412 1074.32114571,673.64347023 C1074.32114571,670.88454634 1076.56222182,668.64347023 1079.32114571,668.64347023 L1079.32114571,668.64347023 M1106.59387298,673.18892478 C1109.35279687,673.18892478 1111.59387298,675.43000089 1111.59387298,678.18892478 C1111.59387298,680.94784867 1109.35279687,683.18892478 1106.59387298,683.18892478 C1103.83494909,683.18892478 1101.59387298,680.94784867 1101.59387298,678.18892478 C1101.59387298,675.43000089 1103.83494909,673.18892478 1106.59387298,673.18892478 L1106.59387298,673.18892478 M1133.86660025,677.73437932 C1136.62552414,677.73437932 1138.86660025,679.97545543 1138.86660025,682.73437932 C1138.86660025,685.49330321 1136.62552414,687.73437932 1133.86660025,687.73437932 C1131.10767636,687.73437932 1128.86660025,685.49330321 1128.86660025,682.73437932 C1128.86660025,679.97545543 1131.10767636,677.73437932 1133.86660025,677.73437932 L1133.86660025,677.73437932 M1161.13932752,682.2798338700001 C1163.89825141,682.2798338700001 1166.13932752,684.52090998 1166.13932752,687.2798338700001 C1166.13932752,690.0387577600001 1163.89825141,692.2798338700001 1161.13932752,692.2798338700001 C1158.38040363,692.2798338700001 1156.13932752,690.0387577600001 1156.13932752,687.2798338700001 C1156.13932752,684.52090998 1158.38040363,682.2798338700001 1161.13932752,682.2798338700001 L1161.13932752,682.2798338700001 M1188.4120548,686.82528841 C1191.17097868,686.82528841 1193.4120548,689.06636452 1193.4120548,691.82528841 C1193.4120548,694.5842123 1191.17097868,696.82528841 1188.4120548,696.82528841 C1185.65313091,696.82528841 1183.4120548,694.5842123 1183.4120548,691.82528841 C1183.4120548,689.06636452 1185.65313091,686.82528841 1188.4120548,686.82528841 L1188.4120548,686.82528841 M1215.68478207,691.37074296 C1218.44370596,691.37074296 1220.68478207,693.61181907 1220.68478207,696.37074296 C1220.68478207,699.12966685 1218.44370596,701.37074296 1215.68478207,701.37074296 C1212.92585818,701.37074296 1210.68478207,699.12966685 1210.68478207,696.37074296 C1210.68478207,693.61181907 1212.92585818,691.37074296 1215.68478207,691.37074296 L1215.68478207,691.37074296 M1242.9575093400001,695.9161975000001 C1245.7164332300001,695.9161975000001 1247.9575093400001,698.1572736100001 1247.9575093400001,700.9161975000001 C1247.9575093400001,703.67512139 1245.7164332300001,705.9161975000001 1242.9575093400001,705.9161975000001 C1240.19858545,705.9161975000001 1237.9575093400001,703.67512139 1237.9575093400001,700.9161975000001 C1237.9575093400001,698.1572736100001 1240.19858545,695.9161975000001 1242.9575093400001,695.9161975000001 L1242.9575093400001,695.9161975000001 M1270.23023661,700.46165205 C1272.9891605,700.46165205 1275.23023661,702.70272816 1275.23023661,705.46165205 C1275.23023661,708.22057594 1272.9891605,710.46165205 1270.23023661,710.46165205 C1267.4713127300001,710.46165205 1265.23023661,708.22057594 1265.23023661,705.46165205 C1265.23023661,702.70272816 1267.4713127300001,700.46165205 1270.23023661,700.46165205 L1270.23023661,700.46165205 M1297.50296389,705.00710659 C1300.26188778,705.00710659 1302.50296389,707.24818271 1302.50296389,710.00710659 C1302.50296389,712.76603048 1300.26188778,715.00710659 1297.50296389,715.00710659 C1294.74404,715.00710659 1292.50296389,712.76603048 1292.50296389,710.00710659 C1292.50296389,707.24818271 1294.74404,705.00710659 1297.50296389,705.00710659 L1297.50296389,705.00710659 M997.50296389,683.57853517 C1000.26188778,683.57853517 1002.50296389,685.81961128 1002.50296389,688.57853517 C1002.50296389,691.33745906 1000.26188778,693.57853517 997.50296389,693.57853517 C994.74404,693.57853517 992.50296389,691.33745906 992.50296389,688.57853517 C992.50296389,685.81961128 994.74404,683.57853517 997.50296389,683.57853517 L997.50296389,683.57853517 M1024.77569116,688.12398971 C1027.53461505,688.12398971 1029.77569116,690.36506582 1029.77569116,693.12398971 C1029.77569116,695.8829136 1027.53461505,698.12398971 1024.77569116,698.12398971 C1022.0167672700001,698.12398971 1019.7756911600001,695.8829136 1019.7756911600001,693.12398971 C1019.7756911600001,690.36506582 1022.0167672700001,688.12398971 1024.77569116,688.12398971 L1024.77569116,688.12398971 M1052.04841843,692.66944426 C1054.80734232,692.66944426 1057.04841843,694.91052037 1057.04841843,697.66944426 C1057.04841843,700.42836815 1054.80734232,702.66944426 1052.04841843,702.66944426 C1049.28949454,702.66944426 1047.04841843,700.42836815 1047.04841843,697.66944426 C1047.04841843,694.91052037 1049.28949454,692.66944426 1052.04841843,692.66944426 L1052.04841843,692.66944426 M1079.32114571,697.2148988 C1082.08006959,697.2148988 1084.32114571,699.45597491 1084.32114571,702.2148988 C1084.32114571,704.97382269 1082.08006959,707.2148988 1079.32114571,707.2148988 C1076.56222182,707.2148988 1074.32114571,704.97382269 1074.32114571,702.2148988 C1074.32114571,699.45597491 1076.56222182,697.2148988 1079.32114571,697.2148988 L1079.32114571,697.2148988 M1106.59387298,701.7603533500001 C1109.35279687,701.7603533500001 1111.59387298,704.00142946 1111.59387298,706.7603533500001 C1111.59387298,709.5192772400001 1109.35279687,711.7603533500001 1106.59387298,711.7603533500001 C1103.83494909,711.7603533500001 1101.59387298,709.5192772400001 1101.59387298,706.7603533500001 C1101.59387298,704.00142946 1103.83494909,701.7603533500001 1106.59387298,701.7603533500001 L1106.59387298,701.7603533500001 M1133.86660025,706.30580789 C1136.62552414,706.30580789 1138.86660025,708.546884 1138.86660025,711.30580789 C1138.86660025,714.06473178 1136.62552414,716.30580789 1133.86660025,716.30580789 C1131.10767636,716.30580789 1128.86660025,714.06473178 1128.86660025,711.30580789 C1128.86660025,708.546884 1131.10767636,706.30580789 1133.86660025,706.30580789 L1133.86660025,706.30580789 M1161.13932752,710.85126244 C1163.89825141,710.85126244 1166.13932752,713.09233855 1166.13932752,715.85126244 C1166.13932752,718.61018633 1163.89825141,720.85126244 1161.13932752,720.85126244 C1158.38040363,720.85126244 1156.13932752,718.61018633 1156.13932752,715.85126244 C1156.13932752,713.09233855 1158.38040363,710.85126244 1161.13932752,710.85126244 L1161.13932752,710.85126244 M1188.4120548,715.3967169800001 C1191.17097868,715.3967169800001 1193.4120548,717.6377931000001 1193.4120548,720.3967169800001 C1193.4120548,723.1556408700001 1191.17097868,725.3967169800001 1188.4120548,725.3967169800001 C1185.65313091,725.3967169800001 1183.4120548,723.1556408700001 1183.4120548,720.3967169800001 C1183.4120548,717.6377931000001 1185.65313091,715.3967169800001 1188.4120548,715.3967169800001 L1188.4120548,715.3967169800001 M1215.68478207,719.94217153 C1218.44370596,719.94217153 1220.68478207,722.18324764 1220.68478207,724.94217153 C1220.68478207,727.70109542 1218.44370596,729.94217153 1215.68478207,729.94217153 C1212.92585818,729.94217153 1210.68478207,727.70109542 1210.68478207,724.94217153 C1210.68478207,722.18324764 1212.92585818,719.94217153 1215.68478207,719.94217153 L1215.68478207,719.94217153 M1242.9575093400001,724.48762608 C1245.7164332300001,724.48762608 1247.9575093400001,726.72870219 1247.9575093400001,729.48762608 C1247.9575093400001,732.24654996 1245.7164332300001,734.48762608 1242.9575093400001,734.48762608 C1240.19858545,734.48762608 1237.9575093400001,732.24654996 1237.9575093400001,729.48762608 C1237.9575093400001,726.72870219 1240.19858545,724.48762608 1242.9575093400001,724.48762608 L1242.9575093400001,724.48762608 M1270.23023661,729.03308062 C1272.9891605,729.03308062 1275.23023661,731.27415673 1275.23023661,734.03308062 C1275.23023661,736.79200451 1272.9891605,739.03308062 1270.23023661,739.03308062 C1267.4713127300001,739.03308062 1265.23023661,736.79200451 1265.23023661,734.03308062 C1265.23023661,731.27415673 1267.4713127300001,729.03308062 1270.23023661,729.03308062 L1270.23023661,729.03308062 M1297.50296389,733.57853517 C1300.26188778,733.57853517 1302.50296389,735.81961128 1302.50296389,738.57853517 C1302.50296389,741.33745906 1300.26188778,743.57853517 1297.50296389,743.57853517 C1294.74404,743.57853517 1292.50296389,741.33745906 1292.50296389,738.57853517 C1292.50296389,735.81961128 1294.74404,733.57853517 1297.50296389,733.57853517 L1297.50296389,733.57853517 M997.50296389,712.14996374 C1000.26188778,712.14996374 1002.50296389,714.39103985 1002.50296389,717.14996374 C1002.50296389,719.90888763 1000.26188778,722.14996374 997.50296389,722.14996374 C994.74404,722.14996374 992.50296389,719.90888763 992.50296389,717.14996374 C992.50296389,714.39103985 994.74404,712.14996374 997.50296389,712.14996374 L997.50296389,712.14996374 M1024.77569116,716.69541828 C1027.53461505,716.69541828 1029.77569116,718.93649439 1029.77569116,721.69541828 C1029.77569116,724.45434217 1027.53461505,726.69541828 1024.77569116,726.69541828 C1022.0167672700001,726.69541828 1019.7756911600001,724.45434217 1019.7756911600001,721.69541828 C1019.7756911600001,718.93649439 1022.0167672700001,716.69541828 1024.77569116,716.69541828 L1024.77569116,716.69541828 M1052.04841843,721.2408728300001 C1054.80734232,721.2408728300001 1057.04841843,723.48194894 1057.04841843,726.2408728300001 C1057.04841843,728.9997967200001 1054.80734232,731.2408728300001 1052.04841843,731.2408728300001 C1049.28949454,731.2408728300001 1047.04841843,728.9997967200001 1047.04841843,726.2408728300001 C1047.04841843,723.48194894 1049.28949454,721.2408728300001 1052.04841843,721.2408728300001 L1052.04841843,721.2408728300001 M1079.32114571,725.78632737 C1082.08006959,725.78632737 1084.32114571,728.02740349 1084.32114571,730.78632737 C1084.32114571,733.54525126 1082.08006959,735.78632737 1079.32114571,735.78632737 C1076.56222182,735.78632737 1074.32114571,733.54525126 1074.32114571,730.78632737 C1074.32114571,728.02740349 1076.56222182,725.78632737 1079.32114571,725.78632737 L1079.32114571,725.78632737 M1106.59387298,730.33178192 C1109.35279687,730.33178192 1111.59387298,732.57285803 1111.59387298,735.33178192 C1111.59387298,738.09070581 1109.35279687,740.33178192 1106.59387298,740.33178192 C1103.83494909,740.33178192 1101.59387298,738.09070581 1101.59387298,735.33178192 C1101.59387298,732.57285803 1103.83494909,730.33178192 1106.59387298,730.33178192 L1106.59387298,730.33178192 M1133.86660025,734.8772364600001 C1136.62552414,734.8772364600001 1138.86660025,737.1183125800001 1138.86660025,739.8772364600001 C1138.86660025,742.6361603500001 1136.62552414,744.8772364600001 1133.86660025,744.8772364600001 C1131.10767636,744.8772364600001 1128.86660025,742.6361603500001 1128.86660025,739.8772364600001 C1128.86660025,737.1183125800001 1131.10767636,734.8772364600001 1133.86660025,734.8772364600001 L1133.86660025,734.8772364600001 M1161.13932752,739.42269101 C1163.89825141,739.42269101 1166.13932752,741.66376712 1166.13932752,744.42269101 C1166.13932752,747.1816149 1163.89825141,749.42269101 1161.13932752,749.42269101 C1158.38040363,749.42269101 1156.13932752,747.1816149 1156.13932752,744.42269101 C1156.13932752,741.66376712 1158.38040363,739.42269101 1161.13932752,739.42269101 L1161.13932752,739.42269101 M1188.4120548,743.96814556 C1191.17097868,743.96814556 1193.4120548,746.20922167 1193.4120548,748.96814556 C1193.4120548,751.72706944 1191.17097868,753.96814556 1188.4120548,753.96814556 C1185.65313091,753.96814556 1183.4120548,751.72706944 1183.4120548,748.96814556 C1183.4120548,746.20922167 1185.65313091,743.96814556 1188.4120548,743.96814556 L1188.4120548,743.96814556 M1215.68478207,748.5136001 C1218.44370596,748.5136001 1220.68478207,750.7546762100001 1220.68478207,753.5136001 C1220.68478207,756.27252399 1218.44370596,758.5136001 1215.68478207,758.5136001 C1212.92585818,758.5136001 1210.68478207,756.27252399 1210.68478207,753.5136001 C1210.68478207,750.7546762100001 1212.92585818,748.5136001 1215.68478207,748.5136001 L1215.68478207,748.5136001 M1242.9575093400001,753.05905465 C1245.7164332300001,753.05905465 1247.9575093400001,755.30013076 1247.9575093400001,758.05905465 C1247.9575093400001,760.81797854 1245.7164332300001,763.05905465 1242.9575093400001,763.05905465 C1240.19858545,763.05905465 1237.9575093400001,760.81797854 1237.9575093400001,758.05905465 C1237.9575093400001,755.30013076 1240.19858545,753.05905465 1242.9575093400001,753.05905465 L1242.9575093400001,753.05905465 M1270.23023661,757.60450919 C1272.9891605,757.60450919 1275.23023661,759.8455853 1275.23023661,762.60450919 C1275.23023661,765.36343308 1272.9891605,767.60450919 1270.23023661,767.60450919 C1267.4713127300001,767.60450919 1265.23023661,765.36343308 1265.23023661,762.60450919 C1265.23023661,759.8455853 1267.4713127300001,757.60450919 1270.23023661,757.60450919 L1270.23023661,757.60450919 M1297.50296389,762.14996374 C1300.26188778,762.14996374 1302.50296389,764.39103985 1302.50296389,767.14996374 C1302.50296389,769.90888763 1300.26188778,772.14996374 1297.50296389,772.14996374 C1294.74404,772.14996374 1292.50296389,769.90888763 1292.50296389,767.14996374 C1292.50296389,764.39103985 1294.74404,762.14996374 1297.50296389,762.14996374 L1297.50296389,762.14996374 M997.50296389,740.72139231 C1000.26188778,740.72139231 1002.50296389,742.96246842 1002.50296389,745.72139231 C1002.50296389,748.4803162000001 1000.26188778,750.72139231 997.50296389,750.72139231 C994.74404,750.72139231 992.50296389,748.4803162000001 992.50296389,745.72139231 C992.50296389,742.96246842 994.74404,740.72139231 997.50296389,740.72139231 L997.50296389,740.72139231 M1024.77569116,745.26684685 C1027.53461505,745.26684685 1029.77569116,747.50792297 1029.77569116,750.26684685 C1029.77569116,753.02577074 1027.53461505,755.26684685 1024.77569116,755.26684685 C1022.0167672700001,755.26684685 1019.7756911600001,753.02577074 1019.7756911600001,750.26684685 C1019.7756911600001,747.50792297 1022.0167672700001,745.26684685 1024.77569116,745.26684685 L1024.77569116,745.26684685 M1052.04841843,749.8123014 C1054.80734232,749.8123014 1057.04841843,752.05337751 1057.04841843,754.8123014 C1057.04841843,757.57122529 1054.80734232,759.8123014 1052.04841843,759.8123014 C1049.28949454,759.8123014 1047.04841843,757.57122529 1047.04841843,754.8123014 C1047.04841843,752.05337751 1049.28949454,749.8123014 1052.04841843,749.8123014 L1052.04841843,749.8123014 M1079.32114571,754.3577559500001 C1082.08006959,754.3577559500001 1084.32114571,756.5988320600001 1084.32114571,759.3577559500001 C1084.32114571,762.1166798300001 1082.08006959,764.3577559500001 1079.32114571,764.3577559500001 C1076.56222182,764.3577559500001 1074.32114571,762.1166798300001 1074.32114571,759.3577559500001 C1074.32114571,756.5988320600001 1076.56222182,754.3577559500001 1079.32114571,754.3577559500001 L1079.32114571,754.3577559500001 M1106.59387298,758.90321049 C1109.35279687,758.90321049 1111.59387298,761.1442866 1111.59387298,763.90321049 C1111.59387298,766.66213438 1109.35279687,768.90321049 1106.59387298,768.90321049 C1103.83494909,768.90321049 1101.59387298,766.66213438 1101.59387298,763.90321049 C1101.59387298,761.1442866 1103.83494909,758.90321049 1106.59387298,758.90321049 L1106.59387298,758.90321049 M1133.86660025,763.44866504 C1136.62552414,763.44866504 1138.86660025,765.68974115 1138.86660025,768.44866504 C1138.86660025,771.20758893 1136.62552414,773.44866504 1133.86660025,773.44866504 C1131.10767636,773.44866504 1128.86660025,771.20758893 1128.86660025,768.44866504 C1128.86660025,765.68974115 1131.10767636,763.44866504 1133.86660025,763.44866504 L1133.86660025,763.44866504 M1161.13932752,767.99411958 C1163.89825141,767.99411958 1166.13932752,770.2351956900001 1166.13932752,772.99411958 C1166.13932752,775.75304347 1163.89825141,777.99411958 1161.13932752,777.99411958 C1158.38040363,777.99411958 1156.13932752,775.75304347 1156.13932752,772.99411958 C1156.13932752,770.2351956900001 1158.38040363,767.99411958 1161.13932752,767.99411958 L1161.13932752,767.99411958 M1188.4120548,772.53957413 C1191.17097868,772.53957413 1193.4120548,774.78065024 1193.4120548,777.53957413 C1193.4120548,780.29849802 1191.17097868,782.53957413 1188.4120548,782.53957413 C1185.65313091,782.53957413 1183.4120548,780.29849802 1183.4120548,777.53957413 C1183.4120548,774.78065024 1185.65313091,772.53957413 1188.4120548,772.53957413 L1188.4120548,772.53957413 M1215.68478207,777.08502867 C1218.44370596,777.08502867 1220.68478207,779.32610478 1220.68478207,782.08502867 C1220.68478207,784.84395256 1218.44370596,787.08502867 1215.68478207,787.08502867 C1212.92585818,787.08502867 1210.68478207,784.84395256 1210.68478207,782.08502867 C1210.68478207,779.32610478 1212.92585818,777.08502867 1215.68478207,777.08502867 L1215.68478207,777.08502867 M1242.9575093400001,781.63048322 C1245.7164332300001,781.63048322 1247.9575093400001,783.87155933 1247.9575093400001,786.63048322 C1247.9575093400001,789.38940711 1245.7164332300001,791.63048322 1242.9575093400001,791.63048322 C1240.19858545,791.63048322 1237.9575093400001,789.38940711 1237.9575093400001,786.63048322 C1237.9575093400001,783.87155933 1240.19858545,781.63048322 1242.9575093400001,781.63048322 L1242.9575093400001,781.63048322 M1270.23023661,786.17593776 C1272.9891605,786.17593776 1275.23023661,788.41701387 1275.23023661,791.17593776 C1275.23023661,793.93486165 1272.9891605,796.17593776 1270.23023661,796.17593776 C1267.4713127300001,796.17593776 1265.23023661,793.93486165 1265.23023661,791.17593776 C1265.23023661,788.41701387 1267.4713127300001,786.17593776 1270.23023661,786.17593776 L1270.23023661,786.17593776 M1297.50296389,790.72139231 C1300.26188778,790.72139231 1302.50296389,792.96246842 1302.50296389,795.72139231 C1302.50296389,798.4803162000001 1300.26188778,800.72139231 1297.50296389,800.72139231 C1294.74404,800.72139231 1292.50296389,798.4803162000001 1292.50296389,795.72139231 C1292.50296389,792.96246842 1294.74404,790.72139231 1297.50296389,790.72139231 L1297.50296389,790.72139231 diff --git a/vendor/linesweeper/tests/regression/graphite_4.txt b/vendor/linesweeper/tests/regression/graphite_4.txt new file mode 100644 index 00000000..cfaea76e --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_4.txt @@ -0,0 +1,2 @@ +M1106.59387298,616.04606763 C1106.59302755,616.04606768 1106.59260483,616.04606774 1106.59262791,616.04606833 C1103.833007,616.04801024 1101.59387298,618.28833942 1101.59387298,621.04606763 C1101.59387298,623.80379584 1103.833007,626.04412502 1106.59028622,626.0460663700001 C1106.59036086,626.04606642 1106.59043808,626.04606647 1106.59053213,626.04606697 C1106.59044892,626.04606749 1106.59155558,626.04606756 1106.59387298,626.04606763 C1106.59619038,626.04606756 1106.59729704,626.04606749 1106.59721383,626.04606697 C1106.59730788,626.04606647 1106.5973851,626.04606642 1106.59745974,626.0460663700001 C1109.35473896,626.04412502 1111.59387298,623.80379584 1111.59387298,621.04606763 C1111.59387298,618.28833942 1109.35473896,616.04801024 1106.59511805,616.04606833 C1106.59514113,616.04606774 1106.59471841,616.04606768 1106.59387298,616.04606763 L1106.59387298,616.04606763 M1084.17786655,617.6925065400001 C1083.92379166,618.24982361 1083.66489428,618.81553061 1083.40117472,619.3894593 C1083.7593015300001,618.88489596 1084.02624396,618.3112009 1084.17786655,617.6925065400001 L1084.17786655,617.6925065400001 M1133.86660025,620.59152218 C1133.8657548200001,620.59152223 1133.8653321,620.5915222900001 1133.86535518,620.59152288 C1131.10573427,620.59346479 1128.86660025,622.83379397 1128.86660025,625.59152218 C1128.86660025,628.3492503900001 1131.10573427,630.5895795700001 1133.86301349,630.59152092 C1133.86308813,630.59152097 1133.86316535,630.59152102 1133.8632594,630.59152152 C1133.86317619,630.59152204 1133.86428286,630.59152211 1133.86660025,630.59152218 C1133.86891764,630.59152211 1133.87002431,630.59152204 1133.8699411,630.59152152 C1133.87003515,630.59152102 1133.87011237,630.59152097 1133.87018701,630.59152092 C1136.62746623,630.5895795700001 1138.86660025,628.3492503900001 1138.86660025,625.59152218 C1138.86660025,622.83379397 1136.62746623,620.59346479 1133.86784532,620.59152288 C1133.8678684000001,620.5915222900001 1133.86744568,620.59152223 1133.86660025,620.59152218 L1133.86660025,620.59152218 M1161.13932752,625.13697672 C1158.38037404,625.1370063100001 1156.13932752,627.37807105 1156.13932752,630.13697672 C1156.13932752,632.89470493 1158.37846154,635.13503411 1161.13574076,635.13697546 C1161.1358154,635.13697551 1161.13589262,635.13697556 1161.13598667,635.13697606 C1161.13590346,635.13697658 1161.1370101300001,635.1369766500001 1161.13932752,635.13697672 C1161.14164492,635.1369766500001 1161.14275158,635.13697658 1161.1426683700001,635.13697606 C1161.14276242,635.13697556 1161.14283964,635.13697551 1161.14291428,635.13697546 C1163.9001935000001,635.13503411 1166.13932752,632.89470493 1166.13932752,630.13697672 C1166.13932752,627.37805284 1163.89825141,625.13697672 1161.13932752,625.13697672 L1161.13932752,625.13697672 M1183.41754278,634.44642006 C1183.4138974100001,634.5246381100001 1183.4120548,634.6033218 1183.4120548,634.68243127 C1183.4120548,637.4413551600001 1185.65313091,639.68243127 1188.4120548,639.68243127 C1188.40973827,639.68243048 1188.4086199,639.68243011 1188.40871395,639.68243061 C1188.40863074,639.68243113 1188.40973741,639.6824312 1188.4120548,639.68243127 C1188.41437219,639.6824312 1188.4154788600001,639.68243113 1188.41539565,639.68243061 C1188.4154897,639.68243011 1188.41556692,639.68243006 1188.41564156,639.68243001 C1189.50876543,639.68166036 1190.520451,639.32907713 1191.34346184,638.73191583 C1188.7081734600001,637.28051938 1186.0650880800001,635.8520703400001 1183.41754278,634.44642006 L1183.41754278,634.44642006 M1079.32114571,640.07204166 C1079.32030028,640.07204171 1079.3198775600001,640.07204177 1079.31990064,640.0720423600001 C1076.56027973,640.07398427 1074.32114571,642.31431345 1074.32114571,645.07204166 C1074.32114571,647.8297698700001 1076.56027973,650.0700990500001 1079.31755895,650.0720404 C1079.31763359,650.07204045 1079.31771081,650.0720405 1079.31780486,650.072041 C1079.31772165,650.07204152 1079.31882832,650.07204159 1079.32114571,650.07204166 C1079.3234631,650.07204159 1079.32456977,650.07204152 1079.32448656,650.072041 C1079.32458061,650.0720405 1079.32465783,650.07204045 1079.32473247,650.0720404 C1082.08201168,650.0700990500001 1084.32114571,647.8297698700001 1084.32114571,645.07204166 C1084.32114571,642.31431345 1082.08201168,640.07398427 1079.32239078,640.0720423600001 C1079.32241386,640.07204177 1079.32199114,640.07204171 1079.32114571,640.07204166 L1079.32114571,640.07204166 M1106.59387298,644.61749621 C1106.59302755,644.61749626 1106.59260483,644.61749632 1106.59262791,644.61749691 C1103.833007,644.61943882 1101.59387298,646.859768 1101.59387298,649.61749621 C1101.59387298,652.37522441 1103.833007,654.6155536 1106.59028622,654.61749495 C1106.59036086,654.617495 1106.59043808,654.61749505 1106.59053213,654.6174955500001 C1106.59044892,654.61749607 1106.59155558,654.61749614 1106.59387298,654.61749621 C1106.59619037,654.61749614 1106.59729704,654.61749607 1106.59721383,654.6174955500001 C1106.59730788,654.61749505 1106.5973851,654.617495 1106.59745974,654.61749495 C1109.35473896,654.6155536 1111.59387298,652.37522441 1111.59387298,649.61749621 C1111.59387298,646.859768 1109.35473896,644.61943882 1106.59511805,644.61749691 C1106.59514113,644.61749632 1106.59471841,644.61749626 1106.59387298,644.61749621 L1106.59387298,644.61749621 M1133.86660025,649.16295075 C1133.8657548200001,649.1629508 1133.8653321,649.16295086 1133.86535518,649.16295145 C1131.10573427,649.1648933600001 1128.86660025,651.4052225400001 1128.86660025,654.16295075 C1128.86660025,656.92067896 1131.10573427,659.16100814 1133.86301349,659.16294949 C1133.86308813,659.16294954 1133.86316535,659.16294959 1133.8632594,659.16295009 C1133.86317619,659.16295061 1133.86428286,659.16295068 1133.86660025,659.16295075 C1133.86891764,659.16295068 1133.87002431,659.16295061 1133.8699411,659.16295009 C1133.87003515,659.16294959 1133.87011237,659.16294954 1133.87018701,659.16294949 C1136.62746623,659.16100814 1138.86660025,656.92067896 1138.86660025,654.16295075 C1138.86660025,651.4052225400001 1136.62746623,649.1648933600001 1133.86784532,649.16295145 C1133.8678684000001,649.16295086 1133.86744568,649.1629508 1133.86660025,649.16295075 L1133.86660025,649.16295075 M1161.13932752,653.7084053 C1161.13848209,653.70840535 1161.13805937,653.7084054100001 1161.13808245,653.708406 C1158.37846154,653.71034791 1156.13932752,655.95067709 1156.13932752,658.7084053 C1156.13932752,661.4661335000001 1158.37846154,663.70646269 1161.13574076,663.70840404 C1161.1358154,663.70840409 1161.13589262,663.70840414 1161.13598667,663.70840464 C1161.13590346,663.70840516 1161.13701012,663.70840523 1161.13932752,663.7084053 C1161.14164491,663.70840523 1161.14275158,663.70840516 1161.1426683700001,663.70840464 C1161.14276242,663.70840414 1161.14283964,663.70840409 1161.14291428,663.70840404 C1163.9001935000001,663.70646269 1166.13932752,661.4661335000001 1166.13932752,658.7084053 C1166.13932752,655.95067709 1163.9001935000001,653.71034791 1161.14057259,653.708406 C1161.14059567,653.7084054100001 1161.14017295,653.70840535 1161.13932752,653.7084053 L1161.13932752,653.7084053 M1188.4120548,658.25385984 C1188.41120937,658.2538598900001 1188.41078665,658.25385995 1188.41080973,658.25386054 C1185.65118882,658.25580245 1183.4120548,660.49613163 1183.4120548,663.25385984 C1183.4120548,666.01158805 1185.65118882,668.25191723 1188.4084680400001,668.25385858 C1188.40854268,668.25385863 1188.4086199,668.25385868 1188.40871395,668.2538591800001 C1188.40863074,668.2538597 1188.40973741,668.25385977 1188.4120548,668.25385984 C1188.41437219,668.25385977 1188.4154788600001,668.2538597 1188.41539565,668.2538591800001 C1188.4154897,668.25385868 1188.41556692,668.25385863 1188.41564156,668.25385858 C1191.17292077,668.25191723 1193.4120548,666.01158805 1193.4120548,663.25385984 C1193.4120548,660.49613163 1191.17292077,658.25580245 1188.41329987,658.25386054 C1188.41332295,658.25385995 1188.41290023,658.2538598900001 1188.4120548,658.25385984 L1188.4120548,658.25385984 M1215.68478207,662.7993143900001 C1215.68393664,662.79931444 1215.68351392,662.7993145 1215.6835370000001,662.79931509 C1212.92391609,662.801257 1210.68478207,665.04158618 1210.68478207,667.7993143900001 C1210.68478207,670.5570426 1212.92391609,672.79737178 1215.68119531,672.79931313 C1215.6812699500001,672.79931318 1215.68134717,672.79931323 1215.6814412200001,672.79931373 C1215.68135801,672.7993142500001 1215.68246468,672.79931432 1215.68478207,672.7993143900001 C1215.6870994600001,672.79931432 1215.68820613,672.7993142500001 1215.68812292,672.79931373 C1215.68821697,672.79931323 1215.68829419,672.79931318 1215.68836883,672.79931313 C1218.44564805,672.79737178 1220.68478207,670.5570426 1220.68478207,667.7993143900001 C1220.68478207,665.04158618 1218.44564805,662.801257 1215.68602714,662.79931509 C1215.68605022,662.7993145 1215.6856275,662.79931444 1215.68478207,662.7993143900001 L1215.68478207,662.7993143900001 M1079.32114571,668.64347023 C1079.32030028,668.64347028 1079.3198775600001,668.64347034 1079.31990064,668.64347093 C1076.56027973,668.6454128400001 1074.32114571,670.8857420200001 1074.32114571,673.64347023 C1074.32114571,676.40119844 1076.56027973,678.64152762 1079.31755895,678.64346897 C1079.31763359,678.64346902 1079.31771081,678.64346907 1079.31780486,678.64346957 C1079.31772165,678.64347009 1079.31882832,678.64347016 1079.32114571,678.64347023 C1079.3234631,678.64347016 1079.32456977,678.64347009 1079.32448656,678.64346957 C1079.32458061,678.64346907 1079.32465783,678.64346902 1079.32473247,678.64346897 C1082.08201168,678.64152762 1084.32114571,676.40119844 1084.32114571,673.64347023 C1084.32114571,670.8857420200001 1082.08201168,668.6454128400001 1079.32239078,668.64347093 C1079.32241386,668.64347034 1079.32199114,668.64347028 1079.32114571,668.64347023 L1079.32114571,668.64347023 M1239.3382182,668.89693067 C1238.48295777,669.79436937 1237.9575093400001,671.0088611900001 1237.9575093400001,672.34476893 C1237.9575093400001,675.10369282 1240.19858545,677.34476893 1242.9575093400001,677.34476893 C1242.95519281,677.34476814 1242.95407444,677.34476777 1242.95416849,677.34476827 C1242.95408528,677.34476879 1242.95519195,677.34476886 1242.9575093400001,677.34476893 C1242.95982673,677.34476886 1242.9609334,677.34476879 1242.96085019,677.34476827 C1242.9609442400001,677.34476777 1242.96102146,677.34476772 1242.9610961,677.34476767 C1244.8096086,677.34346617 1246.42523973,676.33611684 1247.2894246600001,674.84128289 C1244.67813569,672.82987588 1242.02617975,670.84849411 1239.3382182,668.89693067 L1239.3382182,668.89693067 M1106.59387298,673.18892478 C1106.59302755,673.18892483 1106.59260483,673.1889248900001 1106.59262791,673.18892548 C1103.833007,673.19086739 1101.59387298,675.43119657 1101.59387298,678.18892478 C1101.59387298,680.94665299 1103.833007,683.18698217 1106.59028622,683.18892352 C1106.59036086,683.18892357 1106.59043808,683.18892362 1106.59053213,683.18892412 C1106.59044892,683.18892464 1106.59155559,683.18892471 1106.59387298,683.18892478 C1106.59619037,683.18892471 1106.59729704,683.18892464 1106.59721383,683.18892412 C1106.59730788,683.18892362 1106.5973851,683.18892357 1106.59745974,683.18892352 C1109.35473896,683.18698217 1111.59387298,680.94665299 1111.59387298,678.18892478 C1111.59387298,675.43119657 1109.35473896,673.19086739 1106.59511805,673.18892548 C1106.59514113,673.1889248900001 1106.59471841,673.18892483 1106.59387298,673.18892478 L1106.59387298,673.18892478 M1133.86660025,677.73437932 C1133.8657548200001,677.73437937 1133.8653321,677.73437943 1133.86535518,677.73438002 C1131.10573427,677.73632193 1128.86660025,679.97665111 1128.86660025,682.73437932 C1128.86660025,685.49210753 1131.10573427,687.73243671 1133.86301349,687.73437806 C1133.86308813,687.73437811 1133.86316535,687.73437816 1133.8632594,687.7343786600001 C1133.86317619,687.73437918 1133.86428286,687.73437925 1133.86660025,687.73437932 C1133.86891764,687.73437925 1133.87002431,687.73437918 1133.8699411,687.7343786600001 C1133.87003515,687.73437816 1133.87011237,687.73437811 1133.87018701,687.73437806 C1136.62746623,687.73243671 1138.86660025,685.49210753 1138.86660025,682.73437932 C1138.86660025,679.97665111 1136.62746623,677.73632193 1133.86784532,677.73438002 C1133.8678684000001,677.73437943 1133.86744568,677.73437937 1133.86660025,677.73437932 L1133.86660025,677.73437932 M1161.13932752,682.2798338700001 C1161.13848209,682.27983392 1161.13805937,682.27983398 1161.13808245,682.27983457 C1158.37846154,682.28177648 1156.13932752,684.52210566 1156.13932752,687.2798338700001 C1156.13932752,690.03756208 1158.37846154,692.27789126 1161.13574076,692.27983261 C1161.1358154,692.27983266 1161.13589262,692.27983271 1161.13598667,692.27983321 C1161.13590346,692.2798337300001 1161.13701012,692.2798338 1161.13932752,692.2798338700001 C1161.14164492,692.2798338 1161.14275158,692.2798337300001 1161.1426683700001,692.27983321 C1161.14276242,692.27983271 1161.14283964,692.27983266 1161.14291428,692.27983261 C1163.9001935000001,692.27789126 1166.13932752,690.03756208 1166.13932752,687.2798338700001 C1166.13932752,684.52210566 1163.9001935000001,682.28177648 1161.14057259,682.27983457 C1161.14059567,682.27983398 1161.14017295,682.27983392 1161.13932752,682.2798338700001 L1161.13932752,682.2798338700001 M1188.4120548,686.82528841 C1188.41120937,686.82528846 1188.41078665,686.82528852 1188.41080973,686.82528911 C1185.65118882,686.82723102 1183.4120548,689.0675602 1183.4120548,691.82528841 C1183.4120548,694.58301662 1185.65118882,696.8233458 1188.4084680400001,696.82528715 C1188.40854268,696.8252872 1188.4086199,696.82528725 1188.40871395,696.82528775 C1188.40863074,696.82528827 1188.40973741,696.82528834 1188.4120548,696.82528841 C1188.41437219,696.82528834 1188.4154788600001,696.82528827 1188.41539565,696.82528775 C1188.4154897,696.82528725 1188.41556692,696.8252872 1188.41564156,696.82528715 C1191.17292077,696.8233458 1193.4120548,694.58301662 1193.4120548,691.82528841 C1193.4120548,689.0675602 1191.17292077,686.82723102 1188.41329987,686.82528911 C1188.41332295,686.82528852 1188.41290023,686.82528846 1188.4120548,686.82528841 L1188.4120548,686.82528841 M1215.68478207,691.37074296 C1215.68393664,691.3707430100001 1215.68351392,691.37074307 1215.6835370000001,691.37074366 C1212.92391609,691.37268557 1210.68478207,693.61301475 1210.68478207,696.37074296 C1210.68478207,699.12847117 1212.92391609,701.36880035 1215.68119531,701.3707417 C1215.6812699500001,701.37074175 1215.68134717,701.3707418 1215.6814412200001,701.3707423 C1215.68135801,701.37074282 1215.68246467,701.37074289 1215.68478207,701.37074296 C1215.68709947,701.37074289 1215.68820613,701.37074282 1215.68812292,701.3707423 C1215.68821697,701.3707418 1215.68829419,701.37074175 1215.68836883,701.3707417 C1218.44564805,701.36880035 1220.68478207,699.12847117 1220.68478207,696.37074296 C1220.68478207,693.61301475 1218.44564805,691.37268557 1215.68602714,691.37074366 C1215.68605022,691.37074307 1215.6856275,691.3707430100001 1215.68478207,691.37074296 L1215.68478207,691.37074296 M1052.04841843,692.66944426 C1052.047573,692.66944431 1052.04715028,692.6694443700001 1052.04717336,692.66944496 C1049.28755245,692.67138687 1047.04841843,694.91171605 1047.04841843,697.66944426 C1047.04841843,700.42717247 1049.28755245,702.66750165 1052.0448316700001,702.669443 C1052.04490631,702.66944305 1052.04498353,702.6694431 1052.04507758,702.6694436 C1052.04499437,702.66944412 1052.04610104,702.66944419 1052.04841843,702.66944426 C1052.05073582,702.66944419 1052.0518424900001,702.66944412 1052.05175928,702.6694436 C1052.05185333,702.6694431 1052.05193055,702.66944305 1052.05200519,702.669443 C1054.80928441,702.66750165 1057.04841843,700.42717247 1057.04841843,697.66944426 C1057.04841843,694.91171605 1054.80928441,692.67138687 1052.0496635,692.66944496 C1052.04968658,692.6694443700001 1052.04926386,692.66944431 1052.04841843,692.66944426 L1052.04841843,692.66944426 M1242.9575093400001,695.9161975000001 C1242.95666391,695.91619755 1242.9562411900001,695.91619761 1242.95626427,695.9161982 C1240.19664336,695.91814011 1237.9575093400001,698.15846929 1237.9575093400001,700.9161975000001 C1237.9575093400001,703.67392571 1240.19664336,705.91425489 1242.95392258,705.91619624 C1242.95399722,705.91619629 1242.95407444,705.91619634 1242.95416849,705.91619684 C1242.95408528,705.9161973600001 1242.95519195,705.91619743 1242.9575093400001,705.9161975000001 C1242.95982673,705.91619743 1242.9609334,705.9161973600001 1242.96085019,705.91619684 C1242.9609442400001,705.91619634 1242.96102146,705.91619629 1242.9610961,705.91619624 C1245.71837532,705.91425489 1247.9575093400001,703.67392571 1247.9575093400001,700.9161975000001 C1247.9575093400001,698.15846929 1245.71837532,695.91814011 1242.95875441,695.9161982 C1242.95877749,695.91619761 1242.95835477,695.91619755 1242.9575093400001,695.9161975000001 L1242.9575093400001,695.9161975000001 M1079.32114571,697.2148988 C1079.32030028,697.21489885 1079.3198775600001,697.21489891 1079.31990064,697.2148995 C1076.56027973,697.21684141 1074.32114571,699.45717059 1074.32114571,702.2148988 C1074.32114571,704.97262701 1076.56027973,707.21295619 1079.31755895,707.21489754 C1079.31763359,707.21489759 1079.31771081,707.21489764 1079.31780486,707.2148981400001 C1079.31772165,707.21489866 1079.31882832,707.21489873 1079.32114571,707.2148988 C1079.3234631,707.21489873 1079.32456977,707.21489866 1079.32448656,707.2148981400001 C1079.32458061,707.21489764 1079.32465783,707.21489759 1079.32473247,707.21489754 C1082.08201168,707.21295619 1084.32114571,704.97262701 1084.32114571,702.2148988 C1084.32114571,699.45717059 1082.08201168,697.21684141 1079.32239078,697.2148995 C1079.32241386,697.21489891 1079.32199114,697.21489885 1079.32114571,697.2148988 L1079.32114571,697.2148988 M1270.23023661,700.46165205 C1270.22939118,700.4616521 1270.22896846,700.46165216 1270.22899154,700.46165275 C1267.46937064,700.46359466 1265.23023661,702.70392384 1265.23023661,705.46165205 C1265.23023661,708.21938026 1267.46937064,710.45970944 1270.22664985,710.46165079 C1270.22672449,710.4616508400001 1270.22680171,710.46165089 1270.22689576,710.46165139 C1270.22681255,710.46165191 1270.22791922,710.46165198 1270.23023661,710.46165205 C1270.232554,710.46165198 1270.23366067,710.46165191 1270.23357746,710.46165139 C1270.23367151,710.46165089 1270.2337487300001,710.4616508400001 1270.23382337,710.46165079 C1272.99110259,710.45970944 1275.23023661,708.21938026 1275.23023661,705.46165205 C1275.23023661,702.70392384 1272.99110259,700.46359466 1270.2314816800001,700.46165275 C1270.23150476,700.46165216 1270.23108204,700.4616521 1270.23023661,700.46165205 L1270.23023661,700.46165205 M1106.59387298,701.7603533500001 C1106.59302755,701.7603534 1106.59260483,701.76035346 1106.59262791,701.76035405 C1103.833007,701.76229596 1101.59387298,704.00262514 1101.59387298,706.7603533500001 C1101.59387298,709.51808156 1103.833007,711.75841074 1106.59028622,711.76035209 C1106.59036086,711.76035214 1106.59043808,711.76035219 1106.59053213,711.76035269 C1106.59044892,711.7603532100001 1106.59155559,711.76035328 1106.59387298,711.7603533500001 C1106.59619037,711.76035328 1106.59729704,711.7603532100001 1106.59721383,711.76035269 C1106.59730788,711.76035219 1106.5973851,711.76035214 1106.59745974,711.76035209 C1109.35473896,711.75841074 1111.59387298,709.51808156 1111.59387298,706.7603533500001 C1111.59387298,704.00262514 1109.35473896,701.76229596 1106.59511805,701.76035405 C1106.59514113,701.76035346 1106.59471841,701.7603534 1106.59387298,701.7603533500001 L1106.59387298,701.7603533500001 M1133.86660025,706.30580789 C1133.8657548200001,706.30580794 1133.8653321,706.3058080000001 1133.86535518,706.30580859 C1131.10573427,706.3077505 1128.86660025,708.54807968 1128.86660025,711.30580789 C1128.86660025,714.0635361 1131.10573427,716.30386528 1133.86301349,716.30580663 C1133.86308813,716.30580668 1133.86316535,716.30580673 1133.8632594,716.30580723 C1133.86317619,716.30580775 1133.86428285,716.30580782 1133.86660025,716.30580789 C1133.86891765,716.30580782 1133.87002431,716.30580775 1133.8699411,716.30580723 C1133.87003515,716.30580673 1133.87011237,716.30580668 1133.87018701,716.30580663 C1136.62746623,716.30386528 1138.86660025,714.0635361 1138.86660025,711.30580789 C1138.86660025,708.54807968 1136.62746623,706.3077505 1133.86784532,706.30580859 C1133.8678684000001,706.3058080000001 1133.86744568,706.30580794 1133.86660025,706.30580789 L1133.86660025,706.30580789 M1161.13932752,710.85126244 C1161.13848209,710.8512624900001 1161.13805937,710.85126255 1161.13808245,710.85126314 C1158.37846154,710.85320505 1156.13932752,713.09353423 1156.13932752,715.85126244 C1156.13932752,718.60899065 1158.37846154,720.84931983 1161.13574076,720.85126118 C1161.1358154,720.85126123 1161.13589262,720.85126128 1161.13598667,720.85126178 C1161.13590346,720.8512623 1161.1370101300001,720.85126237 1161.13932752,720.85126244 C1161.14164491,720.85126237 1161.14275158,720.8512623 1161.1426683700001,720.85126178 C1161.14276242,720.85126128 1161.14283964,720.85126123 1161.14291428,720.85126118 C1163.9001935000001,720.84931983 1166.13932752,718.60899065 1166.13932752,715.85126244 C1166.13932752,713.09353423 1163.9001935000001,710.85320505 1161.14057259,710.85126314 C1161.14059567,710.85126255 1161.14017295,710.8512624900001 1161.13932752,710.85126244 L1161.13932752,710.85126244 M1188.4120548,715.3967169800001 C1185.6531013200001,715.39674657 1183.4120548,717.63781131 1183.4120548,720.3967169800001 C1183.4120548,723.15444519 1185.65118882,725.39477437 1188.4084680400001,725.39671572 C1188.40854268,725.39671577 1188.4086199,725.39671582 1188.40871395,725.39671632 C1188.40863074,725.3967168400001 1188.40973741,725.39671691 1188.4120548,725.3967169800001 C1188.41437219,725.39671691 1188.4154788600001,725.3967168400001 1188.41539565,725.39671632 C1188.4154897,725.39671582 1188.41556692,725.39671577 1188.41564156,725.39671572 C1191.17292077,725.39477437 1193.4120548,723.15444519 1193.4120548,720.3967169800001 C1193.4120548,717.6377931000001 1191.17097868,715.3967169800001 1188.4120548,715.3967169800001 L1188.4120548,715.3967169800001 M1027.95122347,717.83477228 C1026.09979908,720.55782852 1024.21007171,723.2905907 1022.28210635,726.02902595 C1023.01536647,726.45227289 1023.86574148,726.69477887 1024.7721044,726.69541702 C1024.77217904,726.69541707 1024.77225626,726.69541712 1024.77235031,726.6954176200001 C1024.7722671,726.69541814 1024.77337377,726.6954182100001 1024.77569116,726.69541828 C1024.77800855,726.6954182100001 1024.77911522,726.69541814 1024.77903201,726.6954176200001 C1024.77912606,726.69541712 1024.77800769,726.6954174900001 1024.77569116,726.69541828 C1027.53461505,726.69541828 1029.77569116,724.45434217 1029.77569116,721.69541828 C1029.77569116,720.14171288 1029.06494501,718.7522403300001 1027.95122347,717.83477228 L1027.95122347,717.83477228 M1215.68478207,719.94217153 C1215.68393664,719.94217158 1215.68351392,719.94217164 1215.6835370000001,719.94217223 C1212.92391609,719.94411414 1210.68478207,722.18444332 1210.68478207,724.94217153 C1210.68478207,727.69989974 1212.92391609,729.94022892 1215.68119531,729.94217027 C1215.6812699500001,729.9421703200001 1215.68134717,729.94217037 1215.6814412200001,729.94217087 C1215.68135801,729.94217139 1215.68246468,729.94217146 1215.68478207,729.94217153 C1215.6870994600001,729.94217146 1215.68820613,729.94217139 1215.68812292,729.94217087 C1215.68821697,729.94217037 1215.68829419,729.9421703200001 1215.68836883,729.94217027 C1218.44564805,729.94022892 1220.68478207,727.69989974 1220.68478207,724.94217153 C1220.68478207,722.18444332 1218.44564805,719.94411414 1215.68602714,719.94217223 C1215.68605022,719.94217164 1215.6856275,719.94217158 1215.68478207,719.94217153 L1215.68478207,719.94217153 M1052.04841843,721.2408728300001 C1052.047573,721.24087288 1052.04715028,721.24087294 1052.04717336,721.24087353 C1049.28755245,721.24281544 1047.04841843,723.48314462 1047.04841843,726.2408728300001 C1047.04841843,728.99860104 1049.28755245,731.23893022 1052.0448316700001,731.24087157 C1052.04490631,731.24087162 1052.04498353,731.24087167 1052.04507758,731.24087217 C1052.04499437,731.2408726900001 1052.04610104,731.24087276 1052.04841843,731.2408728300001 C1052.05073582,731.24087276 1052.0518424900001,731.2408726900001 1052.05175928,731.24087217 C1052.05185333,731.24087167 1052.05193055,731.24087162 1052.05200519,731.24087157 C1054.80928441,731.23893022 1057.04841843,728.99860104 1057.04841843,726.2408728300001 C1057.04841843,723.48314462 1054.80928441,721.24281544 1052.0496635,721.24087353 C1052.04968658,721.24087294 1052.04926386,721.24087288 1052.04841843,721.2408728300001 L1052.04841843,721.2408728300001 M1242.9575093400001,724.48762608 C1242.95666391,724.48762613 1242.9562411900001,724.48762619 1242.95626427,724.48762678 C1240.19664336,724.48956869 1237.9575093400001,726.7298978700001 1237.9575093400001,729.48762608 C1237.9575093400001,732.24653175 1240.19855586,734.48759649 1242.9575093400001,734.48762608 C1245.7164332300001,734.48762608 1247.9575093400001,732.24654996 1247.9575093400001,729.48762608 C1247.9575093400001,726.7298978700001 1245.71837532,724.48956869 1242.95875441,724.48762678 C1242.95877749,724.48762619 1242.95835477,724.48762613 1242.9575093400001,724.48762608 L1242.9575093400001,724.48762608 M1079.32114571,725.78632737 C1076.56219223,725.78635696 1074.32114571,728.0274217 1074.32114571,730.78632737 C1074.32114571,733.54405558 1076.56027973,735.78438476 1079.31755895,735.78632611 C1079.31763359,735.78632616 1079.31771081,735.78632621 1079.31780486,735.78632671 C1079.31772165,735.78632723 1079.31882832,735.7863273 1079.32114571,735.78632737 C1079.3234631,735.7863273 1079.32456977,735.78632723 1079.32448656,735.78632671 C1079.32458061,735.78632621 1079.32465783,735.78632616 1079.32473247,735.78632611 C1082.08201168,735.78438476 1084.32114571,733.54405558 1084.32114571,730.78632737 C1084.32114571,728.02740349 1082.08006959,725.78632737 1079.32114571,725.78632737 L1079.32114571,725.78632737 M1270.23023661,729.03308062 C1270.22939118,729.03308067 1270.22896846,729.03308073 1270.22899154,729.0330813200001 C1267.46937064,729.03502323 1265.23023661,731.27535241 1265.23023661,734.03308062 C1265.23023661,736.7908088300001 1267.46937064,739.0311380100001 1270.22664985,739.03307936 C1270.22672449,739.03307941 1270.22680171,739.0330794600001 1270.22689576,739.03307996 C1270.22681255,739.03308048 1270.22791922,739.03308055 1270.23023661,739.03308062 C1270.232554,739.03308055 1270.23366067,739.03308048 1270.23357746,739.03307996 C1270.23367151,739.0330794600001 1270.2337487300001,739.03307941 1270.23382337,739.03307936 C1272.99110259,739.0311380100001 1275.23023661,736.7908088300001 1275.23023661,734.03308062 C1275.23023661,731.27535241 1272.99110259,729.03502323 1270.2314816800001,729.0330813200001 C1270.23150476,729.03308073 1270.23108204,729.03308067 1270.23023661,729.03308062 L1270.23023661,729.03308062 M1106.59387298,730.33178192 C1106.59302755,730.3317819700001 1106.59260483,730.33178203 1106.59262791,730.33178262 C1103.833007,730.33372453 1101.59387298,732.57405371 1101.59387298,735.33178192 C1101.59387298,738.08951013 1103.833007,740.32983931 1106.59028622,740.33178066 C1106.59036086,740.33178071 1106.59043808,740.33178076 1106.59053213,740.3317812600001 C1106.59044892,740.33178178 1106.59155559,740.33178185 1106.59387298,740.33178192 C1106.59619037,740.33178185 1106.59729704,740.33178178 1106.59721383,740.3317812600001 C1106.59730788,740.33178076 1106.5973851,740.33178071 1106.59745974,740.33178066 C1109.35473896,740.32983931 1111.59387298,738.08951013 1111.59387298,735.33178192 C1111.59387298,732.57405371 1109.35473896,730.33372453 1106.59511805,730.33178262 C1106.59514113,730.33178203 1106.59471841,730.3317819700001 1106.59387298,730.33178192 L1106.59387298,730.33178192 M1297.50296389,733.57853517 C1297.50211846,733.57853522 1297.50169574,733.57853528 1297.50171882,733.57853587 C1294.74209791,733.58047778 1292.50296389,735.82080696 1292.50296389,738.57853517 C1292.50296389,741.33626338 1294.74209791,743.57659256 1297.49937713,743.57853391 C1297.49945177,743.57853396 1297.49952899,743.57853401 1297.49962304,743.57853451 C1297.49953983,743.57853503 1297.5006465000001,743.5785351000001 1297.50296389,743.57853517 C1297.50528128,743.5785351000001 1297.50638795,743.57853503 1297.5063047400001,743.57853451 C1297.50639879,743.57853401 1297.50647601,743.57853396 1297.50655065,743.57853391 C1300.26382987,743.57659256 1302.50296389,741.33626338 1302.50296389,738.57853517 C1302.50296389,735.82080696 1300.26382987,733.58047778 1297.50420896,733.57853587 C1297.50423204,733.57853528 1297.50380932,733.57853522 1297.50296389,733.57853517 L1297.50296389,733.57853517 M1133.86660025,734.8772364600001 C1131.10764677,734.87726605 1128.86660025,737.11833079 1128.86660025,739.8772364600001 C1128.86660025,742.63496467 1131.10573427,744.87529385 1133.86301349,744.8772352 C1133.86308813,744.87723525 1133.86316535,744.8772353 1133.8632594,744.8772358 C1133.86317619,744.8772363200001 1133.86428285,744.87723639 1133.86660025,744.8772364600001 C1133.86891765,744.87723639 1133.87002431,744.8772363200001 1133.8699411,744.8772358 C1133.87003515,744.8772353 1133.87011237,744.87723525 1133.87018701,744.8772352 C1136.62746623,744.87529385 1138.86660025,742.63496467 1138.86660025,739.8772364600001 C1138.86660025,737.1183125800001 1136.62552414,734.8772364600001 1133.86660025,734.8772364600001 L1133.86660025,734.8772364600001 M1161.13932752,739.42269101 C1161.13848209,739.42269106 1161.13805937,739.42269112 1161.13808245,739.42269171 C1158.37846154,739.42463362 1156.13932752,741.6649628 1156.13932752,744.42269101 C1156.13932752,747.18041922 1158.37846154,749.4207484 1161.13574076,749.42268975 C1161.1358154,749.4226898000001 1161.13589262,749.42268985 1161.13598667,749.42269035 C1161.13590346,749.42269087 1161.1370101300001,749.42269094 1161.13932752,749.42269101 C1161.14164491,749.42269094 1161.14275158,749.42269087 1161.1426683700001,749.42269035 C1161.14276242,749.42268985 1161.14283964,749.4226898000001 1161.14291428,749.42268975 C1163.9001935000001,749.4207484 1166.13932752,747.18041922 1166.13932752,744.42269101 C1166.13932752,741.6649628 1163.9001935000001,739.42463362 1161.14057259,739.42269171 C1161.14059567,739.42269112 1161.14017295,739.42269106 1161.13932752,739.42269101 L1161.13932752,739.42269101 M1188.4120548,743.96814556 C1188.41120937,743.96814561 1188.41078665,743.96814567 1188.41080973,743.96814626 C1185.65118882,743.97008817 1183.4120548,746.2104173500001 1183.4120548,748.96814556 C1183.4120548,751.72705122 1185.65310131,753.96811596 1188.4120548,753.96814556 C1191.17097868,753.96814556 1193.4120548,751.72706944 1193.4120548,748.96814556 C1193.4120548,746.2104173500001 1191.17292077,743.97008817 1188.41329987,743.96814626 C1188.41332295,743.96814567 1188.41290023,743.96814561 1188.4120548,743.96814556 L1188.4120548,743.96814556 M1024.77569116,745.26684685 C1022.01673768,745.26687644 1019.7756911600001,747.50794118 1019.7756911600001,750.26684685 C1019.7756911600001,753.02457506 1022.01482518,755.26490424 1024.7721044,755.26684559 C1024.77217904,755.26684564 1024.77225626,755.26684569 1024.77235031,755.26684619 C1024.7722671,755.26684671 1024.77337377,755.26684678 1024.77569116,755.26684685 C1024.77800855,755.26684678 1024.77911522,755.26684671 1024.77903201,755.26684619 C1024.77912606,755.26684569 1024.77920328,755.26684564 1024.77927792,755.26684559 C1027.53655714,755.26490424 1029.77569116,753.02457506 1029.77569116,750.26684685 C1029.77569116,747.50792297 1027.53461505,745.26684685 1024.77569116,745.26684685 L1024.77569116,745.26684685 M1215.68478207,748.5136001 C1215.68393664,748.51360015 1215.68351392,748.51360021 1215.6835370000001,748.5136008000001 C1212.92391609,748.51554271 1210.68478207,750.75587189 1210.68478207,753.5136001 C1210.68478207,756.2713283100001 1212.92391609,758.5116574900001 1215.68119531,758.51359884 C1215.6812699500001,758.51359889 1215.68134717,758.5135989400001 1215.6814412200001,758.51359944 C1215.68135801,758.51359996 1215.68246468,758.51360003 1215.68478207,758.5136001 C1215.6870994600001,758.51360003 1215.68820613,758.51359996 1215.68812292,758.51359944 C1215.68821697,758.5135989400001 1215.68829419,758.51359889 1215.68836883,758.51359884 C1218.44564805,758.5116574900001 1220.68478207,756.2713283100001 1220.68478207,753.5136001 C1220.68478207,750.75587189 1218.44564805,748.51554271 1215.68602714,748.5136008000001 C1215.68605022,748.51360021 1215.6856275,748.51360015 1215.68478207,748.5136001 L1215.68478207,748.5136001 M1052.04841843,749.8123014 C1052.047573,749.8123014500001 1052.04715028,749.81230151 1052.04717336,749.8123021 C1049.28755245,749.81424401 1047.04841843,752.05457319 1047.04841843,754.8123014 C1047.04841843,757.57002961 1049.28755245,759.81035879 1052.0448316700001,759.81230014 C1052.04490631,759.81230019 1052.04498353,759.81230024 1052.04507758,759.8123007400001 C1052.04499437,759.81230126 1052.04610104,759.81230133 1052.04841843,759.8123014 C1052.05073582,759.81230133 1052.0518424900001,759.81230126 1052.05175928,759.8123007400001 C1052.05185333,759.81230024 1052.05193055,759.81230019 1052.05200519,759.81230014 C1054.80928441,759.81035879 1057.04841843,757.57002961 1057.04841843,754.8123014 C1057.04841843,752.05457319 1054.80928441,749.81424401 1052.0496635,749.8123021 C1052.04968658,749.81230151 1052.04926386,749.8123014500001 1052.04841843,749.8123014 L1052.04841843,749.8123014 M1242.9575093400001,753.05905465 C1242.95666391,753.0590547 1242.9562411900001,753.05905476 1242.95626427,753.05905535 C1240.19664336,753.06099726 1237.9575093400001,755.30132644 1237.9575093400001,758.05905465 C1237.9575093400001,760.81678286 1240.19664336,763.05711204 1242.95392258,763.05905339 C1242.95399722,763.0590534400001 1242.95407444,763.05905349 1242.95416849,763.05905399 C1242.95408528,763.05905451 1242.95519195,763.0590545800001 1242.9575093400001,763.05905465 C1242.95982673,763.0590545800001 1242.9609334,763.05905451 1242.96085019,763.05905399 C1242.9609442400001,763.05905349 1242.96102146,763.0590534400001 1242.9610961,763.05905339 C1245.71837532,763.05711204 1247.9575093400001,760.81678286 1247.9575093400001,758.05905465 C1247.9575093400001,755.30132644 1245.71837532,753.06099726 1242.95875441,753.05905535 C1242.95877749,753.05905476 1242.95835477,753.0590547 1242.9575093400001,753.05905465 L1242.9575093400001,753.05905465 M1079.32114571,754.3577559500001 C1079.32030028,754.357756 1079.3198775600001,754.35775606 1079.31990064,754.35775665 C1076.56027973,754.35969856 1074.32114571,756.60002774 1074.32114571,759.3577559500001 C1074.32114571,762.11548415 1076.56027973,764.35581334 1079.31755895,764.35775469 C1079.31763359,764.35775474 1079.31771081,764.3577547900001 1079.31780486,764.35775529 C1079.31772165,764.3577558100001 1079.31882832,764.35775588 1079.32114571,764.3577559500001 C1079.3234631,764.35775588 1079.32456977,764.3577558100001 1079.32448656,764.35775529 C1079.32458061,764.3577547900001 1079.32465783,764.35775474 1079.32473247,764.35775469 C1082.08201168,764.35581334 1084.32114571,762.11548415 1084.32114571,759.3577559500001 C1084.32114571,756.60002774 1082.08201168,754.35969856 1079.32239078,754.35775665 C1079.32241386,754.35775606 1079.32199114,754.357756 1079.32114571,754.3577559500001 L1079.32114571,754.3577559500001 M1270.23023661,757.60450919 C1270.22939118,757.60450924 1270.22896846,757.6045093 1270.22899154,757.60450989 C1267.46937064,757.6064518000001 1265.23023661,759.8467809800001 1265.23023661,762.60450919 C1265.23023661,765.3622374 1267.46937064,767.60256658 1270.22664985,767.6045079300001 C1270.22672449,767.60450798 1270.22680171,767.60450803 1270.22689576,767.60450853 C1270.22681255,767.60450905 1270.22791922,767.60450912 1270.23023661,767.60450919 C1270.232554,767.60450912 1270.23366067,767.60450905 1270.23357746,767.60450853 C1270.23367151,767.60450803 1270.2337487300001,767.60450798 1270.23382337,767.6045079300001 C1272.99110259,767.60256658 1275.23023661,765.3622374 1275.23023661,762.60450919 C1275.23023661,759.8467809800001 1272.99110259,757.6064518000001 1270.2314816800001,757.60450989 C1270.23150476,757.6045093 1270.23108204,757.60450924 1270.23023661,757.60450919 L1270.23023661,757.60450919 M1106.59387298,758.90321049 C1106.59302755,758.90321054 1106.59260483,758.9032106 1106.59262791,758.90321119 C1103.833007,758.9051531 1101.59387298,761.14548228 1101.59387298,763.90321049 C1101.59387298,766.6609387 1103.833007,768.90126788 1106.59028622,768.90320923 C1106.59036086,768.90320928 1106.59043808,768.90320933 1106.59053213,768.90320983 C1106.59044892,768.90321035 1106.59155559,768.90321042 1106.59387298,768.90321049 C1106.59619037,768.90321042 1106.59729704,768.90321035 1106.59721383,768.90320983 C1106.59730788,768.90320933 1106.5973851,768.90320928 1106.59745974,768.90320923 C1109.35473896,768.90126788 1111.59387298,766.6609387 1111.59387298,763.90321049 C1111.59387298,761.14548228 1109.35473896,758.9051531 1106.59511805,758.90321119 C1106.59514113,758.9032106 1106.59471841,758.90321054 1106.59387298,758.90321049 L1106.59387298,758.90321049 M1297.50296389,762.14996374 C1297.50211846,762.14996379 1297.50169574,762.1499638500001 1297.50171882,762.14996444 C1294.74209791,762.15190635 1292.50296389,764.39223553 1292.50296389,767.14996374 C1292.50296389,769.9076919500001 1294.74209791,772.14802113 1297.49937713,772.14996248 C1297.49945177,772.14996253 1297.49952899,772.14996258 1297.49962304,772.14996308 C1297.49953983,772.1499636 1297.5006465000001,772.14996367 1297.50296389,772.14996374 C1297.50528128,772.14996367 1297.50638795,772.1499636 1297.5063047400001,772.14996308 C1297.50639879,772.14996258 1297.50647601,772.14996253 1297.50655065,772.14996248 C1300.26382987,772.14802113 1302.50296389,769.9076919500001 1302.50296389,767.14996374 C1302.50296389,764.39223553 1300.26382987,762.15190635 1297.50420896,762.14996444 C1297.50423204,762.1499638500001 1297.50380932,762.14996379 1297.50296389,762.14996374 L1297.50296389,762.14996374 M1133.86660025,763.44866504 C1133.8657548200001,763.44866509 1133.8653321,763.44866515 1133.86535518,763.44866574 C1131.10573427,763.45060765 1128.86660025,765.69093683 1128.86660025,768.44866504 C1128.86660025,771.20639325 1131.10573427,773.44672243 1133.86301349,773.4486637800001 C1133.86308813,773.44866383 1133.86316535,773.44866388 1133.8632594,773.44866438 C1133.86317619,773.4486649 1133.86428285,773.44866497 1133.86660025,773.44866504 C1133.86891765,773.44866497 1133.87002431,773.4486649 1133.8699411,773.44866438 C1133.87003515,773.44866388 1133.87011237,773.44866383 1133.87018701,773.4486637800001 C1136.62746623,773.44672243 1138.86660025,771.20639325 1138.86660025,768.44866504 C1138.86660025,765.69093683 1136.62746623,763.45060765 1133.86784532,763.44866574 C1133.8678684000001,763.44866515 1133.86744568,763.44866509 1133.86660025,763.44866504 L1133.86660025,763.44866504 M1161.13932752,767.99411958 C1161.13848209,767.99411963 1161.13805937,767.99411969 1161.13808245,767.9941202800001 C1158.37846154,767.99606219 1156.13932752,770.23639137 1156.13932752,772.99411958 C1156.13932752,775.75184779 1158.37846154,777.9921769700001 1161.13574076,777.99411832 C1161.1358154,777.99411837 1161.13589262,777.9941184200001 1161.13598667,777.99411892 C1161.13590346,777.99411944 1161.1370101300001,777.99411951 1161.13932752,777.99411958 C1161.14164491,777.99411951 1161.14275158,777.99411944 1161.1426683700001,777.99411892 C1161.14276242,777.9941184200001 1161.14283964,777.99411837 1161.14291428,777.99411832 C1163.9001935000001,777.9921769700001 1166.13932752,775.75184779 1166.13932752,772.99411958 C1166.13932752,770.23639137 1163.9001935000001,767.99606219 1161.14057259,767.9941202800001 C1161.14059567,767.99411969 1161.14017295,767.99411963 1161.13932752,767.99411958 L1161.13932752,767.99411958 M1188.4120548,772.53957413 C1188.41120937,772.53957418 1188.41078665,772.53957424 1188.41080973,772.53957483 C1185.65118882,772.54151674 1183.4120548,774.78184592 1183.4120548,777.53957413 C1183.4120548,780.29730234 1185.65118882,782.53763152 1188.4084680400001,782.53957287 C1188.40854268,782.5395729200001 1188.4086199,782.53957297 1188.40871395,782.53957347 C1188.40863074,782.53957399 1188.4097374,782.5395740600001 1188.4120548,782.53957413 C1188.41437219,782.5395740600001 1188.4154788600001,782.53957399 1188.41539565,782.53957347 C1188.4154897,782.53957297 1188.41556692,782.5395729200001 1188.41564156,782.53957287 C1191.17292077,782.53763152 1193.4120548,780.29730234 1193.4120548,777.53957413 C1193.4120548,774.78184592 1191.17292077,772.54151674 1188.41329987,772.53957483 C1188.41332295,772.53957424 1188.41290023,772.53957418 1188.4120548,772.53957413 L1188.4120548,772.53957413 M1215.68478207,777.08502867 C1215.68393664,777.08502872 1215.68351392,777.08502878 1215.6835370000001,777.08502937 C1212.92391609,777.0869712800001 1210.68478207,779.3273004600001 1210.68478207,782.08502867 C1210.68478207,783.92257613 1211.67893752,785.53040307 1213.15810637,786.39936741 C1214.00033327,786.39936802 1214.84255809,786.39936808 1215.68478205,786.39936814 C1214.84255809,786.3993682 1215.68478322,786.39936827 1218.21145673,786.39936814 C1219.69062586,785.5304043 1220.68478207,783.92257684 1220.68478207,782.08502867 C1220.68478207,779.3273004600001 1218.44564805,777.0869712800001 1215.68602714,777.08502937 C1215.68605022,777.08502878 1215.6856275,777.08502872 1215.68478207,777.08502867 L1215.68478207,777.08502867 M1242.9575093400001,781.63048322 C1242.95666391,781.63048327 1242.9562411900001,781.6304833300001 1242.95626427,781.63048392 C1240.2740898,781.6323713 1238.08370368,783.74866826 1237.96277078,786.3993692500001 C1238.52680157,786.3993698300001 1239.09082811,786.39936987 1239.65485039,786.39936991 C1239.09083388,786.39936999 1239.3023410600001,786.39937006 1240.2893719400001,786.39937013 C1239.30233731,786.39937019 1239.17895793,786.39937024 1239.9192337900001,786.3993703 C1239.17896395,786.39937038 1239.36403257,786.39937046 1240.4744396400001,786.39937067 C1248.54321677,786.39937084 1252.93693024,786.39937089 1253.65558005,786.39937095 C1252.9369201900001,786.39937099 1252.89200431,786.39937104 1253.5208324,786.39937109 C1252.89207203,786.39937117 1253.1278732,786.3993712500001 1254.22823591,786.39937133 C1253.12782921,786.39937139 1252.99028047,786.39937145 1253.8155897,786.39937151 C1252.99025572,786.39937156 1252.88709056,786.39937161 1253.5060942,786.39937165 C1252.88713033,786.39937172 1253.04187701,786.39937179 1253.97033424,786.39937186 C1253.04187701,786.39937193 1251.03584862,786.39937199 1247.9522481000001,786.3993705 C1251.03584862,786.39937199 1253.04187701,786.39937193 1253.97033424,786.39937186 C1253.04187701,786.39937179 1252.88713033,786.39937172 1253.5060942,786.39937165 C1252.88709056,786.39937161 1252.99025572,786.39937156 1253.8155897,786.39937151 C1252.99028047,786.39937145 1253.12782921,786.39937139 1254.22823591,786.39937133 C1253.1278732,786.3993712500001 1252.89207203,786.39937117 1253.5208324,786.39937109 C1252.89200431,786.39937104 1252.9369201900001,786.39937099 1253.65558005,786.39937095 C1252.93693024,786.39937089 1251.03581954,786.39937084 1247.9522481000001,786.3993705 C1247.83131555,783.74866879 1245.64092922,781.6323713 1242.95875441,781.63048392 C1242.95877749,781.6304833300001 1242.95835477,781.63048327 1242.9575093400001,781.63048322 L1242.9575093400001,781.63048322 M1270.23023661,786.17593776 C1270.22939118,786.17593781 1270.22896846,786.17593787 1270.22899154,786.17593846 C1269.71248562,786.17630104 1269.21633886,786.25449852 1268.74936967,786.39937206 C1269.73661464,786.39937214 1270.7238596700001,786.39937221 1271.71110486,786.39937217 C1271.24413436,786.25449852 1270.7479876,786.17630104 1270.2314816800001,786.17593846 C1270.23150476,786.17593787 1270.23108204,786.17593781 1270.23023661,786.17593776 L1270.23023661,786.17593776 +M982.09012289,761.47344008 C1050.01995756,682.61514836 1084.81059131,590.7547421 1084.81059131,590.7547421 L1088.3841728300001,581.3191379 L1097.78750634,584.97679156 C1182.12559628,617.78212837 1302.34017279,674.88519249 1327.63040624,765.3064381 L1329.21958018,770.98827917 L1325.01476954,775.12692137 L1306.32122684,793.52629569 L1303.40221325,796.39937462 L1299.30645656,796.39937432 L1011.11108325,796.39935303 L1007.4090286,796.39935275 L1004.59941225,793.9886963 L983.15499493,775.58934327 L975.55073627,769.0648741 L982.09012289,761.47344008 L982.09012289,761.47344008 M997.24321044,774.5265599200001 L996.1783384,760.41065673 L1017.62275572,778.8100097600001 L1011.11108472,776.39935303 L1299.30645803,776.39937432 L1292.29168775,779.27245295 L1310.9852304600001,760.87307863 L1308.36959376,770.6935619 C1284.90795714,686.81017616 1163.34425414,631.9364098 1090.53720069,603.61634809 L1103.51411572,597.83839755 C1100.32064474,606.27036646 1064.3848734,696.58323987 997.24321044,774.5265599200001 L997.24321044,774.5265599200001 diff --git a/vendor/linesweeper/tests/regression/graphite_5.txt b/vendor/linesweeper/tests/regression/graphite_5.txt new file mode 100644 index 00000000..0e0b4954 --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_5.txt @@ -0,0 +1,2 @@ +M0,340 C161.73791371000002,383.57576471 107.56418217,490.73058652000003 273,476 C419,463 481.74119799,514.69227252 481.33333333,768 C481.33333333,768 -0,768 -0,768 C-0,768 0,340 0,340 L0,340 +M0.00000118,340.00000063 C0,340.01956513 0,565.33734898 0.00000305,689.0000006 C36.95467457,696.25895726 71.52028852000001,733.27936387 114.1739269,733.28721767 C114.17421003,733.2872177300001 114.17450212,733.28721778 114.17479023,733.28721818 C114.17447732000001,733.28721805 114.17457669000001,733.28721813 114.17504492,733.28721821 C114.17458128,733.28721827 114.17452308,733.28721833 114.17479023,733.28721818 C114.17447829,733.2872185900001 114.17454326000001,733.28721865 114.17493557,733.28721872 C114.17454326000001,733.28721879 114.1787321,733.28721886 114.18750207000001,733.28721892 C114.19627217,733.28721886 114.20046062,733.28721879 114.20010011000001,733.28721855 C114.20046062,733.28721865 114.20052571000001,733.2872185900001 114.20023978,733.28721811 C114.20052134000001,733.28721847 114.20047804000001,733.28721843 114.20010011000001,733.28721855 C114.20048076,733.28721833 114.20042242,733.28721827 114.19995778,733.28721821 C114.20042726,733.28721813 114.20052685,733.28721805 114.20023978,733.28721811 C114.20050169,733.28721778 114.20079386,733.2872177300001 114.20107659,733.28721767 C136.17620277,733.2831713100001 160.2983806,723.4526911 188,694.66666667 C280.96421132,598.06328693 341.25735896000003,565.3752631 404.56938754000004,565.36776692 C404.56967787,565.36776666 404.56967196,565.36776662 404.56930161,565.36776624 C404.56893409,565.36776651 404.56909019,565.36776645 404.56977678,565.36776639 C404.56777842,565.36776631 404.56762556,565.36776623 404.56930161,565.36776624 C404.56933179000004,565.36776595 404.57194292,565.3677659 404.57711156,565.3677658400001 C404.58223904,565.3677659 404.58480747,565.36776595 404.58478914,565.36776636 C404.5865175,565.36776623 404.58635002,565.36776631 404.58428263,565.36776639 C404.58503745,565.36776645 404.58520212,565.36776651 404.58478914,565.36776636 C404.58565083,565.36776678 404.58561061,565.3677668500001 404.58465731,565.36776702 C422.0448013,565.36982731 439.71964088,567.85379516 458.37025448,572.16574812 C432.70315584,498.6798599 384.8001224,474.16753872 311.04913551,474.16125007 C311.05037295,474.16124990000003 311.05042416000003,474.16124984 311.04930474,474.16124944 C311.04983879,474.16124959 311.04962484,474.16124953 311.04864823,474.16124947000003 C311.05133979,474.16124939 311.05155656,474.16124932 311.04930474,474.16124944 C311.04932269,474.16124906 311.0459606,474.16124901 311.03925496,474.16124896 C311.03256688,474.16124901 311.02922253,474.16124906 311.02925759,474.16124944 C311.02698744,474.16124932 311.02721009,474.16124939 311.02992915,474.16124947000003 C311.02892511,474.16124953 311.02870773,474.16124959 311.02925759,474.16124944 C311.02811977,474.16124984 311.02817483,474.16124990000003 311.02943543,474.16125007 C299.02774707000003,474.16227143000003 286.36259278,474.81018009 273,476 C262.50555953,476.93443648 252.89483097000002,477.37840436 244.05664154000002,477.37924601000003 C244.0561175,477.37924606 244.0555754,477.37924611 244.05501524000002,477.37924616000004 C244.05557329,477.3792462 244.05560823000002,477.37924625 244.05512578,477.37924656 C244.05562249000002,477.37924637000003 244.05543748,477.37924644000003 244.05456503,477.37924652000004 C244.05542822,477.37924658000003 244.05553661000002,477.37924663 244.05489021,477.37924669 C244.0555314,477.37924673000003 244.05561182,477.37924678 244.05512578,477.37924656 C244.0556202,477.37924689 244.05549928,477.37924695000004 244.05476873,477.37924701000003 C244.05549928,477.37924708 244.04773046,477.37924714 244.03146226,477.37924721 C244.0152888,477.37924714 244.00743904,477.37924708 244.00787253000001,477.37924685 C244.00743904,477.37924695000004 244.00735431,477.37924689 244.00769022,477.37924642 C244.00729702,477.37924678 244.00735477,477.37924673000003 244.00787253000001,477.37924685 C244.00739042,477.37924663 244.0074595,477.37924658000003 244.00803933,477.37924652000004 C244.00752289000002,477.37924644000003 244.00739892,477.37924637000003 244.00769022,477.37924642 C244.00734149000002,477.37924611 244.01524745,477.37924646 244.03146226,477.37924721 C113.5732624,477.37924721 151.46830545,380.80890773 0.00000118,340.00000063 L0.00000118,340.00000063 diff --git a/vendor/linesweeper/tests/regression/graphite_6.txt b/vendor/linesweeper/tests/regression/graphite_6.txt new file mode 100644 index 00000000..71562552 --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_6.txt @@ -0,0 +1,2 @@ +M-309.57295624,-270.00777117 C-309.57295624,-270.00777117 -311.41340739000003,-216.28078669 -311.41340739000003,-216.28078669 C-311.41340739000003,-216.28078669 -351.51886288000003,-252.07923027 -351.51886288000003,-252.07923027 C-351.51886288000003,-252.07923027 -329.86578716,-202.87434805 -329.86578716,-202.87434805 C-329.86578716,-202.87434805 -381.53191653,-217.72652625 -381.53191653,-217.72652625 C-381.53191653,-217.72652625 -340.67397113,-182.78937667 -340.67397113,-182.78937667 C-340.67397113,-182.78937667 -393.66766383,-173.75363345 -393.66766383,-173.75363345 C-393.66766383,-173.75363345 -341.69727067,-160.00394875 -341.69726593,-160.00394749 C-341.69726593,-160.00394749 -341.69726593,-160.00394749 -341.69726593,-160.00394749 C-341.69726746000003,-160.0039464 -385.52247121,-128.8699226 -385.52247121,-128.8699226 C-385.52247121,-128.8699226 -358.81363105,-134.0109177 -343.39203698,-136.97931091 C-343.97299745,-132.31826018 -345.06350128,-123.56913893000001 -346.17106274,-114.68316414 C-350.49807163,-106.84325123 -354.75843994,-99.12408138 -357.02812992,-95.01173213 C-367.75968386,-106.47779825 -386.34578834,-126.33601300000001 -386.34578834,-126.33601300000001 C-386.34578834,-126.33601300000001 -386.34578834,-126.33601300000001 -386.34578834,-126.33601300000001 C-386.34578834,-126.33601300000001 -369.19057317,-75.38824843 -369.19057317,-75.38824843 C-369.19057317,-75.38824843 -419.31737091,-94.81195225 -419.31737091,-94.81195225 C-419.31737091,-94.81195225 -381.75564952,-56.35297139 -381.75564952,-56.35297139 C-381.75564952,-56.35297139 -435.34596299000003,-52.103921400000004 -435.34596299000003,-52.103921400000004 C-435.34596299000003,-52.103921400000004 -384.81729498,-33.75099708 -384.81729498,-33.75099708 C-384.81729498,-33.75099708 -431.25690533,-6.67076973 -431.25690533,-6.67076973 C-431.25690533,-6.67076973 -377.76911308,-12.05892369 -377.76911308,-12.05892369 C-377.76911308,-12.05892369 -407.85264390000003,32.47789237 -407.86008457,32.48890782 C-407.86008503,32.4889085 -407.86008549,32.48890918 -407.86008595,32.48890986 C-407.86008484,32.48890918 -407.86008372000003,32.4889085 -407.86008261,32.48890782 C-407.84579985,32.480166770000004 -362.02000608000003,4.4347767000000005 -362.00708544,4.42686927 C-362.00708453,4.42686871 -362.00708362,4.42686816 -362.00708271,4.4268676000000005 C-362.00708279,4.42686816 -362.00708287000003,4.42686871 -362.00708295,4.42686927 C-362.00867591,4.43775696 -369.78739577,57.60446257 -369.78953143,57.619059570000005 C-369.78953158,57.61906058 -369.78953172,57.61906158 -369.78953187,57.61906258 C-369.78953122,57.61906158 -369.78953058,57.61906058 -369.78952993,57.619059570000005 C-369.7808544,57.60560762 -340.66022242,12.45225812 -340.6530686,12.44116567 C-340.65306816000003,12.44116499 -340.65306772,12.44116431 -340.65306728,12.44116363 C-340.65306707,12.44116431 -340.65306685,12.44116499 -340.65306664,12.44116567 C-340.64936448000003,12.45298614 -324.59027851,63.727362150000005 -324.58558427,63.74235018 C-324.58558393,63.74235128 -324.58558358,63.74235237 -324.58558324,63.742353470000005 C-324.5855831,63.74235237 -324.58558297,63.74235128 -324.58558283,63.74235018 C-324.58422772,63.73147814 -321.22108328,36.74894232 -319.27904968,21.16799066 C-315.02565368,23.160860800000002 -307.04172911,26.901618720000002 -298.9329202,30.70088949 C-294.32976908,35.619099410000004 -289.7799484,40.480328730000004 -286.44175296000003,44.04700415 C-286.44175296000003,44.04700415 -286.44175296000003,44.04700415 -286.44175296000003,44.04700415 C-290.93120416,42.75644629 -293.88219469,41.90814127 -293.88662067,41.90686895 C-293.88662234000003,41.90686848 -293.886624,41.906868 -293.88662566,41.90686752 C-293.8866251,41.906868 -293.88662454,41.90686848 -293.88662398,41.90686895 C-293.88399641,41.90911576 -290.79214413,44.55292235 -286.31055615,48.385075640000004 C-286.31055615,48.385075640000004 -286.31055615,48.385075640000004 -286.31055615,48.385075640000004 C-300.73158259,55.14184901 -322.42221072,65.30469416 -322.43010884,65.30839472 C-322.43010956,65.30839506 -322.43011028,65.30839539 -322.430111,65.30839573 C-322.43007932,65.30839539 -322.43004765,65.30839506 -322.43001597,65.30839472 C-322.34765695,65.30751858 -268.75710222,64.73741871 -268.67474949,64.73654264 C-268.67471782,64.7365423 -268.67468615,64.73654197 -268.67465448,64.73654163 C-268.67465475,64.73654197 -268.67465503,64.7365423 -268.6746553,64.73654264 C-268.6773476,64.73984596 -275.27893096,72.83969197 -282.66038186000003,81.89640037 C-294.78545077,83.96379752 -306.02237297,85.87976032 -306.02237297,85.87976032 C-306.02237297,85.87976032 -298.65872262,87.82794398 -289.47501971,90.25765466 C-289.47501971,90.25765466 -289.47501971,90.25765466 -289.47501971,90.25765466 C-296.51657572,98.89732731000001 -302.63423901,106.40342515 -302.63772546,106.40770286 C-302.63772596,106.40770347 -302.63772645,106.40770408 -302.63772695,106.40770469 C-302.63772572,106.40770408 -302.63772449,106.40770347 -302.63772325,106.40770286 C-302.62964402,106.40370568 -289.38252815,99.84973105 -276.78221479,93.61576021 C-269.47735281,95.54839036 -262.52113201000003,97.38878134000001 -258.27188161,98.51299553 C-258.65549971,100.11498895 -259.05362597,101.77756854 -259.4590738,103.47072313 C-259.4590738,103.47072313 -259.4590738,103.47072313 -259.4590738,103.47072313 C-271.17725811,111.79548145 -297.86921317,130.75781119 -297.87717855,130.7634699 C-297.87717914,130.76347033 -297.87717974000003,130.76347075 -297.87718033,130.76347117 C-297.87717814,130.76347075 -297.87717594000003,130.76347033 -297.87717375,130.7634699 C-297.86448743,130.761028 -279.51326148,127.22873074 -264.45412318,124.33010408 C-265.9331108,130.50637258 -266.97211069,134.84524749 -266.97308258,134.84930611000001 C-266.97308281,134.84930706 -266.97308303,134.84930801000002 -266.97308326,134.84930896 C-266.97308252,134.84930801000002 -266.97308178000003,134.84930706 -266.97308104,134.84930611000001 C-266.96964042,134.84489628 -262.97348682,129.72304276 -257.76060733,123.04171672 C-257.07105556,122.90898979 -256.39881106,122.77959422 -255.7467461,122.65408286 C-256.32770658,127.31513359 -257.41821041000003,136.06425484 -258.52577187,144.95022963 C-262.85278076000003,152.79014254 -267.11314907,160.50931238 -269.38283905000003,164.62166164 C-280.11211964,153.15802447000002 -298.69262336,133.30579382 -298.70049497,133.29738344 C-298.7004958,133.29738255 -298.70049664,133.29738166 -298.70049747,133.29738077 C-298.70049717,133.29738166 -298.70049687,133.29738255 -298.70049657,133.29738344 C-298.69596053,133.31085462000001 -281.54861877,184.23523662 -281.54528278,184.24514389 C-281.54528261,184.24514438 -281.54528245,184.24514486 -281.54528229,184.24514534 C-281.54528353,184.24514486 -281.54528478000003,184.24514438 -281.54528602,184.24514389 C-281.56107074,184.23902745 -331.65534526,164.82792609 -331.67207585,164.82144313 C-331.67207725000003,164.82144259 -331.67207864,164.82144205 -331.67208004,164.82144151 C-331.67207951,164.82144205 -331.67207898,164.82144259 -331.67207845,164.82144313 C-331.66316682,164.83056765 -294.11035866000003,203.28042238 -294.11035866000003,203.28042238 C-294.11035866000003,203.28042238 -347.70067212000004,207.52947237 -347.70067212000004,207.52947237 C-347.70067212000004,207.52947237 -297.17200411,225.88239669 -297.17200411,225.88239669 C-297.17200411,225.88239669 -343.60070047,252.95625978 -343.61161254,252.96262291 C-343.61161318,252.96262328 -343.61161382,252.96262366 -343.61161446,252.96262403 C-343.61161075,252.96262366 -343.61160704,252.96262328 -343.61160332000003,252.96262291 C-343.58344155000003,252.959786 -290.15182267,247.57729074 -290.12383321,247.57447119 C-290.12382954000003,247.57447082000002 -290.12382588,247.57447045 -290.12382221,247.57447008 C-290.12382246,247.57447045 -290.12382271,247.57447082000002 -290.12382296,247.57447119 C-290.12930153,247.58258187 -320.20735388,292.11128739000003 -320.21479370000003,292.12230158 C-320.21479416,292.12230226 -320.21479462,292.12230294 -320.21479508,292.12230362 C-320.21479397,292.12230294 -320.21479285,292.12230226 -320.21479174,292.12230158 C-320.2005093,292.11356072 -274.37471497,264.06817033 -274.36179456,264.06026303 C-274.36179365,264.06026247 -274.36179274,264.06026192 -274.36179183,264.06026136 C-274.36179191,264.06026192 -274.36179199000003,264.06026247 -274.36179207,264.06026303 C-274.363385,264.07115053 -282.14210497,317.23785677 -282.14424057,317.25245334 C-282.14424072,317.25245435 -282.14424086,317.25245535 -282.14424101000003,317.25245635 C-282.14424036,317.25245535 -282.14423972000003,317.25245435 -282.14423907,317.25245334 C-282.13556379,317.23900178 -253.01493139000002,272.08565164 -253.00777773000002,272.07455944000003 C-253.00777729,272.07455876 -253.00777685,272.07455808000003 -253.00777641000002,272.0745574 C-253.0077762,272.07455808000003 -253.00777598000002,272.07455876 -253.00777577,272.07455944000003 C-253.00407369,272.08637964 -236.94498788,323.36075517 -236.94029341,323.37574395 C-236.94029307,323.37574505000003 -236.94029272,323.37574614 -236.94029238000002,323.37574724 C-236.94029224,323.37574614 -236.94029211,323.37574505000003 -236.94029197,323.37574395 C-236.93893679,323.36487136 -233.57579239,296.38233594 -231.63375881000002,280.80138443 C-227.38036281,282.79425456 -219.39643824,286.53501249 -211.28762933000002,290.33428325 C-205.16854835,296.87217958 -199.14370778,303.30938529 -195.93400401,306.73877454 C-210.15268325,313.40074092000003 -234.77640267,324.93784562 -234.78481797,324.94178849 C-234.78481869,324.94178883 -234.78481941,324.94178916 -234.78482013,324.9417895 C-234.78478845,324.94178916 -234.78475678,324.94178883 -234.7847251,324.94178849 C-234.7023661,324.94091235 -181.11181131,324.37081248 -181.02945862,324.36993641 C-181.02942695000002,324.36993607 -181.02939528000002,324.36993574 -181.02936361,324.36993540000003 C-181.02936388,324.36993574 -181.02936416,324.36993607 -181.02936443000002,324.36993641 C-181.03547047,324.37742824000003 -214.98422041,366.0310182 -214.99243459000002,366.04109663 C-214.99243509000001,366.04109724 -214.99243558,366.04109785 -214.99243608,366.04109846 C-214.99243485,366.04109785 -214.99243362,366.04109724 -214.99243238,366.04109663 C-214.9770267,366.03347471 -166.82279781,342.20930063000003 -166.80856464000001,342.2022588 C-166.80856358,342.20225828 -166.80856253000002,342.20225776 -166.80856148,342.20225724 C-166.8085616,342.20225776 -166.80856173,342.20225828 -166.80856185,342.2022588 C-166.8110593,342.21268817000004 -179.32441783000002,394.46861057 -179.3277917,394.48269988 C-179.32779193000002,394.48270083 -179.32779215,394.48270178 -179.32779238,394.48270273 C-179.32779164000002,394.48270178 -179.3277909,394.48270083 -179.32779016,394.48269988 C-179.31789289,394.47001459 -146.26720626,352.1090864 -146.25890856,352.09845127 C-146.25890804,352.09845061 -146.25890752,352.09844994 -146.258907,352.09844927 C-146.25890686,352.09844994 -146.25890671,352.09845061 -146.25890657,352.09845127 C-146.25633621,352.11029194 -134.8580109,404.61818373 -134.85470985,404.63339047 C-134.85470961000001,404.63339157 -134.85470937,404.63339267000003 -134.85470913,404.63339377 C-134.85470889,404.63339267000003 -134.85470865,404.63339157 -134.85470841,404.63339047 C-134.85140736,404.61818373 -123.45308203,352.11029183 -123.4505117,352.09845127 C-123.45051156000001,352.09845061 -123.45051141,352.09844994 -123.45051127,352.09844927 C-123.45051075,352.09844994 -123.45051023,352.09845061 -123.45050971,352.09845127 C-123.44221208,352.10908631 -90.39152515,394.47001487 -90.3816281,394.48269988 C-90.38162736,394.48270083 -90.38162662,394.48270178 -90.38162588,394.48270273 C-90.38162611,394.48270178 -90.38162633,394.48270083 -90.38162656,394.48269988 C-90.38500036,394.46861088 -102.89835901000001,342.21268801 -102.90085642,342.20225881 C-102.90085654,342.20225829000003 -102.90085667,342.20225777 -102.90085679,342.20225725 C-102.90085574,342.20225777 -102.90085469,342.20225829000003 -102.90085363,342.20225881 C-102.88662069,342.20930052 -54.73239112,366.03347493 -54.71698588,366.04109663 C-54.71698464,366.04109724 -54.716983410000005,366.04109785 -54.71698218,366.04109846 C-54.71698268,366.04109785 -54.71698317,366.04109724 -54.71698367,366.04109663 C-54.72519762,366.03101848 -88.67394780000001,324.37742822 -88.68005383,324.36993640000003 C-88.6800541,324.36993606 -88.68005438,324.36993573 -88.68005465,324.36993539 C-88.68002298,324.36993573 -88.67999131,324.36993606 -88.67995964,324.36993640000003 C-88.597607,324.37081247000003 -35.00705209,324.94091235 -34.92469317,324.94178849 C-34.92466149,324.94178883 -34.92462982,324.94178916 -34.92459814,324.9417895 C-34.92459886,324.94178916 -34.92459958,324.94178883 -34.9246003,324.94178849 C-34.933015590000004,324.93784562 -59.55673502,313.40074092000003 -73.77541426,306.73877454 C-70.56571048,303.30938529 -64.54086991,296.87217958 -58.42178894,290.33428326 C-50.31298002,286.53501249 -42.32905545,282.79425457 -38.07565945,280.80138443 C-36.13362581,296.38233641 -32.77048127,323.36487302 -32.7691263,323.37574395 C-32.76912616,323.37574505000003 -32.76912603,323.37574614 -32.76912589,323.37574724 C-32.76912555,323.37574614 -32.7691252,323.37574505000003 -32.76912486,323.37574395 C-32.764431110000004,323.36075746 -16.70534434,272.08637892 -16.70164249,272.07455944000003 C-16.70164228,272.07455876 -16.70164206,272.07455808000003 -16.70164185,272.0745574 C-16.70164141,272.07455808000003 -16.70164097,272.07455876 -16.70164053,272.07455944000003 C-16.69448731,272.08565096 12.42614624,317.2390029 12.4348208,317.25245334 C12.43482145,317.25245435 12.43482209,317.25245535 12.43482274,317.25245635 C12.43482259,317.25245535 12.43482245,317.25245435 12.4348223,317.25245334 C12.43268688,317.23785798 4.65396667,264.07114998000003 4.65237382,264.06026303 C4.65237374,264.06026247 4.65237366,264.06026192 4.65237358,264.06026136 C4.65237449,264.06026192 4.6523754,264.06026247 4.65237631,264.06026303 C4.66529607,264.06816993 50.49109201,292.11356132000003 50.50537348,292.12230159 C50.50537459,292.12230227000003 50.50537571,292.12230295 50.50537682,292.12230363000003 C50.50537636,292.12230295 50.505375900000004,292.12230227000003 50.50537544,292.12230159 C50.49793612,292.11128813 20.41988319,247.58258177000002 20.41440469,247.57447119 C20.414404440000002,247.57447082000002 20.41440419,247.57447045 20.41440394,247.57447008 C20.41440761,247.57447045 20.41441127,247.57447082000002 20.41441494,247.57447119 C20.44240404,247.5772907 73.87402371,252.95978604 73.90218506000001,252.96262291 C73.90218878,252.96262328 73.90219249,252.96262366 73.9021962,252.96262403 C73.90219556,252.96262366 73.90219492,252.96262328 73.90219428,252.96262291 C73.89128236,252.95625987 27.46258584,225.88239669 27.46258584,225.88239669 C27.46258584,225.88239669 77.99125386,207.52947237 77.99125386,207.52947237 C77.99125386,207.52947237 24.4009404,203.28042237 24.4009404,203.28042237 C24.4009404,203.28042237 61.953750740000004,164.83056541 61.96266019,164.82144313 C61.96266072,164.82144259 61.962661250000004,164.82144205 61.962661780000005,164.82144151 C61.96266038,164.82144205 61.96265899,164.82144259 61.96265759,164.82144313 C61.94593109,164.82792451 11.85165186,184.23902769 11.835867760000001,184.24514389 C11.83586652,184.24514438 11.835865270000001,184.24514486 11.83586403,184.24514534 C11.83586419,184.24514486 11.83586435,184.24514438 11.83586452,184.24514389 C11.83920038,184.235237 28.98654265,133.31085349 28.99107831,133.29738343 C28.99107861,133.29738254 28.991078910000002,133.29738165 28.991079210000002,133.29738076 C28.99107838,133.29738165 28.99107754,133.29738254 28.99107671,133.29738343 C28.98320575,133.30579311 10.40270156,153.15802426 -0.32657921,164.62166163 C-2.59626919,160.50931238 -6.8566375100000005,152.79014253 -11.1836464,144.95022962000002 C-12.29120786,136.06425484 -13.38171168,127.31513359 -13.96267216,122.65408286 C1.45628657,125.62196881 28.15863406,130.76171418 28.16773547,130.76345267 C28.1005398,130.71571555 -15.59022611,99.67719822000001 -15.65740384,99.62947363 C-15.64809848,99.62697397000001 36.300383430000004,85.88308626 36.31295259,85.87976028 C36.29600765,85.87687073000001 -16.66508095,76.84668671 -16.68073565,76.84401686 C-16.67258627,76.83704665 24.166194660000002,41.91628439 24.1772055,41.90686849 C24.16174132,41.91131347 -27.4759558,56.7553184 -27.48892055,56.75904428 C-27.484041,56.7479541 -5.8421150200000005,7.56840878 -5.83584755,7.55416528 C-5.84745716,7.564527490000001 -45.93226129,43.34453749 -45.94130109,43.352605080000004 C-45.94147206,43.34762172 -46.1929195,36.007300120000004 -46.50988042,26.75448941 C-41.01648831,30.116445770000002 -37.1435067,32.48671075 -37.13991556,32.48890836 C-37.14332275,32.48386395 -41.449852130000004,26.108312050000002 -46.80351803,18.18253894 C-47.24360137,5.33550423 -47.7014475,-8.0300661 -47.77231124,-10.09874168 C-32.48201244,-8.55845614 -13.752260360000001,-6.67169324 -13.743096770000001,-6.67077041 C-13.75653938,-6.67860971 -60.17223674,-33.744892730000004 -60.182702920000004,-33.750997070000004 C-60.17002431,-33.75560294 -9.6691626,-52.09842752 -9.65404002,-52.10392138 C-9.66848161,-52.10506669 -63.23365846,-56.35212365 -63.24434848,-56.352972470000005 C-63.233252820000004,-56.36433415 -25.69464109,-94.7996533 -25.68263148,-94.81195083 C-25.69475441,-94.8072538 -75.80041361,-75.39174098000001 -75.80941581,-75.38827907 C-75.78311496,-75.46638977 -58.68052557,-126.25786563 -58.65422748,-126.33596757000001 C-58.66056365,-126.32922625 -77.24215002,-106.47583885 -87.97187008,-95.01173213 C-90.24156006,-99.12408139 -94.50192837,-106.84325123 -98.82893726,-114.68316413000001 C-99.93649872,-123.56913891 -101.02700255,-132.31826017 -101.60796302,-136.9793109 C-86.18636895,-134.01091769 -59.47752879,-128.86992259000002 -59.47752879,-128.86992259000002 C-59.47752879,-128.86992259000002 -103.30273407,-160.00394748 -103.30273407,-160.00394748 C-103.30273407,-160.00394748 -51.33233617,-173.75363345 -51.33233617,-173.75363345 C-51.33233617,-173.75363345 -104.32602887,-182.78937668 -104.32602887,-182.78937668 C-104.32602887,-182.78937668 -63.46808347,-217.72652625 -63.46808347,-217.72652625 C-63.46808347,-217.72652625 -115.13421285,-202.87434805 -115.13421285,-202.87434805 C-115.13421285,-202.87434805 -93.48113712,-252.07923027 -93.48113712,-252.07923027 C-93.48113712,-252.07923027 -133.58659261,-216.28078668 -133.58659261,-216.28078668 C-133.58659261,-216.28078668 -135.42704376,-270.00777117 -135.42704376,-270.00777117 C-135.42704376,-270.00777117 -156.02844803,-220.353383 -156.02844803,-220.353383 C-156.02844803,-220.353383 -180.99790216,-267.96118156 -180.99790216,-267.96118156 C-180.99790216,-267.96118156 -178.01489443,-214.28550935 -178.01489443,-214.28550935 C-178.01489443,-214.28550935 -178.01489443,-214.28550935 -178.01489443,-214.28550935 C-178.01489443,-214.28550935 -221.16784489,-246.34481361000002 -221.16784489,-246.34481361000002 C-221.16784489,-246.34481361000002 -208.02498248,-222.53185199 -200.43633814,-208.7823284 C-205.04878746,-207.89451076 -213.70668046,-206.22801438 -222.5,-204.5354507 C-231.29331954,-206.22801438 -239.95121254,-207.89451076 -244.56366187,-208.7823284 C-236.97501751000001,-222.53185198 -223.83215511,-246.3448136 -223.83215511,-246.3448136 C-223.83215511,-246.3448136 -266.98510556,-214.28550934 -266.98510556,-214.28550934 C-266.98510556,-214.28550934 -264.00209784000003,-267.96118156 -264.00209784000003,-267.96118156 C-264.00209784000003,-267.96118156 -288.97155197,-220.353383 -288.97155197,-220.353383 C-288.97155197,-220.353383 -309.57295624,-270.00777117 -309.57295624,-270.00777117 L-309.57295624,-270.00777117 L-309.57295624,-270.00777117 M-249.80875355,-199.27898252 C-249.80875355,-199.27898252 -247.96746624,-202.61512824000002 -245.265332,-207.5110038 C-243.31326458,-206.12422841 -240.44525732,-204.08675685 -237.11656888000002,-201.72201109 C-244.43501611,-200.31333511 -249.80875355,-199.27898252 -249.80875355,-199.27898252 L-249.80875355,-199.27898252 L-249.80875355,-199.27898252 M-207.88343113,-201.72201108000002 C-204.55474269,-204.08675685 -201.68673542,-206.12422842 -199.73466799,-207.5110038 C-197.03253376,-202.61512825 -195.19124644000001,-199.27898252 -195.19124644000001,-199.27898252 C-195.19124644000001,-199.27898252 -200.56498389,-200.31333511 -207.88343113,-201.72201108000002 L-207.88343113,-201.72201108000002 L-207.88343113,-201.72201108000002 M-240.84448310000002,-178.3060292 C-240.84448310000002,-178.3060292 -232.14624183,-184.4853787 -222.5,-191.33820076 C-212.85375817,-184.48537869 -204.1555169,-178.3060292 -204.1555169,-178.3060292 C-204.1555169,-178.3060292 -212.49907607,-176.09859336 -222.5,-173.45267244000001 C-232.50092393,-176.09859337 -240.84448310000002,-178.3060292 -240.84448310000002,-178.3060292 L-240.84448310000002,-178.3060292 L-240.84448310000002,-178.3060292 M-241.86777791,-155.5206 C-241.86777791,-155.5206 -232.98900412,-157.03448431 -222.49999999,-158.82292258 C-212.01099587000002,-157.03448432 -203.13222209,-155.52060001 -203.13222209,-155.52060001 C-203.13222209,-155.52060001 -212.65122177,-147.38101511 -222.5,-138.95944043 C-232.34877824,-147.38101511 -241.86777791,-155.5206 -241.86777791,-155.5206 L-241.86777791,-155.5206 L-241.86777791,-155.5206 M-341.96610743,-137.25377797000002 C-336.47484768,-138.31075158000002 -332.73299548,-139.03099416 -332.73299548,-139.03099416 C-332.73299548,-139.03099416 -335.37729944,-134.23989868 -338.97855447,-127.71494655000001 C-340.19894005000003,-131.61146402 -341.25042768000003,-134.96871436 -341.96610743,-137.25377797000002 L-341.96610743,-137.25377797000002 L-341.96610743,-137.25377797000002 M-106.02144553000001,-127.71494656 C-109.62270056,-134.23989867 -112.26700452,-139.03099416 -112.26700452,-139.03099416 C-112.26700452,-139.03099416 -108.52515233,-138.31075158000002 -103.03389258,-137.25377796 C-103.74957232,-134.96871435 -104.80105995,-131.61146401 -106.02144553000001,-127.71494656 L-106.02144553000001,-127.71494656 L-106.02144553000001,-127.71494656 M-231.02288616,-86.23074642 C-231.02288616,-86.23074642 -252.67596118,-135.43562705 -252.67596189,-135.43562864 C-252.67596189,-135.43562864 -243.66898731,-132.84644317000001 -233.1763819,-129.83019158 C-239.33956614000002,-124.56012507 -243.99016699,-120.58345087000001 -243.9901675,-120.58345043 C-243.9901675,-120.58345043 -233.81755364,-123.50771582 -222.49999999,-126.76111075 C-211.18244635,-123.50771582 -201.00983250000002,-120.58345044000001 -201.00983250000002,-120.58345044000001 C-201.00983250000002,-120.58345044000001 -205.66043352,-124.56012478000001 -211.82361809,-129.83019157 C-201.33101268000001,-132.84644317000001 -192.32403812,-135.43562863 -192.32403812,-135.43562863 C-192.32403872,-135.43562726 -213.97711384000002,-86.23074642 -213.97711384000002,-86.23074642 C-213.97711384000002,-86.23074642 -173.87165835,-122.02919 -173.87165835,-122.02919 C-173.87165835,-122.02919 -173.55081185,-112.66294583 -173.1770442,-101.75181209 C-180.09370434000002,-105.98480873 -185.31286114,-109.17893318 -185.31286114,-109.17893318 C-185.31286114,-109.17893318 -179.38820893,-100.40785019 -172.79673006000002,-90.64957137 C-172.39357611,-78.88058391 -172.0312072,-68.30220551000001 -172.0312072,-68.30220551000001 C-172.0312072,-68.30220551000001 -169.68627989,-73.95404987 -166.57867731000002,-81.44412691000001 C-160.46765818,-72.39713917 -155.22188826000001,-64.63109963 -155.22188826000001,-64.63109963 C-155.22188826000001,-64.63109963 -208.70968051,-70.01925358 -208.70968051,-70.01925358 C-208.70968051,-70.01925358 -162.27007016000002,-42.939026240000004 -162.27007016000002,-42.939026240000004 C-162.27007016000002,-42.939026240000004 -171.07875069,-39.73955446 -181.34035498,-36.01235455 C-179.45190165,-43.89855635 -178.02691702,-49.849307780000004 -178.02691674,-49.84930895 C-178.02691725,-49.849308300000004 -184.53789444,-41.504215800000004 -191.78168983,-32.21987325 C-202.85008062,-28.19963392 -212.79873817,-24.58610191 -212.79873817,-24.58610191 C-212.79873817,-24.58610191 -206.69889238000002,-24.10245948 -198.61510379,-23.46151488 C-205.33089167,-14.85391736 -211.09580214000002,-7.4650555 -211.09580214000002,-7.4650555 C-211.09580214000002,-7.4650555 -222.5,-60 -222.5,-60 C-222.5,-60 -233.90419787000002,-7.46505551 -233.90419787000002,-7.46505551 C-233.90419787000002,-7.46505551 -239.66910833,-14.85391737 -246.38489621,-23.46151488 C-238.30110762,-24.10245948 -232.20126182,-24.58610191 -232.20126182,-24.58610191 C-232.20126182,-24.58610191 -232.20126182,-24.58610191 -232.20126182,-24.58610191 C-232.20126182,-24.58610191 -242.14991937,-28.19963392 -253.21831017,-32.21987325 C-260.46210584,-41.50421617 -266.97308326,-49.84930896 -266.97308326,-49.84930896 C-266.97308326,-49.84930896 -265.54809853,-43.89855714 -263.65964502,-36.01235455 C-273.92124931,-39.73955446 -282.72992984,-42.939026240000004 -282.72992984,-42.939026240000004 C-282.72992833,-42.93902712 -236.29031949,-70.01925358 -236.29031949,-70.01925358 C-236.29031949,-70.01925358 -289.77811174,-64.63109962 -289.77811174,-64.63109962 C-289.77811174,-64.63109962 -284.53234182,-72.39713917 -278.42132269,-81.44412691000001 C-275.31372032,-73.95405039 -272.96879313,-68.30220629 -272.9687928,-68.30220551000001 C-272.9687928,-68.30220551000001 -272.6064239,-78.88058391 -272.20326994,-90.64957137 C-265.61179108,-100.40785018 -259.68713886,-109.17893318 -259.68713886,-109.17893318 C-259.68713886,-109.17893318 -264.90629567,-105.98480873 -271.82295579,-101.75181208000001 C-271.44918814,-112.66294583 -271.12834165,-122.02919 -271.12834165,-122.02919 C-271.12834165,-122.02919 -231.02288616,-86.23074642 -231.02288616,-86.23074642 L-231.02288616,-86.23074642 L-231.02288616,-86.23074642 M-128.39041607000001,-105.07805743 C-128.96445119,-115.40713583 -129.44335653000002,-124.02446734 -129.44335653000002,-124.02446734 C-129.44335653000002,-124.02446734 -120.87854156,-117.66147231000001 -111.38026782,-110.60498963 C-114.91683623,-99.31323 -118.10584247,-89.13118695 -118.10584247,-89.13118695 C-118.10584247,-89.13118695 -122.78354027,-96.38424808 -128.39041607000001,-105.07805743 L-128.39041607000001,-105.07805743 L-128.39041607000001,-105.07805743 M-333.61973218,-110.60498962 C-324.12145845000003,-117.6614723 -315.55664347,-124.02446733000001 -315.55664347,-124.02446733000001 C-315.55664353000003,-124.0244662 -316.03554885,-115.40713515 -316.60958393,-105.07805742000001 C-322.21645972,-96.38424808 -326.89415753000003,-89.13118695 -326.89415753000003,-89.13118695 C-326.89415753000003,-89.13118695 -330.08316377,-99.31323 -333.61973218,-110.60498962 L-333.61973218,-110.60498962 L-333.61973218,-110.60498962 M-302.695865,-100.5572161 C-297.7536786,-109.98019399 -293.57019708,-117.95659369 -293.57019708,-117.95659369 C-293.57019708,-117.95659369 -288.77052448,-106.38821696000001 -283.80457087,-94.41906254 C-294.85740418,-87.65472751 -305.54014211000003,-81.11689091 -305.54014211000003,-81.11689091 C-305.54014203,-81.11689149 -304.23623961,-90.0289229 -302.695865,-100.5572161 L-302.695865,-100.5572161 L-302.695865,-100.5572161 M-161.19542914000002,-94.41906254 C-156.22947551000001,-106.38821695 -151.42980292000001,-117.95659368 -151.42980292000001,-117.95659368 C-151.42980292000001,-117.95659368 -151.42980292000001,-117.95659368 -151.42980292000001,-117.95659368 C-151.42980292000001,-117.95659368 -147.24632139,-109.98019401 -142.304135,-100.55721611 C-140.76376036,-90.02892265 -139.45985791,-81.11689105 -139.45985789,-81.11689092 C-139.45985789,-81.11689092 -150.14259582,-87.65472752000001 -161.19542914000002,-94.41906254 L-161.19542914000002,-94.41906254 L-161.19542914000002,-94.41906254 M-356.03585638,-93.95154326000001 C-354.1137326,-95.37953255000001 -351.28971901,-97.4775562 -348.01209083000003,-99.91258041 C-348.93388495,-92.51701865 -349.61073337,-87.08665811 -349.61073337,-87.08665811 C-349.61073337,-87.08665811 -352.21460742,-89.86875214 -356.03585638,-93.95154326000001 L-356.03585638,-93.95154326000001 L-356.03585638,-93.95154326000001 M-95.38926663000001,-87.08665812 C-95.38926663000001,-87.08665812 -96.06611506,-92.51701866 -96.98790916,-99.91258041 C-93.71028099,-97.4775562 -90.88626740000001,-95.37953255000001 -88.96414362,-93.95154326000001 C-92.78539258,-89.86875214 -95.38926663000001,-87.08665812 -95.38926663000001,-87.08665812 L-95.38926663000001,-87.08665812 L-95.38926663000001,-87.08665812 M-285.79157528,-20.33705193 C-285.79157528,-20.33705193 -273.30621482,-21.32698681 -260.38830846,-22.35121724 C-257.37055689,-9.74905361 -254.45385234,2.43113653 -254.45385234,2.43113653 C-254.45385234,2.43113653 -262.52677009,-1.5629176200000001 -272.06377032,-6.2813226 C-279.49833527,-13.8934815 -285.79157528,-20.33705193 -285.79157528,-20.33705193 L-285.79157528,-20.33705193 L-285.79157528,-20.33705193 M-190.54614765,2.43113653 C-190.54614765,2.43113653 -187.62944311,-9.74905361 -184.61169154,-22.35121724 C-171.69378518,-21.3269868 -159.20842472,-20.33705192 -159.20842472,-20.33705192 C-159.20842472,-20.33705192 -165.50166473000002,-13.893481490000001 -172.93622969,-6.2813226 C-174.17728828,-5.66731223 -175.39355402,-5.06556806 -176.57046338,-4.48329535 C-176.43675062,-6.88929722 -176.35680698000002,-8.32778779 -176.35680698000002,-8.32778779 C-176.35680698000002,-8.32778779 -177.37696402,-6.382714 -179.0048711,-3.27887873 C-179.0048711,-3.27887873 -179.0048711,-3.27887873 -179.0048711,-3.27887873 C-185.66256366000002,0.0149966 -190.54614765,2.43113653 -190.54614765,2.43113653 L-190.54614765,2.43113653 L-190.54614765,2.43113653 M-54.74934985,6.41922358 C-61.32508601,-3.31574943 -67.22754095,-12.05397117 -67.23088511,-12.05892278 C-67.22598318,-12.05842927 -58.38387995,-11.167710060000001 -47.89114559,-10.1107126 C-48.47653346,-8.69978713 -51.33992883,-1.79830895 -54.74934985,6.41922358 L-54.74934985,6.41922358 L-54.74934985,6.41922358 M-291.17577131,20.02409 C-294.95175699,8.81012224 -298.35665138,-1.30177411 -298.35665164,-1.3017748900000001 C-298.35665164,-1.3017748900000001 -290.30907225,1.81659305 -280.66292153,5.55439364 C-274.12870492,13.57158367 -268.67710317,20.26045394 -268.6746553,20.26345736 C-268.67465503,20.2634577 -268.67465475,20.26345803 -268.67465448,20.263458370000002 C-268.67468618,20.26345803 -268.67471789,20.2634577 -268.67474959000003,20.26345736 C-268.71145121,20.26306692 -279.36421163,20.14974216 -291.17577131,20.02409 L-291.17577131,20.02409 L-291.17577131,20.02409 M-164.33707847,5.55439364 C-154.69092774,1.81659304 -146.64334835,-1.3017748900000001 -146.64334835,-1.3017748900000001 C-146.64334835,-1.3017748900000001 -150.04824286000002,8.81012181 -153.82422869,20.02409 C-165.63578743,20.14974215 -176.28854709,20.26306691 -176.32525040000002,20.26345736 C-176.3252821,20.2634577 -176.32531381,20.26345803 -176.32534552,20.263458370000002 C-176.32534525,20.26345803 -176.32534497,20.2634577 -176.32534470000002,20.26345736 C-176.32289674,20.26045383 -170.87129502,13.5715836 -164.33707847,5.55439364 L-164.33707847,5.55439364 L-164.33707847,5.55439364 M-81.57302800000001,14.13165511 C-82.41403593,8.38345656 -82.9922899,4.4311526500000005 -82.99291635,4.42686976 C-82.98830832,4.42968908 -71.3553941,11.54903328 -59.80940313,18.61518055 C-64.32306176,29.494193680000002 -68.38133291,39.27561208 -68.38315757,39.280008540000004 C-68.38698503,39.2727117 -75.10653964000001,26.46093008 -81.57302800000001,14.13165511 L-81.57302800000001,14.13165511 L-81.57302800000001,14.13165511 M-319.09944690000003,19.7270361 C-318.40808245,14.18021339 -317.93688191,10.39976755 -317.93649168,10.39663675 C-317.9364916,10.3966361 -317.93649152,10.39663545 -317.93649144,10.396634800000001 C-317.93649083,10.39663545 -317.93649023,10.3966361 -317.93648962000003,10.39663675 C-317.93347755,10.39985497 -314.19497158,14.394239840000001 -309.10427450000003,19.8333654 C-313.18720271,19.78993094 -316.70506506,19.75250769 -319.09944690000003,19.7270361 L-319.09944690000003,19.7270361 L-319.09944690000003,19.7270361 M-131.30920682,14.93292633 C-128.71300613,12.15903085 -127.06551040000001,10.39877365 -127.06351038,10.39663675 C-127.06350978,10.3966361 -127.06350917,10.39663545 -127.06350856,10.396634800000001 C-127.06350848,10.39663545 -127.0635084,10.3966361 -127.06350832,10.39663675 C-127.06313832000001,10.39960522 -126.63989296,13.795306270000001 -126.00713133,18.87195808 C-126.00713133,18.87195808 -126.00713138,18.87195808 -126.00713138,18.87195808 C-128.16905622000002,17.26581521 -129.99734498,15.90753814 -131.30920682,14.93292633 L-131.30920682,14.93292633 L-131.30920682,14.93292633 M-110.06946908,30.71240809 C-106.96085186,20.78703603 -104.35686579,12.47287949 -104.34694657,12.44120951 C-104.34447341,12.44501258 -97.88706744,22.45760503 -91.05281038,33.05453631 C-91.05281038,33.05453631 -91.05281038,33.05453631 -91.05281038,33.05453631 C-90.65684738,40.17948477 -90.36978771,45.34487663 -90.3696293,45.34786447 C-90.40078624,45.32471766 -99.9582113,38.22429033 -110.06946908,30.71240809 L-110.06946908,30.71240809 L-110.06946908,30.71240809 M-135.8957255,19.8333654 C-134.527604,18.37160397 -133.25714613,17.01419069 -132.1306136,15.81055358 C-131.54910207,16.86417156 -130.80683771,18.20904984 -129.94528315,19.7700642 C-129.94528315,19.7700642 -129.94528313,19.7700642 -129.94528313,19.7700642 C-131.71297202,19.78886899 -133.72444694,19.810267200000002 -135.8957255,19.8333654 L-135.8957255,19.8333654 L-135.8957255,19.8333654 M-145.70290032,30.530258200000002 C-139.23032337,27.49762139 -132.8718455,24.51844414 -128.46431973,22.45335862 C-127.32425768,24.51898809 -126.05213426,26.823893 -124.71373619,29.24887755 C-123.74109071000001,37.05241919 -122.64633187,45.83567421 -121.80494815,52.58608718 C-125.75253838,53.34593081 -130.28533935000002,54.21841751 -134.85470912,55.09794307 C-140.66973246,53.97865049 -146.42553136,52.87075761 -150.99133528000002,51.991918410000004 C-152.77099025,51.15808558 -154.52067987,50.3382926 -156.20087223000002,49.551061600000004 C-153.09189462,43.91804483 -149.21464906,36.89303789 -145.70290032,30.530258200000002 L-145.70290032,30.530258200000002 L-145.70290032,30.530258200000002 M-282.7271823,45.114811360000004 C-283.45268482,42.96020527 -284.39370354,40.16555652 -285.45416926,37.01617265 C-278.70542211000003,40.1782052 -273.75,42.5 -273.75,42.5 C-273.75,42.5 -276.46919674000003,43.77404219 -280.62225868,45.71990218 C-280.62225868,45.71990218 -280.62225868,45.71990218 -280.62225868,45.71990218 C-281.34219036,45.51294738 -282.04490224,45.31094266 -282.7271823,45.114811360000004 L-282.7271823,45.114811360000004 L-282.7271823,45.114811360000004 M-116.60691924,51.585555490000004 C-116.12254282,50.03901056 -115.60951098,48.40097294 -115.07870022,46.70616978 C-114.27697624,48.15877746 -113.50914433,49.549977580000004 -112.79104727,50.85106537 C-113.8614309,51.05709592 -115.14967672,51.30506122 -116.60691924,51.585555490000004 C-116.60691924,51.585555490000004 -116.60691924,51.585555490000004 -116.60691924,51.585555490000004 L-116.60691924,51.585555490000004 L-116.60691924,51.585555490000004 M-118.11597979,56.40377088 C-115.69359851,54.682878110000004 -113.612626,53.20452679 -112.08937712000001,52.12238997 C-109.38818605,57.01655667 -107.54724104,60.35208215 -107.54595624,60.35441002 C-107.54595602,60.35441043 -107.54595579000001,60.35441083 -107.54595557,60.354411240000005 C-107.54595768,60.35441083 -107.54595979,60.35441043 -107.54596190000001,60.35441002 C-107.55220645,60.35320805 -112.17486373,59.463425560000005 -118.68197646,58.21091761 C-118.50396075,57.64253882 -118.31486971,57.03879806 -118.11597979,56.40377088 L-118.11597979,56.40377088 L-118.11597979,56.40377088 M-153.19919224,81.32736457 C-153.19919224,81.32736457 -144.50095098,75.14801507 -134.85470913,68.29519301 C-125.20846768,75.1480148 -116.51022671,81.32736409 -116.51022603,81.32736457 C-116.51022603,81.32736457 -116.51022603,81.32736457 -116.51022603,81.32736457 C-116.5102268,81.32736477 -124.85378566,83.53480051 -134.85470914,86.18072133 C-144.85563307,83.53480040000001 -153.19919224,81.32736457 -153.19919224,81.32736457 L-153.19919224,81.32736457 L-153.19919224,81.32736457 M-145.45155322,102.6172968 C-142.38306313,102.09410079 -138.72871328,101.47101217000001 -134.85470915,100.81047118000001 C-124.3657051,102.59890944 -115.48693136,104.11279374 -115.48693123,104.11279376 C-115.48693123,104.11279376 -125.0059309,112.25237866 -134.85470913,120.67395333 C-144.70348736,112.25237865 -154.22248704,104.11279375000001 -154.22248704,104.11279375000001 C-154.22248704,104.11279375000001 -152.161226,103.76133635000001 -148.85175418,103.19705151000001 C-144.92051631,105.14202078 -142.36582322,106.40594825000001 -142.36227675,106.40770286 C-142.36227551,106.40770347 -142.36227428,106.40770408 -142.36227305,106.40770469 C-142.36227355,106.40770408 -142.36227404000002,106.40770347 -142.36227454000002,106.40770286 C-142.36380207,106.40582865 -143.53908358,104.96381108 -145.45155322,102.6172968 L-145.45155322,102.6172968 L-145.45155322,102.6172968 M-18.37615466,131.91844721 C-21.97636191,125.39539351 -24.62017486,120.60518766 -24.62171298,120.60240082 C-24.621713200000002,120.60240041 -24.62171343,120.60240001 -24.62171365,120.6023996 C-24.62171154,120.60240001 -24.62170944,120.60240041 -24.62170733,120.60240082 C-24.61609669,120.60348077 -20.87574066,121.32343536 -15.3886017,122.3796158 C-16.10428146,124.66467941 -17.15576908,128.02192974 -18.37615466,131.91844721 L-18.37615466,131.91844721 L-18.37615466,131.91844721 M-254.32081656,122.3796158 C-248.83367836,121.32343551 -245.09332261,120.60348098 -245.08771093000001,120.60240083000001 C-245.08770883,120.60240042000001 -245.08770672,120.60240002 -245.08770461,120.60239961 C-245.08770483,120.60240002 -245.08770506000002,120.60240042000001 -245.08770528,120.60240083000001 C-245.08924368,120.60518819 -247.73305654,125.39539386 -251.33326361000002,131.91844721 C-252.55364918,128.02192975 -253.60513681,124.66467941 -254.32081656,122.3796158 L-254.32081656,122.3796158 L-254.32081656,122.3796158 M-143.37759529000002,173.40264734000002 C-143.37759565,173.40264652 -143.37759601,173.40264569 -143.37759638,173.40264487000002 C-143.38319683,173.38991829 -165.02655620000002,124.20711572 -165.03067043000001,124.19776646 C-165.03067063,124.19776602 -165.03067082,124.19776557 -165.03067102,124.19776513000001 C-165.03066947,124.19776557 -165.03066793,124.19776602 -165.03066638,124.19776646 C-165.02320765000002,124.19991059 -156.01934939,126.78820021 -145.53109104,129.80320219 C-151.69213555,135.07143904 -156.34164676,139.04718151 -156.34487495,139.04994189 C-156.34487551,139.04994237 -156.34487607,139.04994285 -156.34487663000002,139.04994333 C-156.34487496,139.04994285 -156.34487329,139.04994237 -156.34487163,139.04994189 C-156.33663801,139.04757502 -146.16767993,136.12436055 -134.85470913,132.87228301 C-123.54173313,136.12436203000001 -113.37277091,139.0475777 -113.36454663,139.04994188 C-113.36454497,139.04994236000002 -113.36454330000001,139.04994284 -113.36454164,139.04994332 C-113.3645422,139.04994284 -113.36454276,139.04994236000002 -113.36454332,139.04994188 C-113.36776784,139.04718463 -118.01728029,135.07144110000002 -124.17832722,129.80320219 C-113.69006755000001,126.78819983 -104.68620835,124.19990993 -104.67875188000001,124.19776646 C-104.67875034000001,124.19776602 -104.67874879,124.19776557 -104.67874725,124.19776513000001 C-104.67874745,124.19776557 -104.67874764,124.19776602 -104.67874784,124.19776646 C-104.68286081000001,124.20711287 -126.32622047,173.38991611 -126.33182188,173.40264488 C-126.33182225,173.4026457 -126.33182261,173.40264653 -126.33182297,173.40264735 C-126.33182297,173.40264735 -126.33182297,173.40264735 -126.33182297,173.40264735 C-126.33182297,173.40264735 -126.33182297,173.40264735 -126.33182297,173.40264735 C-126.33182205,173.40264653 -126.33182112,173.4026457 -126.3318202,173.40264488 C-126.31965773,173.39178857000002 -86.23694188,137.61364256000002 -86.22636957,137.60420564 C-86.22636887,137.60420501000002 -86.22636818000001,137.60420439 -86.22636748000001,137.60420377 C-86.22636746,137.60420439 -86.22636744,137.60420501000002 -86.22636742,137.60420564 C-86.22620206,137.60903288 -85.90542463,146.9732607 -85.53175333,157.88158168 C-92.4452146,153.65054275 -97.66274232,150.45741529 -97.66756691,150.45446264 C-97.66756803,150.45446196 -97.66756914,150.45446127 -97.66757026,150.45446059 C-97.6675698,150.45446127 -97.66756934,150.45446196 -97.66756888,150.45446264 C-97.66426319,150.4593565 -91.74107845,159.22826702 -85.1514392,168.9838224 C-84.74841383,180.74905615 -84.38614749,191.3244402 -84.38591644,191.33118503 C-84.3859164,191.33118611 -84.38591637,191.33118718 -84.38591633,191.33118826 C-84.38591588,191.33118718 -84.38591544,191.33118611 -84.38591499,191.33118503 C-84.38386976,191.32625552000002 -82.03963302,185.67607559 -78.93338644,178.18926685 C-72.82370011,187.23428148 -67.57888561,194.99890659 -67.57659815,195.00229303 C-67.5765979,195.0022934 -67.57659765,195.00229377 -67.5765974,195.00229414 C-67.57660107,195.00229377 -67.57660473,195.0022934 -67.5766084,195.00229303 C-67.60459794,194.99947347 -121.03621707,189.61697819 -121.0643785,189.61414131 C-121.06438222,189.61414094 -121.06438593,189.61414056 -121.06438964,189.61414019 C-121.064389,189.61414056 -121.06438836000001,189.61414094 -121.06438772,189.61414131 C-121.05347578,189.62050437 -74.6247793,216.69436753 -74.6247793,216.69436753 C-74.6247793,216.69436753 -83.43345982,219.8938393 -93.69506412,223.62103922 C-91.80736555,215.73798931000002 -90.38276528,209.788843 -90.38162655000001,209.78408766 C-90.38162633,209.78408671 -90.3816261,209.78408576 -90.38162587000001,209.78408481 C-90.38162661,209.78408576 -90.38162735,209.78408671 -90.3816281,209.78408766 C-90.38602198,209.78971929 -96.89504869,218.13231186000002 -104.13639896000001,227.41352051 C-115.20478975,231.43375984 -125.15344731,235.04729185 -125.15344731,235.04729185 C-125.15344731,235.04729185 -119.05360151000001,235.53093429 -110.96981292,236.17187889000002 C-117.68358105,244.77688769 -123.44704362,252.16389381000002 -123.4505097,252.16833627 C-123.45051022,252.16833693 -123.45051074,252.1683376 -123.45051126,252.16833827 C-123.45051141,252.1683376 -123.45051155,252.16833693 -123.4505117,252.16833627 C-123.45308326,252.15649000000002 -134.85140674000002,199.64860668 -134.85470841,199.63339707 C-134.85470865,199.63339597 -134.85470889,199.63339487000002 -134.85470913,199.63339377 C-134.85470937,199.63339487000002 -134.85470961000001,199.63339597 -134.85470985,199.63339707 C-134.85801152,199.64860668 -146.25633556,252.15649263 -146.25890656,252.16833627 C-146.2589067,252.16833693 -146.25890685000002,252.1683376 -146.25890699,252.16833827 C-146.25890751,252.1683376 -146.25890803000001,252.16833693 -146.25890855,252.16833627 C-146.26237386,252.16389479 -152.02583675,244.77688828 -158.73960534,236.17187888 C-150.65581731,235.53093434000002 -144.5559718,235.04729192 -144.55597095000002,235.04729186 C-144.55597095000002,235.04729186 -154.50462851,231.43375985 -165.5730193,227.41352052 C-172.81436931000002,218.13231221 -179.32339581,209.78971989000001 -179.32779016,209.78408766 C-179.32779091,209.78408671 -179.32779165,209.78408576 -179.32779239,209.78408481 C-179.32779216,209.78408576 -179.32779193000002,209.78408671 -179.32779171,209.78408766 C-179.32665286,209.78884351 -177.90205264,215.73798964 -176.01435415,223.62103921 C-186.27595845,219.8938393 -195.08463898,216.69436753 -195.08463898,216.69436753 C-195.08463898,216.69436753 -148.6559548,189.62051155 -148.64503055,189.61414131 C-148.64502991,189.61414094 -148.64502926,189.61414056 -148.64502862,189.61414019 C-148.64503234,189.61414056 -148.64503606,189.61414094 -148.64503978,189.61414131 C-148.67323298,189.61698139 -202.10479404,194.99947083 -202.13280985,195.00229303 C-202.13281352,195.0022934 -202.1328172,195.00229377 -202.13282087000002,195.00229414 C-202.13282062000002,195.00229377 -202.13282037,195.0022934 -202.13282012,195.00229303 C-202.13053052,194.99890341 -196.88571691,187.23427962 -190.77603182000001,178.18926685 C-187.66978675000001,185.67607195 -185.32555078000001,191.32625002 -185.32350327,191.33118502 C-185.32350282000002,191.3311861 -185.32350238,191.33118718 -185.32350193,191.33118826 C-185.32350189000002,191.33118718 -185.32350186,191.3311861 -185.32350182,191.33118502 C-185.32327051000001,191.32443268 -184.96100429,180.74905197 -184.55797906,168.98382239 C-177.96833811,159.22826449000002 -172.04515199,150.45935196 -172.04184937,150.45446264 C-172.04184891,150.45446195 -172.04184845,150.45446127 -172.04184799,150.45446059 C-172.0418491,150.45446127 -172.04185022000001,150.45446195 -172.04185133000001,150.45446264 C-172.04667145,150.45741255000002 -177.26420068000002,153.65054093 -184.17766492,157.88158168 C-183.80399365,146.9732614 -183.48321625,137.60903409 -183.48305084,137.60420564 C-183.48305082000002,137.60420502 -183.4830508,137.60420439 -183.48305078,137.60420377 C-183.48305008,137.60420439 -183.48304938,137.60420502 -183.48304869,137.60420564 C-183.47247373,137.61364493 -143.38975843,173.39179043000001 -143.37759806,173.40264487000002 C-143.37759713,173.40264569 -143.37759621,173.40264652 -143.37759529000002,173.40264734000002 L-143.37759529000002,173.40264734000002 L-143.37759529000002,173.40264734000002 M-40.745125200000004,154.55533634 C-41.31900982,144.22896593000002 -41.79781454,135.61344499 -41.79806556,135.60892821000002 C-41.79806559,135.60892761 -41.79806563,135.60892702 -41.79806566,135.60892643 C-41.798064860000004,135.60892702 -41.79806407,135.60892761 -41.79806327,135.60892821000002 C-41.79283928,135.61280923 -33.23035273,141.97407441000001 -23.73497695,149.02840414 C-27.27062975,160.31724036 -30.45890032,170.49693452 -30.46055096,170.50220477 C-30.460551170000002,170.50220546 -30.46055139,170.50220614 -30.460551600000002,170.50220682 C-30.46055204,170.50220614 -30.46055248,170.50220546 -30.46055292,170.50220477 C-30.46342147,170.49775691 -35.13996933,163.24647885000002 -40.745125200000004,154.55533634 L-40.745125200000004,154.55533634 L-40.745125200000004,154.55533634 M-245.97444131,149.02840414 C-236.47906611,141.97407485 -227.91658002,135.61281001 -227.91135499,135.60892822 C-227.9113542,135.60892762 -227.9113534,135.60892703 -227.91135260000001,135.60892644 C-227.91135263,135.60892703 -227.91135267,135.60892762 -227.9113527,135.60892822 C-227.91160377,135.61344590000002 -228.39040847,144.22896647 -228.96429306000002,154.55533634 C-234.56944848,163.24647812 -239.24599601,170.49775569 -239.24886534,170.50220476 C-239.24886578000002,170.50220544 -239.24886622,170.50220613 -239.24886666,170.50220681 C-239.24886687,170.50220613 -239.24886709,170.50220544 -239.2488673,170.50220476 C-239.25051839,170.49693309 -242.43878876,160.31723956 -245.97444131,149.02840414 L-245.97444131,149.02840414 L-245.97444131,149.02840414 M-215.05057412,159.07617766 C-210.10984033,149.65596935 -205.92736548,141.68148903 -205.92490729,141.67680215000001 C-205.92490693,141.67680146 -205.92490657,141.67680077 -205.92490621000002,141.67680008 C-205.92490592000001,141.67680077 -205.92490564,141.67680146 -205.92490535000002,141.67680215000001 C-205.9225634,141.68244682 -201.12402164,153.24809799 -196.15927999000002,165.21433122 C-207.20888065,171.97668787 -217.88860236,178.51267855 -217.89484849000002,178.51650118 C-217.8948494,178.51650174 -217.89485032000002,178.5165023 -217.89485123,178.51650286 C-217.89485115,178.5165023 -217.89485107000002,178.51650174 -217.89485098,178.51650118 C-217.89419796,178.51203781 -216.59056291000002,169.60183386 -215.05057412,159.07617766 L-215.05057412,159.07617766 L-215.05057412,159.07617766 M-73.55013826,165.21433122 C-68.58539718,153.24809932 -63.78685594,141.68244941 -63.784512920000004,141.67680216 C-63.78451263,141.67680147000002 -63.78451235,141.67680078 -63.784512060000004,141.67680009 C-63.7845117,141.67680078 -63.78451134,141.67680147000002 -63.78451097,141.67680216 C-63.78205166,141.68149118 -59.599577260000004,149.65597063 -54.65884413,159.07617769 C-53.11885505,169.60183584 -51.81521981,178.51204113 -51.81456727,178.51650117 C-51.814567180000004,178.51650173000002 -51.8145671,178.51650229 -51.81456702,178.51650285 C-51.81456793,178.51650229 -51.81456884,178.51650173000002 -51.81456976,178.51650117 C-51.820811230000004,178.51268139 -62.50053519,171.97668934 -73.55013826,165.21433122 L-73.55013826,165.21433122 L-73.55013826,165.21433122 M-268.3905655,165.6818505 C-266.46844173,164.25386122 -263.64442814,162.15583756 -260.36679997,159.72081336 C-261.28827547000003,167.11381898 -261.96497459,172.54298163 -261.96544226000003,172.5467337 C-261.96544234,172.54673435 -261.96544242,172.546735 -261.9654425,172.54673565000002 C-261.96544311,172.546735 -261.96544371,172.54673435 -261.96544432,172.5467337 C-261.96795752,172.5440485 -264.57116176,169.76267011000002 -268.3905655,165.6818505 L-268.3905655,165.6818505 L-268.3905655,165.6818505 M-7.7439757700000005,172.54673565000002 C-7.74397585,172.546735 -7.74397593,172.54673435 -7.74397601,172.54673371 C-7.744443410000001,172.5429838 -8.42114261,167.11382046 -9.3426183,159.72081336 C-6.06499013,162.15583756 -3.24097653,164.25386122 -1.31885276,165.6818505 C-5.1382575600000004,169.76267125 -7.74146221,172.54405005 -7.74397395,172.54673371 C-7.74397456,172.54673435 -7.74397516,172.546735 -7.7439757700000005,172.54673565000002 L-7.7439757700000005,172.54673565000002 L-7.7439757700000005,172.54673565000002 M-198.14628442,239.29634184 C-198.14628442,239.29634184 -185.66092395,238.30640696 -172.74301759,237.28217653000002 C-169.72589006,249.88173413 -166.80976779,262.05949273 -166.80856185,262.06452873 C-166.80856173,262.06452925 -166.8085616,262.06452977 -166.80856148,262.06453029 C-166.80856253000002,262.06452977 -166.80856359,262.06452925 -166.80856464000001,262.06452873 C-166.81439239,262.06164546 -174.88492331,258.06877219 -184.41847946000001,253.35207116 C-191.85304441,245.73991227000002 -198.14628442,239.29634184 -198.14628442,239.29634184 L-198.14628442,239.29634184 L-198.14628442,239.29634184 M-102.90085679,262.06453029 C-102.90085667,262.06452977 -102.90085654,262.06452925 -102.90085642,262.06452873 C-102.89964973000001,262.05948959 -99.9835278,249.8817325 -96.96640067,237.28217653000002 C-84.04849431,238.30640696 -71.56313384,239.29634184 -71.56313384,239.29634184 C-71.56313384,239.29634184 -77.85637385,245.73991226 -85.29093879,253.35207115 C-94.8244928,258.06877112 -102.89502224,262.06164366 -102.90085363,262.06452873 C-102.90085468000001,262.06452925 -102.90085574,262.06452977 -102.90085679,262.06453029 L-102.90085679,262.06453029 L-102.90085679,262.06453029 M-203.53048044,279.65748377 C-207.30564151000002,268.44596495 -210.70987335,258.33603625 -210.71136028,258.33162033 C-210.71136045,258.33161984000003 -210.71136061,258.33161936 -210.71136077,258.33161888 C-210.71135953,258.33161936 -210.71135828,258.33161984000003 -210.71135704,258.33162033 C-210.70502982,258.33407207 -202.65998726,261.451457 -193.01763067000002,265.18778741 C-186.4834141,273.20497738 -181.03181235,279.89384764 -181.02936443000002,279.89685113 C-181.02936416,279.89685147 -181.02936388,279.8968518 -181.02936361,279.89685214 C-181.02939532,279.8968518 -181.02942702,279.89685147 -181.02945873000002,279.89685113 C-181.0661612,279.89646068 -191.71892125,279.78313592 -203.53048044,279.65748377 L-203.53048044,279.65748377 L-203.53048044,279.65748377 M-76.69178759,265.18778741 C-67.04942983000001,261.45145655 -59.00438651,258.33407132 -58.998061220000004,258.33162033 C-58.99805998,258.33161984000003 -58.998058730000004,258.33161936 -58.99805749,258.33161888 C-58.99805765,258.33161936 -58.99805781,258.33161984000003 -58.99805798,258.33162033 C-58.99954446,258.3360349 -62.40377649,268.4459642 -66.17893783,279.65748377 C-77.99049219,279.78313586 -88.64324835000001,279.89646058 -88.67995949,279.89685112 C-88.67999121,279.89685146 -88.68002293,279.89685179 -88.68005465,279.89685213 C-88.68005437000001,279.89685179 -88.6800541,279.89685146 -88.68005382,279.89685112 C-88.67760532,279.89384691 -83.22600381000001,273.20497695 -76.69178759,265.18778741 L-76.69178759,265.18778741 L-76.69178759,265.18778741 M-231.45415604000002,279.36042986 C-230.76279156,273.81360696 -230.29159102,270.03316105 -230.29120082,270.03003052 C-230.29120074,270.03002987 -230.29120066000002,270.03002922 -230.29120058,270.03002857 C-230.29119997,270.03002922 -230.29119937000002,270.03002987 -230.29119876000001,270.03003052 C-230.28818695,270.03324847 -226.54968089,274.02763343 -221.45898363,279.46675917 C-225.54191184,279.4233247 -229.05977419,279.38590146 -231.45415604000002,279.36042986 L-231.45415604000002,279.36042986 L-231.45415604000002,279.36042986 M-48.25043463,279.46675917 C-43.15973758,274.02763364000003 -39.42123162,270.03324879 -39.41821951,270.03003052 C-39.4182189,270.03002987 -39.4182183,270.03002922 -39.41821769,270.03002857 C-39.41821761,270.03002922 -39.41821753,270.03002987 -39.41821745,270.03003052 C-39.41782721,270.03316136 -38.94662667,273.81360718 -38.25526223,279.36042986 C-40.64964407,279.38590146 -44.16750642,279.42332471000003 -48.25043463,279.46675917 L-48.25043463,279.46675917 L-48.25043463,279.46675917 M-194.61907508000002,306.1226827 C-195.38320469,303.8533614 -196.50587556,300.51923991 -197.8088784,296.64956641000003 C-191.06013124,299.81159897000003 -186.10470913,302.13339377 -186.10470913,302.13339377 C-186.10470913,302.13339377 -189.55527911000001,303.75011082000003 -194.61907508000002,306.1226827 L-194.61907508000002,306.1226827 L-194.61907508000002,306.1226827 M-83.60470913,302.13339377 C-83.60470913,302.13339377 -78.64928702,299.81159897000003 -71.90053987,296.64956641000003 C-73.2035427,300.51923991 -74.32621358,303.8533614 -75.09034318,306.1226827 C-80.15413915,303.75011082000003 -83.60470913,302.13339377 -83.60470913,302.13339377 L-83.60470913,302.13339377 L-83.60470913,302.13339377 +M324.51529217,288.9978096 C324.51529217,288.9978096 294.4243193,244.44997606 294.4243193,244.44997606 C294.4243193,244.44997606 347.91211155,249.83813001000001 347.91211155,249.83813001000001 C347.91211155,249.83813001000001 301.47250119,222.75790267 301.47250119,222.75790267 C301.47250119,222.75790267 352.0011692,204.40497835 352.0011692,204.40497835 C352.0011692,204.40497835 298.41085574,200.15592835 298.41085574,200.15592835 C298.41085574,200.15592835 335.97257713,161.69694748 335.97257713,161.69694748 C335.97257713,161.69694748 285.84577938,181.12065131 285.84577938,181.12065131 C285.84577938,181.12065131 303.00099455000003,130.17288675 303.00099455000003,130.17288675 C303.00099455000003,130.17288675 284.41489007,150.0311015 273.68333613,161.49716761 C271.41364616,157.38481836 267.15327784,149.66564851 262.82626895,141.82573561 C261.71870749,132.93976082 260.62820367,124.19063956000001 260.04724319,119.52958884 C275.46883726,122.49798204 302.17767742,127.63897715 302.17767742,127.63897715 C302.17767742,127.63897715 258.35247215,96.50495226 258.35247215,96.50495226 C258.35247215,96.50495226 310.32287005,82.7552663 310.32287005,82.7552663 C310.32287005,82.7552663 257.32917734,73.71952306 257.32917734,73.71952306 C257.32917734,73.71952306 298.18712275,38.78237349 298.18712275,38.78237349 C298.18712275,38.78237349 246.52099336,53.6345517 246.52099336,53.6345517 C246.52099336,53.6345517 268.17406909,4.42966947 268.17406909,4.42966947 C268.17406909,4.42966947 228.06861361,40.22811306 228.06861361,40.22811306 C228.06861361,40.22811306 226.22816245,-13.49887143 226.22816245,-13.49887143 C226.22816245,-13.49887143 205.62675818,36.15551674 205.62675818,36.15551674 C205.62675818,36.15551674 180.65730406,-11.45228181 180.65730406,-11.45228181 C180.65730406,-11.45228181 183.64031178,42.2233904 183.64031178,42.2233904 C183.64031178,42.2233904 140.48736132,10.16408614 140.48736132,10.16408614 C140.48736132,10.16408614 153.63022372,33.97704776 161.21886807,47.72657135 C156.60641874,48.61438899 147.94852575000002,50.28088537 139.15520621000002,51.97344905 C130.36188667,50.28088537 121.70399368,48.61438899 117.09154435,47.72657135 C124.67889191,33.97939736 137.8185594,10.17222446 137.82302529,10.16410572 C137.75685999,10.213261020000001 94.73628666,42.17421931 94.67013882,42.22336141 C94.67063702,42.21373905 97.65238679000001,-11.43929805 97.65310768,-11.45227981 C97.64512328,-11.43705714 72.69103151,36.14145096 72.68365476,36.15551444 C72.67954399,36.14561 52.08780283,-13.485487710000001 52.08225031,-13.49886932 C52.081699040000004,-13.48278844 50.2422607,40.21462969 50.2417979,40.22811126 C50.23275837,40.22004347 10.14795424,4.44003347 10.13634463,4.42967126 C10.14261211,4.44391475 31.78453808,53.62346007 31.78941737,53.63455046 C31.77645289,53.63082438 -19.86124424,38.78681945 -19.87670788,38.78237525 C-19.865697570000002,38.79179037 20.97308336,73.71255262 20.98123284,73.71952276 C20.96557803,73.72219269 -31.99551057,82.75237671000001 -32.01245485,82.75526654000001 C-31.99988635,82.75859224 19.94859556,96.50247995000001 19.957913350000002,96.50497058 C19.8907232,96.55270420000001 -23.80004272,127.59122153 -23.86722549,127.6389497 C-23.85813697,127.63722016 2.84421051,122.49747479 18.26316924,119.52958884 C17.68220876,124.19063957 16.59170494,132.93976082 15.48414348,141.8257356 C11.15713459,149.6656485 6.8967662700000005,157.38481836 4.62707629,161.49716761 C-6.10447764,150.03110149 -24.69058213,130.17288674 -24.69058213,130.17288674 C-24.69058213,130.17288674 -7.53536695,181.12065132 -7.53536695,181.12065132 C-7.53536695,181.12065132 -57.662164690000004,161.69694749 -57.662164690000004,161.69694749 C-57.662164690000004,161.69694749 -20.10044332,200.15592835 -20.10044332,200.15592835 C-20.10044332,200.15592835 -73.69075677000001,204.40497834 -73.69075677000001,204.40497834 C-73.69075677000001,204.40497834 -23.16208876,222.75790267 -23.16208876,222.75790267 C-23.16208876,222.75790267 -69.60169912,249.83813001000001 -69.60169912,249.83813001000001 C-69.60169912,249.83813001000001 -16.11390686,244.44997605 -16.11390686,244.44997605 C-16.11390686,244.44997605 -46.20487974,288.9978096 -46.20487974,288.9978096 C-46.20487974,288.9978096 -0.3518765,260.93576734 -0.3518765,260.93576734 C-0.3518765,260.93576734 -8.13432566,314.12796233 -8.13432566,314.12796233 C-8.13432566,314.12796233 21.00213893,268.95006338 21.00213893,268.95006338 C21.00213893,268.95006338 37.06962297,320.25125322 37.06962297,320.25125322 C37.06962297,320.25125322 40.43373158,293.26098189 42.37615653,277.67689041 C46.62955254,279.66976054 54.613477100000004,283.41051847 62.72228602,287.20978924 C68.84136699,293.74768556 74.86620757,300.18489127 78.07591135,303.61428052 C63.85480191,310.27738553 39.22509522,321.81729547000003 39.22509522,321.81729547000003 C39.22509522,321.81729547000003 92.98055174,321.24544137 92.98055174,321.24544137 C92.98055174,321.24544137 59.01747926,362.91660443 59.01747926,362.91660443 C59.01747926,362.91660443 107.20135387,339.07776322 107.20135387,339.07776322 C107.20135387,339.07776322 94.68212296,391.35820871 94.68212296,391.35820871 C94.68212296,391.35820871 127.75100835,348.97395525 127.75100835,348.97395525 C127.75100835,348.97395525 139.15520622,401.50889975 139.15520622,401.50889975 C139.15520622,401.50889975 150.55940408,348.97395525 150.55940408,348.97395525 C150.55940408,348.97395525 183.62828947,391.35820870000003 183.62828947,391.35820870000003 C183.62828947,391.35820870000003 171.10905856,339.07776322 171.10905856,339.07776322 C171.10905856,339.07776322 219.29293316000002,362.91660443 219.29293316000002,362.91660443 C219.29293316000002,362.91660443 185.32986069,321.24544138 185.32986069,321.24544138 C185.32986069,321.24544138 239.08531721,321.81729548 239.08531721,321.81729548 C239.08531721,321.81729548 214.45561052,310.27738553 200.23450109,303.61428052 C203.44420486,300.18489127 209.46904544,293.74768556 215.58812641,287.20978923 C223.69693533,283.41051847 231.68085989,279.66976054 235.93425589,277.67689041 C237.87668084,293.26098189 241.24078946,320.25125322 241.24078946,320.25125322 C241.24078946,320.25125322 257.3082735,268.95006337 257.3082735,268.95006337 C257.3082735,268.95006337 286.44473809,314.12796232 286.44473809,314.12796232 C286.44473809,314.12796232 278.66228892,260.93576734 278.66228892,260.93576734 C278.66228892,260.93576734 324.51529217,288.9978096 324.51529217,288.9978096 L324.51529217,288.9978096 M234.59169766,266.90553454 C234.59169766,266.90553454 235.06300218,270.68681466 235.75465312,276.23593584 C233.36027128,276.26140744 229.84240892,276.29883068 225.75948071,276.34226515 C230.85222995,270.90094698 234.59169766,266.90553454 234.59169766,266.90553454 L234.59169766,266.90553454 M202.10937548,293.52507239 C200.80637264,297.39474588 199.68370177,300.72886738 198.91957216,302.99818868 C193.8557762,300.6256168 190.40520621000002,299.00889974 190.40520621000002,299.00889974 C190.40520621000002,299.00889974 195.36062832000002,296.68710495 202.10937548,293.52507239 L202.10937548,293.52507239 M215.01185786,255.20712486 C215.01185786,255.20712486 211.60696335,265.31902156 207.83097752,276.53298974 C195.99901621,276.65885893 185.3298607,276.77235811 185.3298607,276.77235811 C185.32986141,276.77235724 190.78244406000002,270.08228346 197.31812775,262.06329339 C206.96427847,258.32549279 215.01185786,255.20712486 215.01185786,255.20712486 L215.01185786,255.20712486 M202.44678150000001,236.17184782 C202.44678150000001,236.17184782 196.15354149,242.61541825 188.71897654,250.22757713000001 C179.18197632,254.94598211000002 171.10905856,258.94003627 171.10905856,258.94003627 C171.10905856,258.94003627 174.0257631,246.75984614 177.04351468000002,234.15768251 C189.96142104,235.18191293 202.44678150000001,236.17184782 202.44678150000001,236.17184782 L202.44678150000001,236.17184782 M272.69106259,162.55735648 C268.86981364,166.6401476 266.26593958,169.42224162 266.26593958,169.42224162 C266.26593958,169.42224162 265.58909116,163.99188108 264.66729705,156.59631934 C267.94492522,159.03134354 270.76893882,161.1293672 272.69106259,162.55735648 L272.69106259,162.55735648 M76.20103695,293.5250724 C82.9497841,296.68710494 87.90520621,299.00889974 87.90520621,299.00889974 C87.90520621,299.00889974 84.45463623,300.62561679 79.39084026,302.99818867 C78.62671067000001,300.72886738 77.50403978,297.39474588 76.20103695,293.5250724 L76.20103695,293.5250724 M152.9455257,186.48964616 C152.9455257,186.48964616 199.38513606,213.5698735 199.38513606,213.5698735 C199.38513606,213.5698735 190.57645553,216.76934528 180.31485123000002,220.49654519 C182.20330462,212.61034316 183.62828928,206.65959161 183.62828948,206.65959078 C183.62828948,206.65959078 177.11731206000002,215.00468358 169.87351639,224.28902649 C158.8051256,228.30926583000002 148.85646804,231.92279784000002 148.85646804,231.92279784000002 C148.85646804,231.92279784000002 154.95631383,232.40644027000002 163.04010242,233.04738486 C156.32431454000002,241.65498238 150.55940408,249.04384425 150.55940408,249.04384425 C150.55940408,249.04384425 139.15520621000002,196.50889975 139.15520621000002,196.50889975 C139.15520621000002,196.50889975 127.75100872,249.04384255 127.75100835,249.04384425 C127.75100835,249.04384425 121.98609788,241.65498238 115.27031000000001,233.04738486 C123.35409816,232.4064403 129.45394373,231.92279788000002 129.45394439,231.92279783 C129.45394439,231.92279783 119.50528683,228.30926582 108.43689604000001,224.28902648000002 C101.19310037,215.00468357 94.68212296,206.65959078 94.68212296,206.65959078 C94.68212296,206.65959078 96.10710768,212.6103426 97.9955612,220.49654519 C87.73395691,216.76934528 78.92527638,213.56987351 78.92527638,213.56987351 C78.92527638,213.56987351 125.36488672,186.48964616 125.36488672,186.48964616 C125.36488672,186.48964616 71.87709448,191.87780012000002 71.87709448,191.87780012000002 C71.87709448,191.87780012000002 77.1228644,184.11176057 83.23388352,175.06477283 C86.34148611,182.55484988 88.68641341,188.20669423 88.68641341,188.20669423 C88.68641341,188.20669423 89.04878232,177.62831584 89.45193628,165.85932837000001 C96.04341514000001,156.10104956 101.96806735,147.32996657 101.96806735,147.32996657 C101.96806735,147.32996657 96.74891054,150.52409101 89.83225042000001,154.75708766 C90.20601806,143.84595391 90.52686456,134.47970975 90.52686456,134.47970975 C90.52686456,134.47970975 130.63232006,170.27815333 130.63232006,170.27815333 C130.63232006,170.27815333 108.97924433,121.0732711 108.97924433,121.0732711 C108.97924433,121.0732711 117.9862189,123.66245657 128.4788243,126.67870817000001 C122.31564035000001,131.94877443000001 117.66503964,135.92544851 117.66503872,135.9254493 C117.66503952000001,135.92544907 127.83765302,133.00118379 139.15520621000002,129.74778899 C150.47275986,133.00118392000002 160.64537371,135.92544931 160.64537371,135.92544931 C160.64537371,135.92544931 155.99477269,131.94877496 149.83158812,126.67870817000001 C160.32419353,123.66245657 169.3311681,121.07327111000001 169.3311681,121.07327111000001 C169.3311681,121.07327111000001 147.67809237,170.27815332 147.67809237,170.27815332 C147.67809237,170.27815332 187.78354786,134.47970975 187.78354786,134.47970975 C187.78354786,134.47970975 188.10439436000001,143.84595392 188.47816201,154.75708766 C181.56150188,150.52409102000001 176.34234508,147.32996657 176.34234508,147.32996657 C176.34234508,147.32996657 182.26699728,156.10104956 188.85847614,165.85932837000001 C189.2616301,177.62831584 189.62399901,188.20669424 189.62399901,188.20669424 C189.62399901,188.20669424 191.96892632,182.55484987 195.07652891,175.06477283 C201.18754803000002,184.11176057 206.43331795,191.87780012000002 206.43331795,191.87780012000002 C206.43331621000002,191.87779994000002 152.9455257,186.48964616 152.9455257,186.48964616 L152.9455257,186.48964616 M250.27493839000002,145.90391012 C246.73837011,157.19566931 243.54936399000002,167.37771201 243.54936375,167.37771279 C243.54936375,167.37771279 238.87166594,160.12465166 233.26479014,151.43084232 C232.69075503,141.10176391000002 232.21184968,132.48443241 232.21184968,132.48443241 C232.21184968,132.48443241 240.77666466,138.84742744 250.27493839000002,145.90391012 L250.27493839000002,145.90391012 M80.99228468,262.06329339 C87.52796879,270.08228398 92.98055173,276.77235811 92.98055173,276.77235811 C92.98055173,276.77235811 82.31139622,276.65885893 70.47943491,276.53298975 C66.70344908,265.31902155 63.29855457,255.20712486 63.29855457,255.20712486 C63.29855457,255.20712486 71.34613396,258.32549278 80.99228468,262.06329339 L80.99228468,262.06329339 M219.35107121000001,155.95168364 C220.89144587,166.47997718 222.19534832,175.39200883 222.19534832,175.39200883 C222.19534832,175.39200883 211.5126104,168.85417223000002 200.45977708,162.0898372 C205.4257307,150.12068279 210.22540329,138.55230606 210.22540329,138.55230606 C210.22540329,138.55230606 214.40888482,146.52870574 219.35107121000001,155.95168364 L219.35107121000001,155.95168364 M101.26689776,234.1576825 C104.28464933000001,246.75984614 107.20135387,258.94003626 107.20135387,258.94003626 C107.20135387,258.94003626 99.12843611,254.94598211000002 89.59143588,250.22757712 C82.15687093,242.61541824 75.86363092,236.17184782 75.86363092,236.17184782 C75.86363092,236.17184782 88.3489914,235.18191294000002 101.26689776,234.1576825 L101.26689776,234.1576825 M43.71871477,266.90553455 C43.71871477,266.90553455 47.45818249,270.90094699 52.55093171,276.34226515 C48.46800351,276.29883069 44.95014116,276.26140743 42.55575931,276.23593584 C43.24741024,270.68681466 43.71871477,266.90553455 43.71871477,266.90553455 L43.71871477,266.90553455 M258.62131365,119.25512178 C257.90563389,121.54018539 256.85414627,124.89743572 255.63376069,128.79395319 C252.03250566,122.26900107 249.3882017,117.47790558 249.3882017,117.47790558 C249.3882017,117.47790558 253.1300539,118.19814816 258.62131365,119.25512178 L258.62131365,119.25512178 M68.08500914,138.55230607000001 C68.08500914,138.55230607000001 72.88468173,150.12068279 77.85063535,162.0898372 C66.79780203,168.85417222 56.1150641,175.39200883 56.1150641,175.39200883 C56.1150641,175.39200883 57.41896655,166.4799772 58.95934121,155.95168366000001 C63.90152758,146.52870584000001 68.08500908,138.55230619 68.08500914,138.55230607000001 L68.08500914,138.55230607000001 M158.52298412,100.98829973000001 C158.52298412,100.98829973000001 149.00398445,109.12788463 139.15520621000002,117.54945930000001 C129.30642798,109.12788463 119.78742831,100.98829974 119.78742831,100.98829974 C119.78742831,100.98829974 128.6662021,99.47441542 139.15520623,97.68597716000001 C149.64421035,99.47441542 158.52298412,100.98829973000001 158.52298412,100.98829973000001 L158.52298412,100.98829973000001 M157.49968932000002,78.20287054 C157.49968932000002,78.20287054 149.15613015,80.41030637 139.15520621000002,83.0562273 C129.15428228,80.41030637 120.81072311,78.20287054 120.81072311,78.20287054 C120.81072311,78.20287054 129.50896438,72.02352105 139.15520622,65.17069899 C148.80144791,72.02352094 157.49968906,78.20287035 157.49968932000002,78.20287054 L157.49968932000002,78.20287054 M45.04562228,151.43084233 C39.43874649,160.12465167 34.76104868,167.3777128 34.76104868,167.3777128 C34.76104868,167.3777128 31.57204245,157.19566976000002 28.03547403,145.90391012 C37.53374777,138.84742744 46.09856274,132.48443241 46.09856274,132.48443241 C46.09856274,132.48443241 45.61965739,141.10176391000002 45.04562228,151.43084233 L45.04562228,151.43084233 M161.92053822,48.99789595 C164.62267245,53.89377151 166.46395977,57.22991723 166.46395977,57.22991723 C166.46395977,57.22991723 161.09022233000002,56.19556464 153.77177509,54.78688867 C157.10046353,52.422142900000004 159.96847079,50.38467133 161.92053822,48.99789595 L161.92053822,48.99789595 M13.643115380000001,156.59631934 C12.72132128,163.99188108 12.04447285,169.42224162 12.04447285,169.42224162 C12.04447285,169.42224162 9.4405988,166.6401476 5.61934984,162.55735649 C7.54147361,161.12936719 10.36548721,159.03134353000002 13.643115380000001,156.59631934 L13.643115380000001,156.59631934 M116.38987421,48.99789595 C118.34194163000001,50.38467133 121.2099489,52.42214289 124.53863734000001,54.78688866 C117.2201901,56.19556464 111.84645266,57.229917220000004 111.84645266,57.229917220000004 C111.84645266,57.229917220000004 113.68773997,53.8937715 116.38987421,48.99789595 L116.38987421,48.99789595 M28.92221073,117.47790558 C28.92221073,117.47790558 26.277906780000002,122.26900107 22.67665174,128.79395318000002 C21.456266160000002,124.89743572 20.40477854,121.54018539 19.689098780000002,119.25512178 C25.18035854,118.19814816 28.92221073,117.47790558 28.92221073,117.47790558 L28.92221073,117.47790558 diff --git a/vendor/linesweeper/tests/regression/graphite_7.txt b/vendor/linesweeper/tests/regression/graphite_7.txt new file mode 100644 index 00000000..2eeee43c --- /dev/null +++ b/vendor/linesweeper/tests/regression/graphite_7.txt @@ -0,0 +1,2 @@ +M0,-111.5 C-2.03969458,-24.64993384 -0.75486715,-23.863645860000002 0.38532272,-22.922613090000002 C1.527117,-21.98353102 2.54469833,-20.872877210000002 3.39448531,-19.60851345 L0,-111.5 +M-17.01537334,2.69695098 L-1.99367877,-24.5346243 C5.5261131,-20.38649788 8.259388,-10.92778124 4.11126158,-3.40798937 L-17.01537334,2.69695098 diff --git a/vendor/linesweeper/tests/regression/sasha_0.txt b/vendor/linesweeper/tests/regression/sasha_0.txt new file mode 100644 index 00000000..d7c3c3a4 --- /dev/null +++ b/vendor/linesweeper/tests/regression/sasha_0.txt @@ -0,0 +1,2 @@ +M6025,9840.5 C6025,9811.9819969761829 6004.7190026423077,9772.5 5926,9772.5 C5848.2352234822274,9772.5 5830,9811.2112664255637 5830,9830.5 C5830,9850.7486624496178 5841.3744295221686,9878.5670833890726 5926.9050692843539,9897.4042733062724 C5923.1414656954139,9896.5753806921766 5920.6158532561158,9896.6881708042274 5919.7428657446362,9896.7806765477999 C5922.2781984304556,9896.5120211177727 5929.7581421036521,9894.1586192843624 5943.034773076467,9893.7664964749565 C5964.0285023729066,9893.1464506117791 5992.0966123945063,9900.6639968701948 6009.9213151129179,9929.2664268136887 C6020.7494209612541,9946.6417594540435 6036.6421514647836,9954.2087579702147 6069.7403859423248,9970.9765860094976 C6096.4064338250982,9984.4858173157481 6154,10013.416342020173 6154,10086.5 C6154,10204.291870660425 6051.8757061959668,10272.5 5926,10272.5 C5793.4238484174057,10272.5 5699,10181.937205348144 5699,10073.5 C5699,10073.5 5819,10073.5 5819,10073.5 C5819,10105.062794651856 5848.5761515825943,10152.5 5926,10152.5 C6014.1242938040332,10152.5 6034,10112.708129339575 6034,10086.5 C6034,10086.04891236476 6033.9807017554267,10085.850609014404 6033.9729096025885,10085.785708608404 C6034.1402097365763,10087.179142085055 6035.2683900531028,10089.422252966504 6036.0674605371369,10090.290908271427 C6031.784136836327,10085.634583291656 6025.5593821736265,10083.613798019922 5993.9139736805519,10067.1130608739 C5968.463992389773,10053.842780234541 5933.1526880433003,10032.968601506755 5908.0786848870821,9992.7335731863113 C5922.5320746617581,10015.926221894515 5948.1042351958995,10013.643209438689 5948.6368267411644,10013.571880444044 C5943.041688160647,10014.321226943064 5926.1957043674656,10020.123898138048 5901.0949307156452,10014.595726693728 C5804.5118859197682,9993.3243651949706 5710,9942.251337550375 5710,9830.5 C5710,9721.7887335744363 5807.7647765177726,9652.5 5926,9652.5 C6055.2809973576923,9652.5 6145,9731.0180030238171 6145,9840.5 C6145,9840.5 6025,9840.5 6025,9840.5 Z +M6025,9840.5 C6025,9811.9819969761829 6004.7190026423077,9772.5 5926,9772.5 C5926,9772.5 5926,9652.5 5926,9652.5 C6055.2809973576923,9652.5 6145,9731.0180030238171 6145,9840.5 C6145,9840.5 6025,9840.5 6025,9840.5 Z M5926,9772.5 C5848.2352234822274,9772.5 5830,9811.2112664255637 5830,9830.5 C5830,9830.5 5710,9830.5 5710,9830.5 C5710,9721.7887335744363 5807.7647765177726,9652.5 5926,9652.5 C5926,9652.5 5926,9772.5 5926,9772.5 Z M5830,9830.5 C5830,9850.7486624496178 5841.3744295221686,9878.5670833890726 5926.9050692843539,9897.4042733062724 C5926.9050692843539,9897.4042733062724 5901.0949307156461,10014.595726693728 5901.0949307156461,10014.595726693728 C5804.5118859197692,9993.3243651949706 5710,9942.251337550375 5710,9830.5 C5710,9830.5 5830,9830.5 5830,9830.5 Z M5926.9050692843502,9897.4042733062724 C5923.1414656954148,9896.5753806921766 5920.6158532561158,9896.6881708042274 5919.7428657446362,9896.7806765477999 C5919.7428657446362,9896.7806765477999 5932.3878200776235,10016.112588163747 5932.3878200776235,10016.112588163747 C5924.6825868815522,10016.929069862314 5913.6510111797834,10017.361066377565 5901.0949307156498,10014.595726693728 C5901.0949307156498,10014.595726693728 5926.9050692843502,9897.4042733062724 5926.9050692843502,9897.4042733062724 Z M5919.7428657446435,9896.7806765477981 C5922.2781984304638,9896.5120211177709 5929.7581421036521,9894.1586192843624 5943.034773076467,9893.7664964749565 C5964.0285023729066,9893.1464506117791 5992.0966123945063,9900.6639968701948 6009.9213151129161,9929.2664268136868 C6009.9213151129161,9929.2664268136868 5908.0786848870839,9992.7335731863132 5908.0786848870839,9992.7335731863132 C5922.5320813616336,10015.926232645474 5948.1042554148153,10013.643206799046 5948.6368267411754,10013.571880444044 C5946.7576348925104,10013.823557352434 5941.1787512170895,10015.181060952815 5932.3878200776162,10016.112588163749 C5932.3878200776162,10016.112588163749 5919.7428657446435,9896.7806765477981 5919.7428657446435,9896.7806765477981 Z M5919.7428657446389,9896.7806765477981 C5952.695463221462,9893.2888688275925 5982.2394909988989,9917.1715577124596 5985.7312987191053,9950.1241551892817 C5989.2231064393118,9983.0767526661039 5965.3404175544438,10012.620780443543 5932.3878200776207,10016.112588163749 C5899.4352226007977,10019.604395883955 5869.8911948233608,9995.7217069990875 5866.3993871031544,9962.7691095222654 C5862.9075793829479,9929.8165120454432 5886.7902682678159,9900.2724842680036 5919.7428657446389,9896.7806765477981 Z M6009.9213151129179,9929.2664268136887 C6020.7494209612541,9946.6417594540435 6036.6421514647836,9954.2087579702147 6069.7403859423248,9970.9765860094976 C6096.4064338250982,9984.4858173157481 6154,10013.416342020173 6154,10086.5 C6154,10086.5 6034,10086.5 6034,10086.5 C6034,10086.04891236476 6033.9807017554267,10085.850609014404 6033.9729096025885,10085.785708608404 C6034.1402097365763,10087.179142085055 6035.2683900531028,10089.422252966504 6036.0674605371369,10090.290908271427 C6031.784136836327,10085.634583291656 6025.5593821736265,10083.613798019922 5993.9139736805519,10067.1130608739 C5968.463992389773,10053.842780234541 5933.1526880433003,10032.968601506755 5908.0786848870821,9992.7335731863113 C5908.0786848870821,9992.7335731863113 6009.9213151129179,9929.2664268136887 6009.9213151129179,9929.2664268136887 Z M6154,10086.5 C6154,10204.291870660425 6051.8757061959668,10272.5 5926,10272.5 C5926,10272.5 5926,10152.5 5926,10152.5 C6014.1242938040332,10152.5 6034,10112.708129339575 6034,10086.5 C6034,10086.5 6154,10086.5 6154,10086.5 Z M5926,10272.5 C5793.4238484174057,10272.5 5699,10181.937205348144 5699,10073.5 C5699,10073.5 5819,10073.5 5819,10073.5 C5819,10105.062794651856 5848.5761515825943,10152.5 5926,10152.5 C5926,10152.5 5926,10272.5 5926,10272.5 Z M5926.9050692843539,9897.4042733062724 C5959.2665955425527,9904.5315462675317 5979.7229996549859,9936.543543026155 5972.5957266937276,9968.9050692843539 C5965.4684537324692,10001.266595542553 5933.456456973845,10021.722999654987 5901.0949307156461,10014.595726693728 C5868.7334044574473,10007.468453732468 5848.2770003450141,9975.456456973845 5855.4042733062724,9943.0949307156461 C5862.5315462675308,9910.7334044574473 5894.543543026155,9890.2770003450132 5926.9050692843539,9897.4042733062724 Z M6009.9213151129179,9929.2664268136887 C6027.447283641357,9957.3894925918812 6018.8566389645039,9994.3953465844788 5990.7335731863113,10011.921315112919 C5962.6105074081188,10029.447283641359 5925.6046534155212,10020.856638964504 5908.0786848870821,9992.7335731863113 C5890.552716358643,9964.6105074081188 5899.1433610354961,9927.6046534155212 5927.2664268136887,9910.0786848870812 C5955.3894925918812,9892.5527163586412 5992.3953465844788,9901.1433610354961 6009.9213151129179,9929.2664268136887 Z diff --git a/vendor/linesweeper/tests/snapshots.rs b/vendor/linesweeper/tests/snapshots.rs new file mode 100644 index 00000000..b156b6bd --- /dev/null +++ b/vendor/linesweeper/tests/snapshots.rs @@ -0,0 +1,567 @@ +use kurbo::{Affine, BezPath, ParamCurve as _, Point, Rect, Vec2}; +use libtest_mimic::{Arguments, Failed, Trial}; +use linesweeper::{ + Segment, Segments, + sweep::{SweepLineBuffers, SweepLineRange, SweepLineRangeBuffers, Sweeper, Correctness}, + topology::Topology, +}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; +use tiny_skia::Pixmap; + +fn main() { + let args = Arguments::from_args(); + let mut tests = sweep_snapshot_diffs(); + tests.extend(position_snapshot_diffs()); + tests.extend(output_snapshot_diffs()); + + libtest_mimic::run(&args, tests).exit(); +} + +fn path_color(idx: usize) -> tiny_skia::Color { + let palette = [ + tiny_skia::Color::from_rgba8(0x00, 0x5F, 0x73, 0xFF), + tiny_skia::Color::from_rgba8(0x94, 0xD2, 0xBD, 0xFF), + tiny_skia::Color::from_rgba8(0xE9, 0xD8, 0xA6, 0xFF), + tiny_skia::Color::from_rgba8(0xEE, 0x9B, 0x00, 0xFF), + tiny_skia::Color::from_rgba8(0xCA, 0x67, 0x02, 0xFF), + tiny_skia::Color::from_rgba8(0xBB, 0x3E, 0x03, 0xFF), + tiny_skia::Color::from_rgba8(0xAE, 0x20, 0x12, 0xFF), + ]; + palette[idx % palette.len()] +} + +fn sweep_line_color() -> tiny_skia::Color { + tiny_skia::Color::from_rgba8(0x9B, 0x22, 0x26, 0xFF) +} + +fn sweep_snapshot_diffs() -> Vec { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let paths = glob::glob(&format!("{ws}/tests/snapshots/inputs/sweep/**/*.svg")).unwrap(); + paths + .into_iter() + .map(|p| { + let p = p.unwrap(); + let name = input_path_base(&p).display().to_string(); + Trial::test(name, || generate_sweep_snapshot(p)) + }) + .collect() +} + +fn position_snapshot_diffs() -> Vec { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let paths = glob::glob(&format!("{ws}/tests/snapshots/inputs/position/**/*.svg")).unwrap(); + + paths + .into_iter() + .map(|p| { + let p = p.unwrap(); + let name = input_path_base(&p).display().to_string(); + Trial::test(name, || { + generate_position_snapshot(p.clone(), false).and(generate_position_snapshot(p, true)) + }) + }) + .collect() +} + +fn output_snapshot_diffs() -> Vec { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let paths = glob::glob(&format!("{ws}/tests/snapshots/inputs/output/**/*.svg")).unwrap(); + + paths + .into_iter() + .map(|p| { + let p = p.unwrap(); + let name = input_path_base(&p).display().to_string(); + Trial::test(name, || { + generate_output_snapshot(p.clone(), false).and(generate_output_snapshot(p, true)) + }) + }) + .collect() +} + +fn input_path_base(input_path: &Path) -> &Path { + let ws = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let base = format!("{ws}/tests/snapshots/inputs"); + input_path.strip_prefix(base).unwrap() +} + +fn output_path_for(input_path: &Path, correct: bool) -> PathBuf { + let mut ws: PathBuf = std::env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); + ws.push("target/snapshots/snapshots"); + if correct { + ws.push("correct"); + } + ws.push(input_path); + ws.set_extension("png"); + ws +} + +fn saved_snapshot_path_for(input_path: &Path, correct: bool) -> PathBuf { + let mut ws: PathBuf = std::env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); + ws.push("tests/snapshots/snapshots"); + if correct { + ws.push("correct"); + } + ws.push(input_path); + ws.set_extension("png"); + ws +} + +fn skia_path(elts: impl IntoIterator) -> tiny_skia::Path { + let mut pb = tiny_skia::PathBuilder::new(); + for elt in elts { + match elt { + kurbo::PathEl::MoveTo(p) => pb.move_to(p.x as f32, p.y as f32), + kurbo::PathEl::LineTo(p) => pb.line_to(p.x as f32, p.y as f32), + kurbo::PathEl::QuadTo(p0, p1) => { + pb.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32) + } + kurbo::PathEl::CurveTo(p0, p1, p2) => pb.cubic_to( + p0.x as f32, + p0.y as f32, + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + ), + kurbo::PathEl::ClosePath => pb.close(), + } + } + pb.finish().unwrap() +} + +fn skia_kurbo_seg(seg: kurbo::PathSeg) -> tiny_skia::Path { + let mut pb = tiny_skia::PathBuilder::new(); + let start = seg.start(); + pb.move_to(start.x as f32, start.y as f32); + match seg { + kurbo::PathSeg::Line(ell) => pb.line_to(ell.p1.x as f32, ell.p1.y as f32), + kurbo::PathSeg::Quad(q) => { + pb.quad_to(q.p1.x as f32, q.p1.y as f32, q.p2.x as f32, q.p2.y as f32) + } + kurbo::PathSeg::Cubic(c) => pb.cubic_to( + c.p1.x as f32, + c.p1.y as f32, + c.p2.x as f32, + c.p2.y as f32, + c.p3.x as f32, + c.p3.y as f32, + ), + } + pb.finish().unwrap() +} + +fn skia_cubic(s: &kurbo::CubicBez) -> tiny_skia::Path { + let mut pb = tiny_skia::PathBuilder::new(); + pb.move_to(s.p0.x as f32, s.p0.y as f32); + pb.cubic_to( + s.p1.x as f32, + s.p1.y as f32, + s.p2.x as f32, + s.p2.y as f32, + s.p3.x as f32, + s.p3.y as f32, + ); + pb.finish().unwrap() +} + +fn skia_segment(s: &Segment) -> tiny_skia::Path { + skia_cubic(&s.to_kurbo_cubic()) +} + +fn line(p: impl Into, q: impl Into) -> tiny_skia::Path { + let mut pb = tiny_skia::PathBuilder::new(); + let p = p.into(); + pb.move_to(p.x as f32, p.y as f32); + let q = q.into(); + pb.line_to(q.x as f32, q.y as f32); + pb.finish().unwrap() +} + +fn two_lines(p: impl Into, q: impl Into, r: impl Into) -> tiny_skia::Path { + let mut pb = tiny_skia::PathBuilder::new(); + let p = p.into(); + pb.move_to(p.x as f32, p.y as f32); + let q = q.into(); + pb.line_to(q.x as f32, q.y as f32); + let r = r.into(); + pb.line_to(r.x as f32, r.y as f32); + pb.finish().unwrap() +} + +fn draw_orig_path(pixmap: &mut Pixmap, path: &BezPath, offset: kurbo::Point) { + let p = skia_path(path); + let mut paint = tiny_skia::Paint::default(); + paint.set_color_rgba8(0, 0, 0, 255); + let stroke = tiny_skia::Stroke { + width: 1.0, + ..Default::default() + }; + let transform = tiny_skia::Transform::from_translate(offset.x as f32, offset.y as f32); + + pixmap.stroke_path(&p, &paint, &stroke, transform, None); +} + +fn adjust_x_positions(orig: &[f64], padding: f64, min_x: f64, max_x: f64) -> Vec { + if orig.is_empty() { + return Vec::new(); + } + + let mut padded = Vec::with_capacity(orig.len()); + let mut max_so_far = f64::NEG_INFINITY; + for &x in orig { + let x = x.max(max_so_far + padding); + padded.push(x); + max_so_far = x; + } + + let orig_first = *orig.first().unwrap(); + let orig_last = *orig.last().unwrap(); + let padded_min = *padded.first().unwrap(); + let padded_max = *padded.last().unwrap(); + + let mut mid_shift = (orig_first + orig_last - padded_min - padded_max) / 2.0; + mid_shift = mid_shift.clamp(min_x - padded_min, max_x - padded_max); + if padded_max - padded_min > max_x - min_x { + mid_shift = (max_x + min_x - padded_max - padded_min) / 2.0; + } + for x in &mut padded { + *x += mid_shift; + } + padded +} + +fn color(c: tiny_skia::Color) -> tiny_skia::Paint<'static> { + let mut p = tiny_skia::Paint::default(); + p.set_color(c); + p +} + +fn draw_sweep_line_range( + pixmap: &mut Pixmap, + segments: &Segments, + range: SweepLineRange<'_, '_, '_>, + bbox: kurbo::Rect, + padding: f64, +) { + let y = range.line().y(); + let mut paint = tiny_skia::Paint::default(); + paint.set_color(sweep_line_color()); + let stroke = tiny_skia::Stroke { + width: 2.0, + ..Default::default() + }; + let thick_stroke = tiny_skia::Stroke { + width: 4.0, + ..Default::default() + }; + let s_line = line( + (bbox.min_x() - padding, bbox.min_y() + y), + (bbox.max_x() + padding, bbox.min_y() + y), + ); + + pixmap.stroke_path( + &s_line, + &paint, + &stroke, + tiny_skia::Transform::identity(), + None, + ); + + let old_segs: Vec<_> = range.old_segment_range().collect(); + let new_segs: Vec<_> = range.segment_range().collect(); + let really_new_segs = new_segs.iter().filter(|s| !old_segs.contains(s)); + let seg_color: HashMap<_, _> = old_segs + .iter() + .chain(really_new_segs.clone()) + .enumerate() + .map(|(color_idx, seg_idx)| (seg_idx, color_idx)) + .collect(); + let origin = bbox.origin(); + + for seg_idx in old_segs.iter().chain(really_new_segs) { + let color_idx = seg_color[seg_idx]; + let seg = &segments[*seg_idx]; + let p = skia_segment(seg); + let mut paint = tiny_skia::Paint::default(); + paint.set_color(path_color(color_idx)); + let transform = tiny_skia::Transform::from_translate(origin.x as f32, origin.y as f32); + pixmap.stroke_path(&p, &paint, &stroke, transform, None); + } + + let old_seg_positions: Vec<_> = old_segs.iter().map(|s| segments[*s].at_y(y)).collect(); + let padded_old_seg_positions: Vec<_> = adjust_x_positions( + &old_seg_positions, + padding / 2.0, + bbox.min_x() - padding, + bbox.max_x() + padding, + ); + + for ((&px, &x), seg_idx) in padded_old_seg_positions + .iter() + .zip(&old_seg_positions) + .zip(&old_segs) + { + let p = two_lines( + (bbox.min_x() + px, bbox.min_y()), + (bbox.min_x() + px, bbox.min_y() + y - padding / 2.0), + (bbox.min_x() + x, bbox.min_y() + y), + ); + let color_idx = seg_color[seg_idx]; + let c = path_color(color_idx); + + pixmap.stroke_path( + &p, + &color(c), + &thick_stroke, + tiny_skia::Transform::identity(), + None, + ); + } + let new_seg_positions: Vec<_> = new_segs.iter().map(|s| segments[*s].at_y(y)).collect(); + let padded_new_seg_positions: Vec<_> = adjust_x_positions( + &new_seg_positions, + padding / 2.0, + bbox.min_x() - padding, + bbox.max_x() + padding, + ); + for ((&px, &x), seg_idx) in padded_new_seg_positions + .iter() + .zip(&new_seg_positions) + .zip(&new_segs) + { + let p = two_lines( + (bbox.min_x() + x, bbox.min_y() + y), + (bbox.min_x() + px, bbox.min_y() + y + padding / 2.0), + (bbox.min_x() + px, bbox.max_y()), + ); + let color_idx = seg_color[seg_idx]; + let c = path_color(color_idx); + + pixmap.stroke_path( + &p, + &color(c), + &thick_stroke, + tiny_skia::Transform::identity(), + None, + ); + } +} + +fn generate_sweep_snapshot(path: PathBuf) -> Result<(), Failed> { + let input = std::fs::read_to_string(&path).unwrap(); + let tree = usvg::Tree::from_str(&input, &usvg::Options::default()).unwrap(); + let bezs = linesweeper_util::svg_to_bezpaths(&tree); + let bbox = linesweeper_util::bezier_bounding_box(bezs.iter()); + let bez: BezPath = bezs + .into_iter() + .flat_map(|p| Affine::translate(-bbox.origin().to_vec2()) * p) + .collect(); + + let mut segments = Segments::default(); + segments.add_bez_path(&bez).unwrap(); + segments.check_invariants(); + + let eps = 16.0; + let mut range_bufs = SweepLineRangeBuffers::default(); + let mut line_bufs = SweepLineBuffers::default(); + + // Run through once just to count the ranges. + let mut sweep_state = Sweeper::new(&segments, eps); + let mut num_ranges = 0; + while let Some(mut line) = sweep_state.next_line(&mut line_bufs) { + while line.next_range(&mut range_bufs, &segments).is_some() { + num_ranges += 1; + } + } + + let pad = 32.0; + let mut sweep_state = Sweeper::new(&segments, eps); + let mut pixmap = Pixmap::new( + (bbox.width() + 2.0 * pad).ceil() as u32, + ((bbox.height() + pad) * num_ranges as f64 + pad).ceil() as u32, + ) + .unwrap(); + + let mut b = Rect::new(pad, pad, pad + bbox.width(), pad + bbox.height()); + while let Some(mut line) = sweep_state.next_line(&mut line_bufs) { + while let Some(range) = line.next_range(&mut range_bufs, &segments) { + draw_orig_path(&mut pixmap, &bez, b.origin()); + draw_sweep_line_range(&mut pixmap, &segments, range, b, pad); + + b = b + Vec2::new(0.0, bbox.height() + pad); + } + } + + let base_path = input_path_base(&path); + let out_path = output_path_for(base_path, false); + std::fs::create_dir_all(out_path.parent().unwrap()).unwrap(); + pixmap.save_png(&out_path).unwrap(); + + let new_image = kompari::load_image(&out_path)?; + let snapshot = kompari::load_image(&saved_snapshot_path_for(base_path, false))?; + match kompari::compare_images(&snapshot, &new_image) { + kompari::ImageDifference::None => Ok(()), + _ => Err("image comparison failed".into()), + } +} + +fn generate_position_snapshot(path: PathBuf, correct: bool) -> Result<(), Failed> { + let input = std::fs::read_to_string(&path).unwrap(); + let tree = usvg::Tree::from_str(&input, &usvg::Options::default()).unwrap(); + let bezs = linesweeper_util::svg_to_bezpaths(&tree); + let bbox = linesweeper_util::bezier_bounding_box(bezs.iter()); + let bez: BezPath = bezs + .into_iter() + .flat_map(|p| Affine::translate(-bbox.origin().to_vec2()) * p) + .collect(); + + let eps = 16.0; + let correctness = if correct { Correctness::ReallyCorrect } else { Correctness::GoodEnough }; + let top = + Topology::::from_paths_with_correctness([(&bez, ())], eps, correctness).unwrap(); + let out_paths = top.compute_positions(); + + let pad = 2.0 * eps; + let bbox = top.bounding_box(); + let mut pixmap = Pixmap::new( + (bbox.width() + 2.0 * pad).ceil() as u32, + (bbox.height() + 2.0 * pad).ceil() as u32, + ) + .unwrap(); + let pad_transform = tiny_skia::Transform::from_translate( + (pad - bbox.min_x()) as f32, + (pad - bbox.min_y()) as f32, + ); + + let stroke = tiny_skia::Stroke { + width: 1.0, + ..Default::default() + }; + for out_idx in top.segment_indices() { + let path = &out_paths[out_idx].path; + let far_idx = &out_paths[out_idx].copied_idx; + for (idx, seg) in path.segments().enumerate() { + let skia_seg = skia_kurbo_seg(seg); + + let c = if far_idx == &Some(idx) { + path_color(0) + } else { + path_color(3) + }; + + pixmap.stroke_path(&skia_seg, &color(c), &stroke, pad_transform, None); + + let p0 = seg.start(); + let p0 = tiny_skia::PathBuilder::from_circle(p0.x as f32, p0.y as f32, 2.0).unwrap(); + let p1 = seg.end(); + let p1 = tiny_skia::PathBuilder::from_circle(p1.x as f32, p1.y as f32, 2.0).unwrap(); + let black = color(tiny_skia::Color::BLACK); + pixmap.fill_path( + &p0, + &black, + tiny_skia::FillRule::Winding, + pad_transform, + None, + ); + pixmap.fill_path( + &p1, + &black, + tiny_skia::FillRule::Winding, + pad_transform, + None, + ); + } + } + let base_path = input_path_base(&path); + let out_path = output_path_for(base_path, correct); + std::fs::create_dir_all(out_path.parent().unwrap()).unwrap(); + pixmap.save_png(&out_path).unwrap(); + + let new_image = kompari::load_image(&out_path)?; + let snapshot = kompari::load_image(&saved_snapshot_path_for(base_path, correct))?; + match kompari::compare_images(&snapshot, &new_image) { + kompari::ImageDifference::None => Ok(()), + _ => Err("image comparison failed".into()), + } +} + +fn generate_output_snapshot(path: PathBuf, correct: bool) -> Result<(), Failed> { + let input = std::fs::read_to_string(&path).unwrap(); + let tree = usvg::Tree::from_str(&input, &usvg::Options::default()).unwrap(); + let bezs = linesweeper_util::svg_to_bezpaths(&tree); + let bbox = linesweeper_util::bezier_bounding_box(bezs.iter()); + let bez: BezPath = bezs + .into_iter() + .flat_map(|p| Affine::translate(-bbox.origin().to_vec2()) * p) + .collect(); + + let eps = 1.0; + let correctness = if correct { Correctness::ReallyCorrect } else { Correctness::GoodEnough }; + let top = + Topology::::from_paths_with_correctness([(&bez, ())], eps, correctness).unwrap(); + let contours = top.contours(|w| w % 2 == 0); + + let pad = 2.0 * eps; + let bbox = top.bounding_box(); + let mut pixmap = Pixmap::new( + (bbox.width() + 2.0 * pad).ceil() as u32, + (bbox.height() + 2.0 * pad).ceil() as u32, + ) + .unwrap(); + let pad_transform = tiny_skia::Transform::from_translate( + (pad - bbox.min_x()) as f32, + (pad - bbox.min_y()) as f32, + ); + + let stroke = tiny_skia::Stroke { + width: 1.0, + ..Default::default() + }; + for contour in contours.contours() { + for seg in contour.path.segments() { + let skia_seg = skia_kurbo_seg(seg); + pixmap.stroke_path( + &skia_seg, + &color(path_color(0)), + &stroke, + pad_transform, + None, + ); + + let p0 = seg.start(); + let p0 = tiny_skia::PathBuilder::from_circle(p0.x as f32, p0.y as f32, 2.0).unwrap(); + let p1 = seg.end(); + let p1 = tiny_skia::PathBuilder::from_circle(p1.x as f32, p1.y as f32, 2.0).unwrap(); + let black = color(tiny_skia::Color::BLACK); + pixmap.fill_path( + &p0, + &black, + tiny_skia::FillRule::Winding, + pad_transform, + None, + ); + pixmap.fill_path( + &p1, + &black, + tiny_skia::FillRule::Winding, + pad_transform, + None, + ); + } + } + let base_path = input_path_base(&path); + let out_path = output_path_for(base_path, correct); + std::fs::create_dir_all(out_path.parent().unwrap()).unwrap(); + pixmap.save_png(&out_path).unwrap(); + + let new_image = kompari::load_image(&out_path)?; + let snapshot = kompari::load_image(&saved_snapshot_path_for(base_path, correct))?; + match kompari::compare_images(&snapshot, &new_image) { + kompari::ImageDifference::None => Ok(()), + _ => Err("image comparison failed".into()), + } +}